SenShaMart/app/p2p-server.js
Josip Milovac 9847b2056b miner mines continuously,
moved to an account model,
transactions working,
querying working,
more checking of valid data
2023-01-10 17:11:56 +11:00

214 lines
No EOL
5.8 KiB
JavaScript

const Websocket = require('ws');
const fs = require('fs');
const process = require('process');
const Miner = require('./miner');
const Transaction = require('../wallet/transaction');
const TransactionPool = require('../wallet/transaction-pool');
const Metadata = require('../wallet/metadata');
const Blockchain = require('../blockchain');
const P2P_PORT = process.env.P2P_PORT || 5000;
const peers = process.env.PEERS ? process.env.PEERS.split(',') : [];
const MESSAGE_TYPES = {
chain: 'CHAIN',
transaction: 'TRANSACTION',
clear_transactions: 'CLEAR_TRANSACTIONS',
metadata: 'METADATA'
};
class P2pServer {
constructor(transactionPool, rewardPublicKey, chainStorageLocation) {
this.blockchain = new Blockchain();
this.transactionPool = transactionPool;
this.sockets = [];
this.chainStorageLocation = chainStorageLocation;
//possible race if deleted after check, but we live with it I guess
if (fs.existsSync(this.chainStorageLocation)) {
const rawPersistedChain = fs.readFileSync(this.chainStorageLocation, 'utf8');
const deserialized = Blockchain.deserialize(rawPersistedChain);
if (deserialized === null) {
console.log(`Couldn't deserialize chain at '${this.chainStorageLocation}', starting from genesis`);
} else {
this.blockchain = deserialized;
}
} else {
console.log("Didn't find a persisted chain, starting from genesis");
}
this.miner = new Miner(this.blockchain, this.transactionPool, rewardPublicKey, this);
}
listen() {
const server = new Websocket.Server({ port: P2P_PORT });
server.on('connection', socket => this.connectSocket(socket));
this.connectToPeers();
console.log(`Listening for peer-to-peer connections on: ${P2P_PORT}`);
}
connectToPeers() {
peers.forEach(peer => {
const socket = new Websocket(peer);
socket.on('open', () => this.connectSocket(socket));
});
}
connectSocket(socket) {
this.sockets.push(socket);
console.log('Socket connected');
this.messageHandler(socket);
this.sendChain(socket);
}
messageHandler(socket) {
socket.on('message', message => {
const data = JSON.parse(message);
switch(data.type) {
case MESSAGE_TYPES.chain:
this.newChain(data.chain);
break;
case MESSAGE_TYPES.transaction:
this.newTransaction(data.transaction, false);
break;
case MESSAGE_TYPES.metadata:
this.newMetadata(data.metadata, false);
break;
//case MESSAGE_TYPES.clear_transactions:
// this.transactionPool.clear();
// break;
}
});
}
newMetadata(metadata, broadcast) {
if (!Metadata.verifyMetadata(metadata)) {
console.log("Couldn't add metadata to p2pServer, couldn't verify");
return;
}
switch (this.transactionPool.updateOrAddMetadata(metadata)) {
case TransactionPool.Return.add:
this.miner.startMine();
break;
case TransactionPool.Return.update:
this.miner.interruptIfContainsMetadata(metadata);
break;
case TransactionPool.Return.error:
console.log("Couldn't add metadata to p2pServer, couldn't updateOrAdd");
return;
}
if (broadcast === undefined || broadcast) {
this.broadcastMetadata(metadata);
}
}
newTransaction(transaction, broadcast) {
if (!Transaction.verify(transaction)) {
console.log("Couldn't add transaction to p2pServer, couldn't verify");
return false;
}
switch (this.transactionPool.updateOrAddTransaction(transaction)) {
case TransactionPool.Return.add:
this.miner.startMine();
break;
case TransactionPool.Return.update:
this.miner.interruptIfContainsTransaction(transaction);
break;
case TransactionPool.Return.error:
console.log("Couldn't add transaction to p2pServer, couldn't updateOrAdd");
return;
}
if (broadcast === undefined || broadcast) {
this.broadcastTransaction(transaction);
}
}
blockMined(block) {
if (!this.blockchain.addBlock(block)) {
//invalid block, return
return;
}
this.transactionPool.clearFromBlock(block);
this.miner.interrupt();
this.persistChain(this.blockchain);
this.syncChains();
}
newChain(chain, persist) {
const replaceResult = this.blockchain.replaceChain(chain);
if (!replaceResult.result) {
//failed to replace
return;
}
for (let i = 0; i < replaceResult.chainDifference; i++) {
this.transactionPool.clearFromBlock(this.blockchain.chain[i]);
}
this.miner.interrupt();
if (typeof persist === "undefined" || persist) {
this.persistChain(this.blockchain);
}
}
persistChain(chain) {
try {
fs.writeFileSync(
this.chainStorageLocation,
chain.serialize());
} catch (err) {
console.error(`Couldn't persist chain, aborting: ${err}`);
process.exit(-1);
}
}
sendChain(socket) {
socket.send(JSON.stringify({
type: MESSAGE_TYPES.chain,
chain: this.blockchain.serialize()
}));
}
sendTransaction(socket, transaction) {
socket.send(JSON.stringify({
type: MESSAGE_TYPES.transaction,
transaction
}));
}
sendMetadata(socket, metadata) {
socket.send(JSON.stringify({
type: MESSAGE_TYPES.metadata,
metadata
}));
}
syncChains() {
this.sockets.forEach(socket => this.sendChain(socket));
}
broadcastTransaction(transaction) {
this.sockets.forEach(socket => this.sendTransaction(socket, transaction));
}
broadcastMetadata(metadata) {
this.sockets.forEach(socket => this.sendMetadata(socket, metadata));
}
//broadcastClearTransactions() {
// this.sockets.forEach(socket => socket.send(JSON.stringify({
// type: MESSAGE_TYPES.clear_transactions
// })));
//}
}
module.exports = P2pServer;