From 4d964cba8fe507682a1d383d68f10e7295eeacf4 Mon Sep 17 00:00:00 2001 From: yjjnls Date: Thu, 14 Mar 2019 11:00:19 +0800 Subject: [PATCH] add block syncing and fix the network bug --- src/js/Readme.md | 2 +- src/js/block.js | 2 +- src/js/blockchain.js | 274 ++++++++++++++++++++++++++++--------------- src/js/message.js | 8 +- src/js/network.js | 8 ++ src/js/test/sync.js | 41 +++++++ 6 files changed, 236 insertions(+), 99 deletions(-) create mode 100644 src/js/test/sync.js diff --git a/src/js/Readme.md b/src/js/Readme.md index 642e3c0..dd119a3 100644 --- a/src/js/Readme.md +++ b/src/js/Readme.md @@ -4,7 +4,7 @@ Basic implementation for blockchain in node.js, supporting pow, pos, pbft, dpos Persistence is on top of leveldb. -Support UTXO transactions. +Support UTXO transactions and block syncing. Tests passed on Ubuntu and MAC. diff --git a/src/js/block.js b/src/js/block.js index 593c470..49b3c7d 100644 --- a/src/js/block.js +++ b/src/js/block.js @@ -76,7 +76,7 @@ class Block extends EventEmitter { } while (hashes.length > 1) { var tmp = []; - for (var i = 0; i < hashes.length / 2; ++i) { + for (i = 0; i < hashes.length / 2; ++i) { let data = hashes[i * 2] + hashes[i * 2 + 1]; tmp.push(Crypto.calc_hash(data)); } diff --git a/src/js/blockchain.js b/src/js/blockchain.js index 6886ce2..af42552 100644 --- a/src/js/blockchain.js +++ b/src/js/blockchain.js @@ -20,7 +20,7 @@ class BlockChain { // todo this.pending_block_ = {}; this.tx_pool = {}; - this.chain_ = []; + // this.chain_ = []; this.is_bad_ = is_bad; this.pbft_ = new Pbft(this); @@ -74,22 +74,24 @@ class BlockChain { cb(); } - async save_last_block() { + async save_block(block) { + if (!block) + block = this.last_block_; // query from db via hash // if not exist, write into db, else do nothing - if (this.pending_block_[this.last_block_.hash]) { - delete this.pending_block_[this.last_block_.hash]; + if (this.pending_block_[block.hash]) { + delete this.pending_block_[block.hash]; } - await this.db_.put(this.last_block_.hash, JSON.stringify(this.last_block_)); - await this.db_.put("last_block", JSON.stringify(this.last_block_)); - // console.log(`save block: ${this.last_block_.hash} to db`); + await this.db_.put(block.hash, JSON.stringify(block)); + await this.db_.put("last_block", JSON.stringify(block)); + // console.log(`save block: ${block.hash} to db`); // tx - if (!this.last_block_.transactions) { + if (!block.transactions) { return; } - for (var i = 0; i < this.last_block_.transactions.length; ++i) { - let tx = this.last_block_.transactions[i]; + for (var i = 0; i < block.transactions.length; ++i) { + let tx = block.transactions[i]; if (this.tx_pool[tx.id]) { delete this.tx_pool[tx.id]; // console.log(`node ${this.get_account_id()} delete tx ${tx.id}`); @@ -97,6 +99,10 @@ class BlockChain { await this.db_.put(tx.id, JSON.stringify(tx)); } } + async save_last_block() { + await this.save_block(); + } + generate_block(keypair, cb) { // load transactions var tx = [this.create_coinbase()]; @@ -117,7 +123,8 @@ class BlockChain { // make proof of the block/mine let self = this; block.on('block completed', (data) => { - if (data.height == self.last_block_.height + 1) { + if (data.previous_hash == self.last_block_.hash && + data.height == self.last_block_.height + 1) { // console.log("block completed"); self.commit_block(data); @@ -125,11 +132,8 @@ class BlockChain { if (cb) cb(); } else { - // fork or store into tmp - console.log('fork'); - // todo - self.pending_block_[data.hash] = data; - + // [fork] + self.process_fork(data); } }); } @@ -199,19 +203,28 @@ class BlockChain { get_public_key() { return this.get_account_keypair().publicKey.toString('hex'); } + send_msg(socket, data) { + this.node_.send(socket, data); + } broadcast(data) { this.node_.broadcast(data); } list_peers() { return this.node_.list_peers(); } + sync() { + let peers = this.list_peers(); + let index = Math.floor(Math.random() * peers.length); + let id = peers[index]; + this.send_msg(parseInt(id), Msg.sync({ "id": this.get_account_id() })); + } async verify_transaction(tx) { let input_amount = 0; for (var i = 0; i < tx.input.length; ++i) { let input = tx.input[i]; // coinbase if (input.id == null) { - // todo check milestone + // check milestone if (tx.output[0].amount == 50) { return true; } else { @@ -250,18 +263,42 @@ class BlockChain { if (!Block.verify_signature(block)) return false; // verify consensus - if (!this.consensus_.verify(block)) + if (!this.consensus_.verify(block)) { + // [fork] slot + this.save_block(block); return false; + } // verify transactions let tx = block.transactions; if (tx) { for (var i = 0; i < tx.length; ++i) { + try { + if (await this.db_.get(tx[i].id)) { + // [fork] transaction exists + return false; + } + } catch (err) { + // nothing + } if (!await this.verify_transaction(tx[i])) return false; } } return true; } + process_fork(block) { + if (block.previous_hash != this.last_block_.hash && + block.height == this.last_block_.height + 1) { + // [fork] right height and different previous block + this.save_block(block); + + } else if (block.previous_hash == this.last_block_.hash && + block.height == this.last_block_.height && + block.hash != this.last_block_.hash) { + // [fork] same height and same previous block, but different block id + this.save_block(block); + } + } async on_data(msg) { switch (msg.type) { case MessageType.Block: @@ -283,14 +320,14 @@ class BlockChain { this.pending_block_[block.hash] = block; // add to chain - if (block.height == this.last_block_.height + 1) { + if (block.previous_hash == this.last_block_.hash && + block.height == this.last_block_.height + 1) { // console.log("on block data"); this.commit_block(block); // console.log("----------add block"); } else { - // fork or store into tmp - // console.log('fork'); - // todo + // [fork] + this.process_fork(block); } // broadcast this.broadcast(msg); @@ -317,88 +354,135 @@ class BlockChain { this.broadcast(msg); } break; + case MessageType.Sync: + { + console.log(`${this.get_account_id()} receive sync info`); + let data = msg.data; + let id = data.id; + if (data.hash) { + let block = await this.get_from_db(data.hash); + this.send_msg(id, Msg.sync_block({ "id": this.get_account_id(), "block": block })); + console.log(`---> ${this.get_account_id()} send sync block: ${block.height}`); + + } else { + this.send_msg(id, Msg.sync_block({ "id": this.get_account_id(), "last_block": this.last_block_ })); + console.log(`---> ${this.get_account_id()} send sync last block: ${this.last_block_.height}`); + } + } + break; + case MessageType.SyncBlock: + { + let data = msg.data; + let id = data.id; + let block = null; + if (data.hasOwnProperty("last_block")) { + block = data.last_block; + this.last_block_ = block; + console.log(`++++ ${this.get_account_id()} change last block: ${block.height}`); + } else { + block = data.block; + } + console.log(`<--- ${this.get_account_id()} receive sync block: ${block.height}\n`); + + this.save_block(block); + let hash = block.previous_hash; + let res = null; + if (hash) { + res = await this.get_from_db(hash); + } + if (!res) { + console.log(`---> ${this.get_account_id()} continue sync hash: ${hash}`); + this.send_msg(id, Msg.sync({ "id": this.get_account_id(), "hash": hash })); + } else { + console.log(`==== ${this.get_account_id()} complete syning!`); + } + } + break; default: if (pbft && !this.is_bad_) { this.pbft_.processMessage(msg); + } else { + console.log("unkown msg"); + console.log(msg); } break; } } - print() { - // todo chain_ - let output = ''; - for (var i = 0; i < this.chain_.length; ++i) { - let height = this.chain_[i].height; - let hash = this.chain_[i].hash.substr(0, 6); - let generator_id = this.chain_[i].consensus_data.generator_id; - if (generator_id == undefined) generator_id = null; - output += `(${height}:${hash}:${generator_id}) -> `; - } - console.log(`node: ${this.get_account_id()} ${output}`); - } - async fork() { - console.log('----------fork----------'); - // load transactions - var tx1 = [{ - amount: 1000, - recipient: 'bob', - sender: 'alice' - }]; - // create block - let block1 = new Block({ - "keypair": this.get_account_keypair(), - "previous_block": this.last_block_, - "transactions": tx1 - }, this.consensus_); - // make proof of the block/mine - let self = this; - let block_data1 = await new Promise((resolve, reject) => { - block1.on('block completed', (data) => { - if (data.height == self.last_block_.height + 1) { - resolve(data); - } else { - reject('block1 failed'); - } - }); - }); + // print() { + // // todo chain_ + // let output = ''; + // for (var i = 0; i < this.chain_.length; ++i) { + // let height = this.chain_[i].height; + // let hash = this.chain_[i].hash.substr(0, 6); + // let generator_id = this.chain_[i].consensus_data.generator_id; + // if (generator_id == undefined) generator_id = null; + // output += `(${height}:${hash}:${generator_id}) -> `; + // } + // console.log(`node: ${this.get_account_id()} ${output}`); + // } + // async fork() { + // console.log('----------fork----------'); + // // load transactions + // var tx1 = [{ + // amount: 1000, + // recipient: 'bob', + // sender: 'alice' + // }]; + // // create block + // let block1 = new Block({ + // "keypair": this.get_account_keypair(), + // "previous_block": this.last_block_, + // "transactions": tx1 + // }, this.consensus_); + // // make proof of the block/mine + // let self = this; + // let block_data1 = await new Promise((resolve, reject) => { + // block1.on('block completed', (data) => { + // if (data.height == self.last_block_.height + 1) { + // resolve(data); + // } else { + // reject('block1 failed'); + // } + // }); + // }); - // load transactions - var tx2 = [{ - amount: 1000, - recipient: 'cracker', - sender: 'alice' - }]; - // create block - let block2 = new Block({ - "keypair": this.get_account_keypair(), - "previous_block": this.last_block_, - "transactions": tx2 - }, this.consensus_); - let block_data2 = await new Promise((resolve, reject) => { - block2.on('block completed', (data) => { - if (data.height == self.last_block_.height + 1) { - resolve(data); - } else { - reject('block2 failed'); - } - }); - }); + // // load transactions + // var tx2 = [{ + // amount: 1000, + // recipient: 'cracker', + // sender: 'alice' + // }]; + // // create block + // let block2 = new Block({ + // "keypair": this.get_account_keypair(), + // "previous_block": this.last_block_, + // "transactions": tx2 + // }, this.consensus_); + // let block_data2 = await new Promise((resolve, reject) => { + // block2.on('block completed', (data) => { + // if (data.height == self.last_block_.height + 1) { + // resolve(data); + // } else { + // reject('block2 failed'); + // } + // }); + // }); - var i = 0; - for (var id in this.node_.peers_) { - let socket = this.node_.peers_[id]; - if (i % 2 == 0) { - var msg1 = Msg.block(block_data1); - this.node_.send(socket, msg1); - } else { - var msg2 = Msg.block(block_data2); - this.node_.send(socket, msg2); - } - i++; - } - console.log("fork"); - this.commit_block(block_data1); - } + // var i = 0; + // for (var id in this.node_.peers_) { + // let socket = this.node_.peers_[id]; + // if (i % 2 == 0) { + // var msg1 = Msg.block(block_data1); + // this.node_.send(socket, msg1); + // } else { + // var msg2 = Msg.block(block_data2); + // this.node_.send(socket, msg2); + // } + // i++; + // } + // console.log("fork"); + // this.commit_block(block_data1); + // } create_coinbase() { let input = new TxInput(null, -1, `${new Date()} node: ${this.get_account_id()} coinbase tx`); let output = new TxOutput(50, this.get_public_key()); diff --git a/src/js/message.js b/src/js/message.js index 96ca6eb..a07e41c 100644 --- a/src/js/message.js +++ b/src/js/message.js @@ -6,7 +6,9 @@ var MessageType = { Transaction: 2, PrePrepare: 3, Prepare: 4, - Commit: 5 + Commit: 5, + Sync: 6, + SyncBlock: 7 }; module.exports = { @@ -16,5 +18,7 @@ module.exports = { preprepare: (data) => { return { type: MessageType.PrePrepare, data: data }; }, prepare: (data) => { return { type: MessageType.Prepare, data: data }; }, commit: (data) => { return { type: MessageType.Commit, data: data }; }, - transaction: (data)=>{return { type: MessageType.Transaction, data: data }; } + transaction: (data) => { return { type: MessageType.Transaction, data: data }; }, + sync: (data) => { return { type: MessageType.Sync, data: data }; }, + sync_block: (data) => { return { type: MessageType.SyncBlock, data: data }; } }; \ No newline at end of file diff --git a/src/js/network.js b/src/js/network.js index 128dcaf..e6a69e1 100644 --- a/src/js/network.js +++ b/src/js/network.js @@ -33,6 +33,11 @@ class Node extends EventEmitter { socket.on('connect', () => { resolve(); }); + socket.on('error', function (e) { + resolve(); + }); + socket.setEncoding('utf8'); + socket.on('data', (data) => { self.on_data(data, socket); }); }); // console.log(`id: ${self.id_} connected to remote_id: ${remote_id}`); let data = Msg.connection(self.id_); @@ -72,6 +77,9 @@ class Node extends EventEmitter { } send(socket, data) { + if (typeof socket === 'number') { + socket = this.peers_[socket]; + } if (typeof data === 'object') { data = JSON.stringify(data); } diff --git a/src/js/test/sync.js b/src/js/test/sync.js new file mode 100644 index 0000000..51f263a --- /dev/null +++ b/src/js/test/sync.js @@ -0,0 +1,41 @@ +'use strict'; + +var crypto = require('crypto'); +var ed = require('ed25519'); +var BlockChain = require("../blockchain"); +var Consensus = require("../consensus/dpos"); + + +let blockchains = []; +for (var i = 0; i < 20; ++i) { + + var password = `I am tester ${i}!`; + var hash = crypto.createHash('sha256').update(password).digest(); + var keypair = ed.MakeKeypair(hash); + console.log(`node ${i} address: ${keypair.publicKey.toString('hex')}`); + + let blockchain = new BlockChain(Consensus, keypair, i); + blockchain.start(); + blockchains.push(blockchain); +} + +setTimeout(() => { + for (var i = 0; i < blockchains.length; ++i) { + console.log(`${i} --> ${blockchains[i].list_peers()}`); + } +}, 3000); + + +setTimeout(() => { + blockchains[19].sync(); +}, 3000); + + +// async function get_balance() { +// let amount = await blockchains[0].get_balance(); +// console.log(`node 0 balance: ${amount}`); +// amount = await blockchains[6].get_balance(); +// console.log(`node 6 balance: ${amount}`); +// } + +// setInterval(get_balance, 10000);