add block syncing and fix the network bug

This commit is contained in:
yjjnls 2019-03-14 11:00:19 +08:00
parent 805e6728ad
commit 4d964cba8f
6 changed files with 236 additions and 99 deletions

View File

@ -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.

View File

@ -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));
}

View File

@ -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());

View File

@ -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 }; }
};

View File

@ -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);
}

41
src/js/test/sync.js Normal file
View 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);