Commit some new networking code, adding integration, broker still not 100%, hasn't been committed
This commit is contained in:
parent
1af6d56e2d
commit
050e69e23f
18 changed files with 1829 additions and 359 deletions
283
network/blockchain-prop.js
Normal file
283
network/blockchain-prop.js
Normal file
|
@ -0,0 +1,283 @@
|
|||
const Websocket = require('ws');
|
||||
const Assert = require('assert');
|
||||
const ChainUtil = require('../chain-util');
|
||||
const Block = require('../blockchain/block');
|
||||
const Blockchain = require('../blockchain/blockchain');
|
||||
|
||||
const STATE_INIT = 0;
|
||||
const STATE_CONNECTING = 1;
|
||||
const STATE_RUNNING = 2;
|
||||
|
||||
const PEER_OK = 0;
|
||||
const PEER_DEAD = 1;
|
||||
|
||||
const chainValidation = {
|
||||
start: ChainUtil.createValidateIsIntegerWithMin(0),
|
||||
blocks: ChainUtil.createValidateArray(Block.validateIsBlock)
|
||||
};
|
||||
|
||||
class Connection {
|
||||
constructor(parent) {
|
||||
this.parent = parent;
|
||||
this.address = null;
|
||||
this.socket = null;
|
||||
this.state = STATE_INIT;
|
||||
|
||||
this.prev = null;
|
||||
this.next = null;
|
||||
|
||||
this.blockIndex = null;
|
||||
|
||||
this.queue = null;
|
||||
this.queueTimer = null;
|
||||
|
||||
this.sub = {
|
||||
txs: false
|
||||
};
|
||||
|
||||
this.logName = `${parent.logName}:${parent.connectionCounter}`;
|
||||
parent.connectionCounter++;
|
||||
}
|
||||
|
||||
accepted(socket) {
|
||||
console.log(`${this.logName} accepted`);
|
||||
this.socket = socket;
|
||||
this.state = STATE_RUNNING;
|
||||
|
||||
this.socket.on("error", () => {
|
||||
this.onError();
|
||||
});
|
||||
|
||||
this.socket.on("open", () => {
|
||||
this.onConnection();
|
||||
});
|
||||
|
||||
this.socket.on("message", (data) => {
|
||||
this.onMessage(data);
|
||||
});
|
||||
|
||||
this.onConnection();
|
||||
}
|
||||
|
||||
connect(address) {
|
||||
console.log(`${this.logName} connecting`);
|
||||
this.address = address;
|
||||
this.state = STATE_CONNECTING;
|
||||
|
||||
this.reconnectWait = 1;
|
||||
this.socket = new Websocket(this.address);
|
||||
|
||||
this.socket.on("error", () => {
|
||||
this.onError();
|
||||
});
|
||||
|
||||
this.socket.on("open", () => {
|
||||
this.onConnection();
|
||||
});
|
||||
|
||||
this.socket.on("message", (data) => {
|
||||
this.onMessage(data);
|
||||
});
|
||||
}
|
||||
|
||||
retryDead(address) {
|
||||
|
||||
}
|
||||
|
||||
onError() {
|
||||
switch (this.state) {
|
||||
case STATE_CONNECTING:
|
||||
//this.reconnectWait seconds + random [0,1000] ms
|
||||
setTimeout(() => this.socket = new Websocket(this.address),
|
||||
1000 * this.reconnectWait + Math.floor(Math.random() * 1000));
|
||||
this.reconnectWait *= 2;
|
||||
if (this.reconnectWait > 64) {
|
||||
this.reconnectWait = 64;
|
||||
}
|
||||
break;
|
||||
case STATE_RUNNING:
|
||||
this.socket.close();
|
||||
this.next.prev = this.prev;
|
||||
this.prev.next = this.next;
|
||||
this.next = null;
|
||||
this.prev = null;
|
||||
if (this.address !== null) {
|
||||
this.state = STATE_CONNECTING;
|
||||
this.reconnectWait = 1;
|
||||
this.socket = new Websocket(this.address);
|
||||
} else {
|
||||
//do nothing?
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onConnection() {
|
||||
this.state = STATE_RUNNING;
|
||||
|
||||
this.prev = this.parent.connected;
|
||||
this.next = this.parent.connected.next;
|
||||
this.next.prev = this;
|
||||
this.prev.next = this;
|
||||
|
||||
const sending = {
|
||||
sub: {
|
||||
txs: this.parent.subTxs
|
||||
},
|
||||
address: this.parent.myAddress
|
||||
};
|
||||
|
||||
const blocks = this.parent.blockchain.blocks();
|
||||
|
||||
if (blocks.length > 1) {
|
||||
sending.chain = {
|
||||
blocks: blocks.slice(1),
|
||||
start: 1
|
||||
}
|
||||
}
|
||||
|
||||
this.socket.send(JSON.stringify(sending));
|
||||
|
||||
this.blockIndex = blocks.length;
|
||||
}
|
||||
|
||||
onQueueTimer() {
|
||||
this.queueTimer = null;
|
||||
if (this.state !== STATE_RUNNING) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.checkSend();
|
||||
|
||||
// we don't retimer as we wait for external to send
|
||||
}
|
||||
|
||||
onMessage(event) {
|
||||
var recved = null;
|
||||
try {
|
||||
recved = JSON.parse(event);
|
||||
} catch (ex) {
|
||||
console.log(`Bad message on ${this.logName}, not a json object`);
|
||||
this.onError();
|
||||
return;
|
||||
}
|
||||
|
||||
if ("chain" in recved) {
|
||||
const validationRes = ChainUtil.validateObject(recved.chain, chainValidation);
|
||||
if (!validationRes.result) {
|
||||
console.log(`${this.logName} couldn't validate chain message: ${validationRes.reason}`);
|
||||
this.onError();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`${this.logName} recved chain with start: ${recved.chain.start}`);
|
||||
|
||||
var newBlocks = this.parent.blockchain.blocks().slice(0, recved.chain.start + 1);
|
||||
newBlocks = newBlocks.concat(recved.chain.blocks);
|
||||
|
||||
this.parent.updatingConnection = this;
|
||||
this.parent.blockchain.replaceChain(newBlocks);
|
||||
this.parent.updatingConnection = null;
|
||||
}
|
||||
}
|
||||
|
||||
sendChain(oldBlocks, blocks) {
|
||||
if (this.queue === null) {
|
||||
this.queue = {};
|
||||
}
|
||||
|
||||
var startIndex = this.blockIndex - 1;
|
||||
|
||||
while (oldBlocks[startIndex].hash !== blocks[startIndex].hash) {
|
||||
startIndex--;
|
||||
}
|
||||
|
||||
this.queue.chain = {
|
||||
blocks: blocks.slice(startIndex + 1),
|
||||
start: startIndex + 1
|
||||
};
|
||||
|
||||
this.checkSend();
|
||||
}
|
||||
|
||||
checkSend() {
|
||||
if (this.queue === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.socket.bufferedAmount === 0) {
|
||||
this.socket.send(JSON.stringify(this.queue));
|
||||
|
||||
if ("chain" in this.queue) {
|
||||
this.blockIndex = this.queue.chain.start + this.queue.chain.blocks.length;
|
||||
}
|
||||
|
||||
this.queue = null;
|
||||
} else if (this.queueTimer === null) {
|
||||
this.queueTimer = setTimeout(this.onQueueTimer, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateBlocksImpl(server, newBlocks, oldBlocks) {
|
||||
if (server.updatingConnection !== null) {
|
||||
server.updatingConnection.blockIndex = blocks.length;
|
||||
}
|
||||
|
||||
for (var connection = server.connected.next; connection !== server.connected; connection = connection.next) {
|
||||
if (connection === server.updatingConnection) {
|
||||
continue;
|
||||
}
|
||||
connection.sendChain(oldBlocks, newBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
//this acts as a publisher, and subscriber
|
||||
class PropServer {
|
||||
constructor(logName, subTxs, blockchain) {
|
||||
this.logName = logName;
|
||||
this.peerState = new Map();
|
||||
this.connected = {
|
||||
next: null,
|
||||
prev: null
|
||||
};
|
||||
this.connected.next = this.connected;
|
||||
this.connected.prev = this.connected;
|
||||
this.blockchain = blockchain;
|
||||
this.blockchain.addListener((newBlocks, oldBlocks, difference) => {
|
||||
updateBlocksImpl(this, newBlocks, oldBlocks);
|
||||
});
|
||||
this.port = null;
|
||||
this.myAddress = null;
|
||||
this.server = null;
|
||||
this.connectionCounter = 0;
|
||||
this.subTxs = subTxs;
|
||||
this.updatingConnection = null;
|
||||
}
|
||||
|
||||
start(port, myAddress, peers) {
|
||||
if (this.port !== null) {
|
||||
console.log(`Couldn't start BlockchainPub '${this.logName}', already started`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.port = port;
|
||||
this.myAddress = myAddress;
|
||||
for (const peer of peers) {
|
||||
if (!this.peerState.has(peer)) {
|
||||
this.peerState.set(peer, PEER_OK);
|
||||
|
||||
const connection = new Connection(this);
|
||||
connection.connect(peer);
|
||||
}
|
||||
}
|
||||
|
||||
this.server = new Websocket.Server({ port: port });
|
||||
this.server.on('connection', socket => {
|
||||
const connection = new Connection(this);
|
||||
connection.accepted(socket);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PropServer;
|
15
network/test.js
Normal file
15
network/test.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
const PropServer = require('./blockchain-prop');
|
||||
const Block = require('../blockchain/block');
|
||||
|
||||
const s1 = new PropServer('s1', false);
|
||||
const s2 = new PropServer('s2', false);
|
||||
const s3 = new PropServer('s3', false);
|
||||
|
||||
s1.start(9100, 'ws://127.0.0.1:9100', []);
|
||||
s2.start(9101, 'ws://127.0.0.1:9101', ['ws://127.0.0.1:9100']);
|
||||
s3.start(9102, 'ws://127.0.0.1:9102', ['ws://127.0.0.1:9101']);
|
||||
|
||||
const blocks = [Block.genesis()];
|
||||
blocks.push(Block.debugMine(blocks[blocks.length - 1], 'eh', [], [], [], [], []));
|
||||
|
||||
s3.updateBlocks(blocks);
|
Loading…
Add table
Add a link
Reference in a new issue