mirror of
https://github.com/yjjnls/awesome-blockchain.git
synced 2024-10-01 00:45:35 -04:00
add block syncing and fix the network bug
This commit is contained in:
parent
805e6728ad
commit
4d964cba8f
@ -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.
|
||||
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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 }; }
|
||||
};
|
@ -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
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);
|
Loading…
Reference in New Issue
Block a user