add block syncing and fix the network bug
This commit is contained in:
@ -4,7 +4,7 @@ Basic implementation for blockchain in node.js, supporting pow, pos, pbft, dpos
|
|||||||
|
|
||||||
Persistence is on top of leveldb.
|
Persistence is on top of leveldb.
|
||||||
|
|
||||||
Support UTXO transactions.
|
Support UTXO transactions and block syncing.
|
||||||
|
|
||||||
Tests passed on Ubuntu and MAC.
|
Tests passed on Ubuntu and MAC.
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ class Block extends EventEmitter {
|
|||||||
}
|
}
|
||||||
while (hashes.length > 1) {
|
while (hashes.length > 1) {
|
||||||
var tmp = [];
|
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];
|
let data = hashes[i * 2] + hashes[i * 2 + 1];
|
||||||
tmp.push(Crypto.calc_hash(data));
|
tmp.push(Crypto.calc_hash(data));
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ class BlockChain {
|
|||||||
// todo
|
// todo
|
||||||
this.pending_block_ = {};
|
this.pending_block_ = {};
|
||||||
this.tx_pool = {};
|
this.tx_pool = {};
|
||||||
this.chain_ = [];
|
// this.chain_ = [];
|
||||||
|
|
||||||
this.is_bad_ = is_bad;
|
this.is_bad_ = is_bad;
|
||||||
this.pbft_ = new Pbft(this);
|
this.pbft_ = new Pbft(this);
|
||||||
@ -74,22 +74,24 @@ class BlockChain {
|
|||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
async save_last_block() {
|
async save_block(block) {
|
||||||
|
if (!block)
|
||||||
|
block = this.last_block_;
|
||||||
// query from db via hash
|
// query from db via hash
|
||||||
// if not exist, write into db, else do nothing
|
// if not exist, write into db, else do nothing
|
||||||
if (this.pending_block_[this.last_block_.hash]) {
|
if (this.pending_block_[block.hash]) {
|
||||||
delete this.pending_block_[this.last_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(block.hash, JSON.stringify(block));
|
||||||
await this.db_.put("last_block", JSON.stringify(this.last_block_));
|
await this.db_.put("last_block", JSON.stringify(block));
|
||||||
// console.log(`save block: ${this.last_block_.hash} to db`);
|
// console.log(`save block: ${block.hash} to db`);
|
||||||
|
|
||||||
// tx
|
// tx
|
||||||
if (!this.last_block_.transactions) {
|
if (!block.transactions) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (var i = 0; i < this.last_block_.transactions.length; ++i) {
|
for (var i = 0; i < block.transactions.length; ++i) {
|
||||||
let tx = this.last_block_.transactions[i];
|
let tx = block.transactions[i];
|
||||||
if (this.tx_pool[tx.id]) {
|
if (this.tx_pool[tx.id]) {
|
||||||
delete this.tx_pool[tx.id];
|
delete this.tx_pool[tx.id];
|
||||||
// console.log(`node ${this.get_account_id()} delete tx ${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));
|
await this.db_.put(tx.id, JSON.stringify(tx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async save_last_block() {
|
||||||
|
await this.save_block();
|
||||||
|
}
|
||||||
|
|
||||||
generate_block(keypair, cb) {
|
generate_block(keypair, cb) {
|
||||||
// load transactions
|
// load transactions
|
||||||
var tx = [this.create_coinbase()];
|
var tx = [this.create_coinbase()];
|
||||||
@ -117,7 +123,8 @@ class BlockChain {
|
|||||||
// make proof of the block/mine
|
// make proof of the block/mine
|
||||||
let self = this;
|
let self = this;
|
||||||
block.on('block completed', (data) => {
|
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");
|
// console.log("block completed");
|
||||||
self.commit_block(data);
|
self.commit_block(data);
|
||||||
|
|
||||||
@ -125,11 +132,8 @@ class BlockChain {
|
|||||||
|
|
||||||
if (cb) cb();
|
if (cb) cb();
|
||||||
} else {
|
} else {
|
||||||
// fork or store into tmp
|
// [fork]
|
||||||
console.log('fork');
|
self.process_fork(data);
|
||||||
// todo
|
|
||||||
self.pending_block_[data.hash] = data;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -199,19 +203,28 @@ class BlockChain {
|
|||||||
get_public_key() {
|
get_public_key() {
|
||||||
return this.get_account_keypair().publicKey.toString('hex');
|
return this.get_account_keypair().publicKey.toString('hex');
|
||||||
}
|
}
|
||||||
|
send_msg(socket, data) {
|
||||||
|
this.node_.send(socket, data);
|
||||||
|
}
|
||||||
broadcast(data) {
|
broadcast(data) {
|
||||||
this.node_.broadcast(data);
|
this.node_.broadcast(data);
|
||||||
}
|
}
|
||||||
list_peers() {
|
list_peers() {
|
||||||
return this.node_.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) {
|
async verify_transaction(tx) {
|
||||||
let input_amount = 0;
|
let input_amount = 0;
|
||||||
for (var i = 0; i < tx.input.length; ++i) {
|
for (var i = 0; i < tx.input.length; ++i) {
|
||||||
let input = tx.input[i];
|
let input = tx.input[i];
|
||||||
// coinbase
|
// coinbase
|
||||||
if (input.id == null) {
|
if (input.id == null) {
|
||||||
// todo check milestone
|
// check milestone
|
||||||
if (tx.output[0].amount == 50) {
|
if (tx.output[0].amount == 50) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
@ -250,18 +263,42 @@ class BlockChain {
|
|||||||
if (!Block.verify_signature(block))
|
if (!Block.verify_signature(block))
|
||||||
return false;
|
return false;
|
||||||
// verify consensus
|
// verify consensus
|
||||||
if (!this.consensus_.verify(block))
|
if (!this.consensus_.verify(block)) {
|
||||||
|
// [fork] slot
|
||||||
|
this.save_block(block);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
// verify transactions
|
// verify transactions
|
||||||
let tx = block.transactions;
|
let tx = block.transactions;
|
||||||
if (tx) {
|
if (tx) {
|
||||||
for (var i = 0; i < tx.length; ++i) {
|
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]))
|
if (!await this.verify_transaction(tx[i]))
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
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) {
|
async on_data(msg) {
|
||||||
switch (msg.type) {
|
switch (msg.type) {
|
||||||
case MessageType.Block:
|
case MessageType.Block:
|
||||||
@ -283,14 +320,14 @@ class BlockChain {
|
|||||||
this.pending_block_[block.hash] = block;
|
this.pending_block_[block.hash] = block;
|
||||||
|
|
||||||
// add to chain
|
// 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");
|
// console.log("on block data");
|
||||||
this.commit_block(block);
|
this.commit_block(block);
|
||||||
// console.log("----------add block");
|
// console.log("----------add block");
|
||||||
} else {
|
} else {
|
||||||
// fork or store into tmp
|
// [fork]
|
||||||
// console.log('fork');
|
this.process_fork(block);
|
||||||
// todo
|
|
||||||
}
|
}
|
||||||
// broadcast
|
// broadcast
|
||||||
this.broadcast(msg);
|
this.broadcast(msg);
|
||||||
@ -317,88 +354,135 @@ class BlockChain {
|
|||||||
this.broadcast(msg);
|
this.broadcast(msg);
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
if (pbft && !this.is_bad_) {
|
if (pbft && !this.is_bad_) {
|
||||||
this.pbft_.processMessage(msg);
|
this.pbft_.processMessage(msg);
|
||||||
|
} else {
|
||||||
|
console.log("unkown msg");
|
||||||
|
console.log(msg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
print() {
|
// print() {
|
||||||
// todo chain_
|
// // todo chain_
|
||||||
let output = '';
|
// let output = '';
|
||||||
for (var i = 0; i < this.chain_.length; ++i) {
|
// for (var i = 0; i < this.chain_.length; ++i) {
|
||||||
let height = this.chain_[i].height;
|
// let height = this.chain_[i].height;
|
||||||
let hash = this.chain_[i].hash.substr(0, 6);
|
// let hash = this.chain_[i].hash.substr(0, 6);
|
||||||
let generator_id = this.chain_[i].consensus_data.generator_id;
|
// let generator_id = this.chain_[i].consensus_data.generator_id;
|
||||||
if (generator_id == undefined) generator_id = null;
|
// if (generator_id == undefined) generator_id = null;
|
||||||
output += `(${height}:${hash}:${generator_id}) -> `;
|
// output += `(${height}:${hash}:${generator_id}) -> `;
|
||||||
}
|
// }
|
||||||
console.log(`node: ${this.get_account_id()} ${output}`);
|
// console.log(`node: ${this.get_account_id()} ${output}`);
|
||||||
}
|
// }
|
||||||
async fork() {
|
// async fork() {
|
||||||
console.log('----------fork----------');
|
// console.log('----------fork----------');
|
||||||
// load transactions
|
// // load transactions
|
||||||
var tx1 = [{
|
// var tx1 = [{
|
||||||
amount: 1000,
|
// amount: 1000,
|
||||||
recipient: 'bob',
|
// recipient: 'bob',
|
||||||
sender: 'alice'
|
// sender: 'alice'
|
||||||
}];
|
// }];
|
||||||
// create block
|
// // create block
|
||||||
let block1 = new Block({
|
// let block1 = new Block({
|
||||||
"keypair": this.get_account_keypair(),
|
// "keypair": this.get_account_keypair(),
|
||||||
"previous_block": this.last_block_,
|
// "previous_block": this.last_block_,
|
||||||
"transactions": tx1
|
// "transactions": tx1
|
||||||
}, this.consensus_);
|
// }, this.consensus_);
|
||||||
// make proof of the block/mine
|
// // make proof of the block/mine
|
||||||
let self = this;
|
// let self = this;
|
||||||
let block_data1 = await new Promise((resolve, reject) => {
|
// let block_data1 = await new Promise((resolve, reject) => {
|
||||||
block1.on('block completed', (data) => {
|
// block1.on('block completed', (data) => {
|
||||||
if (data.height == self.last_block_.height + 1) {
|
// if (data.height == self.last_block_.height + 1) {
|
||||||
resolve(data);
|
// resolve(data);
|
||||||
} else {
|
// } else {
|
||||||
reject('block1 failed');
|
// reject('block1 failed');
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
// load transactions
|
// // load transactions
|
||||||
var tx2 = [{
|
// var tx2 = [{
|
||||||
amount: 1000,
|
// amount: 1000,
|
||||||
recipient: 'cracker',
|
// recipient: 'cracker',
|
||||||
sender: 'alice'
|
// sender: 'alice'
|
||||||
}];
|
// }];
|
||||||
// create block
|
// // create block
|
||||||
let block2 = new Block({
|
// let block2 = new Block({
|
||||||
"keypair": this.get_account_keypair(),
|
// "keypair": this.get_account_keypair(),
|
||||||
"previous_block": this.last_block_,
|
// "previous_block": this.last_block_,
|
||||||
"transactions": tx2
|
// "transactions": tx2
|
||||||
}, this.consensus_);
|
// }, this.consensus_);
|
||||||
let block_data2 = await new Promise((resolve, reject) => {
|
// let block_data2 = await new Promise((resolve, reject) => {
|
||||||
block2.on('block completed', (data) => {
|
// block2.on('block completed', (data) => {
|
||||||
if (data.height == self.last_block_.height + 1) {
|
// if (data.height == self.last_block_.height + 1) {
|
||||||
resolve(data);
|
// resolve(data);
|
||||||
} else {
|
// } else {
|
||||||
reject('block2 failed');
|
// reject('block2 failed');
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
var i = 0;
|
// var i = 0;
|
||||||
for (var id in this.node_.peers_) {
|
// for (var id in this.node_.peers_) {
|
||||||
let socket = this.node_.peers_[id];
|
// let socket = this.node_.peers_[id];
|
||||||
if (i % 2 == 0) {
|
// if (i % 2 == 0) {
|
||||||
var msg1 = Msg.block(block_data1);
|
// var msg1 = Msg.block(block_data1);
|
||||||
this.node_.send(socket, msg1);
|
// this.node_.send(socket, msg1);
|
||||||
} else {
|
// } else {
|
||||||
var msg2 = Msg.block(block_data2);
|
// var msg2 = Msg.block(block_data2);
|
||||||
this.node_.send(socket, msg2);
|
// this.node_.send(socket, msg2);
|
||||||
}
|
// }
|
||||||
i++;
|
// i++;
|
||||||
}
|
// }
|
||||||
console.log("fork");
|
// console.log("fork");
|
||||||
this.commit_block(block_data1);
|
// this.commit_block(block_data1);
|
||||||
}
|
// }
|
||||||
create_coinbase() {
|
create_coinbase() {
|
||||||
let input = new TxInput(null, -1, `${new Date()} node: ${this.get_account_id()} coinbase tx`);
|
let input = new TxInput(null, -1, `${new Date()} node: ${this.get_account_id()} coinbase tx`);
|
||||||
let output = new TxOutput(50, this.get_public_key());
|
let output = new TxOutput(50, this.get_public_key());
|
||||||
|
@ -6,7 +6,9 @@ var MessageType = {
|
|||||||
Transaction: 2,
|
Transaction: 2,
|
||||||
PrePrepare: 3,
|
PrePrepare: 3,
|
||||||
Prepare: 4,
|
Prepare: 4,
|
||||||
Commit: 5
|
Commit: 5,
|
||||||
|
Sync: 6,
|
||||||
|
SyncBlock: 7
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -16,5 +18,7 @@ module.exports = {
|
|||||||
preprepare: (data) => { return { type: MessageType.PrePrepare, data: data }; },
|
preprepare: (data) => { return { type: MessageType.PrePrepare, data: data }; },
|
||||||
prepare: (data) => { return { type: MessageType.Prepare, data: data }; },
|
prepare: (data) => { return { type: MessageType.Prepare, data: data }; },
|
||||||
commit: (data) => { return { type: MessageType.Commit, 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 }; }
|
||||||
};
|
};
|
@ -33,6 +33,11 @@ class Node extends EventEmitter {
|
|||||||
socket.on('connect', () => {
|
socket.on('connect', () => {
|
||||||
resolve();
|
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}`);
|
// console.log(`id: ${self.id_} connected to remote_id: ${remote_id}`);
|
||||||
let data = Msg.connection(self.id_);
|
let data = Msg.connection(self.id_);
|
||||||
@ -72,6 +77,9 @@ class Node extends EventEmitter {
|
|||||||
|
|
||||||
}
|
}
|
||||||
send(socket, data) {
|
send(socket, data) {
|
||||||
|
if (typeof socket === 'number') {
|
||||||
|
socket = this.peers_[socket];
|
||||||
|
}
|
||||||
if (typeof data === 'object') {
|
if (typeof data === 'object') {
|
||||||
data = JSON.stringify(data);
|
data = JSON.stringify(data);
|
||||||
}
|
}
|
||||||
|
41
src/js/test/sync.js
Normal file
41
src/js/test/sync.js
Normal file
@ -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);
|
Reference in New Issue
Block a user