mirror of
https://github.com/yjjnls/awesome-blockchain.git
synced 2024-10-01 00:45:35 -04:00
add basic blockchain implementaion in node.js
This commit is contained in:
parent
d2e94b75f6
commit
05a373e36b
2
src/.gitignore
vendored
Normal file
2
src/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
package-lock.json
|
||||||
|
node_modules
|
11
src/js/Readme.md
Normal file
11
src/js/Readme.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# BlockChain for Node.js
|
||||||
|
|
||||||
|
Basic implementation for blockchain in node.js, supporting pow, pos, pbft, dpos and pbft+dpos.
|
||||||
|
|
||||||
|
Tests passed on Ubuntu and MAC.
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
|
||||||
|
node test/xxx.js
|
||||||
|
```
|
17
src/js/account.js
Normal file
17
src/js/account.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
class Account {
|
||||||
|
constructor(keypair, id) {
|
||||||
|
this.keypair_ = keypair;
|
||||||
|
this.id_ = id;
|
||||||
|
this.amount_ = 0;
|
||||||
|
if (!this.keypair_) { }
|
||||||
|
if (!this.id_) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
get_id() { return this.id_; }
|
||||||
|
get_key() { return this.keypair_; }
|
||||||
|
get_amount() { return this.amount_; }
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Account;
|
142
src/js/block.js
Normal file
142
src/js/block.js
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
var crypto = require("crypto");
|
||||||
|
var ed = require("ed25519");
|
||||||
|
|
||||||
|
class Block extends EventEmitter {
|
||||||
|
constructor(data, consensus) {
|
||||||
|
super();
|
||||||
|
// body
|
||||||
|
this.transcations_ = data ? data.transactions : [];
|
||||||
|
// header
|
||||||
|
this.version_ = 0;
|
||||||
|
this.height_ = data ? data.previous_block.height + 1 : -1;
|
||||||
|
this.previous_hash_ = data ? data.previous_block.hash : null;
|
||||||
|
this.timestamp_ = (new Date()).getTime();
|
||||||
|
this.merkle_hash_ = data ? this.calc_merkle_hash(data.transactions) : null;
|
||||||
|
this.generator_publickey_ = data ? data.keypair.publicKey.toString('hex') : null;
|
||||||
|
this.hash_ = null;
|
||||||
|
this.block_signature_ = null;
|
||||||
|
// header extension
|
||||||
|
this.consensus_data_ = {};
|
||||||
|
|
||||||
|
if (consensus) {
|
||||||
|
let self = this;
|
||||||
|
setImmediate(() => {
|
||||||
|
self.make_proof(consensus, data.keypair);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
get_version() { return this.version_; }
|
||||||
|
get_height() { return this.height_; }
|
||||||
|
get_hash() { return this.hash_; }
|
||||||
|
get_previous_hash() { return this.previous_hash_; }
|
||||||
|
get_timestamp() { return this.timestamp_; }
|
||||||
|
get_signature() { return this.block_signature_; }
|
||||||
|
get_publickey() { return this.generator_publickey_; }
|
||||||
|
get_transcations() { return this.transcations_; }
|
||||||
|
get_consensus_data() { return this.consensus_data_; }
|
||||||
|
set_consensus_data(data) { this.consensus_data_ = data; }
|
||||||
|
toObject() {
|
||||||
|
let block = {
|
||||||
|
"version": this.version_,
|
||||||
|
"height": this.height_,
|
||||||
|
"previous_hash": this.previous_hash_,
|
||||||
|
"timestamp": this.timestamp_,
|
||||||
|
"merkle_hash": this.merkle_hash_,
|
||||||
|
"generator_publickey": this.generator_publickey_,
|
||||||
|
"hash": this.hash_,
|
||||||
|
"block_signature": this.block_signature_,
|
||||||
|
"consensus_data": this.consensus_data_,
|
||||||
|
"transcations": this.transcations_
|
||||||
|
};
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
set_data(data) {
|
||||||
|
this.version_ = data.version;
|
||||||
|
this.height_ = data.height;
|
||||||
|
this.previous_hash_ = data.previous_hash;
|
||||||
|
this.timestamp_ = data.timestamp;
|
||||||
|
this.merkle_hash_ = data.merkle_hash;
|
||||||
|
this.generator_publickey_ = data.generator_publickey;
|
||||||
|
this.hash_ = data.hash;
|
||||||
|
this.block_signature_ = data.block_signature;
|
||||||
|
this.consensus_data_ = data.consensus_data;
|
||||||
|
this.transcations_ = data.transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
calc_hash(data) {
|
||||||
|
return crypto.createHash('sha256').update(data).digest('hex');
|
||||||
|
}
|
||||||
|
calc_merkle_hash() {
|
||||||
|
// calc merkle root hash according to the transcations in the block
|
||||||
|
var hashes = [];
|
||||||
|
for (var i = 0; i < this.transcations_.length; ++i) {
|
||||||
|
hashes.push(this.calc_hash(this.transcations_.toString('utf-8')));
|
||||||
|
}
|
||||||
|
while (hashes.length > 1) {
|
||||||
|
var tmp = [];
|
||||||
|
for (var i = 0; i < hashes.length / 2; ++i) {
|
||||||
|
let data = hashes[i * 2] + hashes[i * 2 + 1];
|
||||||
|
tmp.push(this.calc_hash(data));
|
||||||
|
}
|
||||||
|
if (hashes.length % 2 === 1) {
|
||||||
|
tmp.push(hashes[hashes.length - 1]);
|
||||||
|
}
|
||||||
|
hashes = tmp;
|
||||||
|
}
|
||||||
|
return hashes[0] ? hashes[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare_data() {
|
||||||
|
let tx = "";
|
||||||
|
for (var i = 0; i < this.transcations_.length; ++i) {
|
||||||
|
tx += this.transcations_[i].toString('utf-8');
|
||||||
|
}
|
||||||
|
let data = this.version_.toString()
|
||||||
|
+ this.height_.toString()
|
||||||
|
+ this.previous_hash_
|
||||||
|
+ this.timestamp_.toString()
|
||||||
|
+ this.merkle_hash_
|
||||||
|
+ this.generator_publickey_
|
||||||
|
+ JSON.stringify(this.consensus_data_)
|
||||||
|
+ tx;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
// calc the hash of the block
|
||||||
|
calc_block_hash() {
|
||||||
|
return this.calc_hash(this.prepare_data());
|
||||||
|
}
|
||||||
|
sign(keypair) {
|
||||||
|
var hash = this.calc_block_hash();
|
||||||
|
return ed.Sign(Buffer.from(hash, 'utf-8'), keypair).toString('hex');
|
||||||
|
}
|
||||||
|
make_proof(consensus, keypair) {
|
||||||
|
let self = this;
|
||||||
|
this.on('consensus completed', () => {
|
||||||
|
self.hash_ = self.calc_block_hash();
|
||||||
|
self.block_signature_ = self.sign(keypair);
|
||||||
|
self.emit('block completed', self.toObject());
|
||||||
|
});
|
||||||
|
|
||||||
|
consensus.make_consensus(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
static verify_signature(block) {
|
||||||
|
var hash = block.hash;
|
||||||
|
var res = ed.Verify(Buffer.from(hash, 'utf8'), Buffer.from(block.block_signature, 'hex'), Buffer.from(block.generator_publickey, 'hex'));
|
||||||
|
return res;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static get_address_by_publickey(publicKey) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Block;
|
284
src/js/blockchain.js
Normal file
284
src/js/blockchain.js
Normal file
@ -0,0 +1,284 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var Block = require("./block");
|
||||||
|
const genesis_block = require("./genesis_block.json");
|
||||||
|
var Node = require("./network");
|
||||||
|
var Account = require("./account");
|
||||||
|
var Transcation = require("./transcation");
|
||||||
|
var Msg = require("./message");
|
||||||
|
var MessageType = require("./message").type;
|
||||||
|
var Promise = require("bluebird");
|
||||||
|
|
||||||
|
var Pbft = require("./consensus/pbft");
|
||||||
|
let pbft = true;
|
||||||
|
class BlockChain {
|
||||||
|
constructor(Consensus, keypair, id, is_bad = false) {
|
||||||
|
// todo
|
||||||
|
this.pending_block_ = {};
|
||||||
|
this.chain_ = [];
|
||||||
|
|
||||||
|
this.is_bad_ = is_bad;
|
||||||
|
this.pbft_ = new Pbft(this);
|
||||||
|
|
||||||
|
// ///////////////////////////////////////
|
||||||
|
this.genesis_block_ = genesis_block;
|
||||||
|
this.last_block_ = genesis_block;
|
||||||
|
this.save_last_block();
|
||||||
|
|
||||||
|
this.account_ = new Account(keypair, id);
|
||||||
|
this.consensus_ = new Consensus(this);
|
||||||
|
this.node_ = null;
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
this.node_ = new Node(this.get_account_id());
|
||||||
|
this.node_.on("message", this.on_data.bind(this));
|
||||||
|
this.node_.start();
|
||||||
|
// start loop
|
||||||
|
var self = this;
|
||||||
|
setTimeout(function next_loop() {
|
||||||
|
self.loop(function () {
|
||||||
|
setTimeout(next_loop, 1000);
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
loop(cb) {
|
||||||
|
let self = this;
|
||||||
|
if (this.consensus_.prepared()) {
|
||||||
|
if (!self.is_bad_) {
|
||||||
|
this.generate_block(this.get_account_keypair(), () => {
|
||||||
|
// broadcast block
|
||||||
|
let block = self.get_last_block();
|
||||||
|
console.log(`node: ${self.get_account_id()} generate block! block height: ${block.height} hash: ${block.hash}`);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
self.fork();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
|
||||||
|
save_last_block() {
|
||||||
|
// query from db via hash
|
||||||
|
// if not exist, write into db, else do nothing
|
||||||
|
// todo(tx is also need to store?)
|
||||||
|
if (this.pending_block_[this.last_block_.hash]) {
|
||||||
|
delete this.pending_block_[this.last_block_.hash];
|
||||||
|
}
|
||||||
|
this.chain_.push(this.last_block_);
|
||||||
|
}
|
||||||
|
generate_block(keypair, cb) {
|
||||||
|
// load transcations
|
||||||
|
var tx = [];
|
||||||
|
// create block
|
||||||
|
let block = new Block({
|
||||||
|
"keypair": keypair,
|
||||||
|
"previous_block": this.last_block_,
|
||||||
|
"transactions": tx
|
||||||
|
}, this.consensus_);
|
||||||
|
// make proof of the block/mine
|
||||||
|
let self = this;
|
||||||
|
block.on('block completed', (data) => {
|
||||||
|
if (data.height == self.last_block_.height + 1) {
|
||||||
|
console.log("block completed");
|
||||||
|
self.commit_block(data);
|
||||||
|
|
||||||
|
self.broadcast(Msg.block(data));
|
||||||
|
|
||||||
|
if (cb) cb();
|
||||||
|
} else {
|
||||||
|
// fork or store into tmp
|
||||||
|
console.log('fork');
|
||||||
|
// todo
|
||||||
|
self.pending_block_[data.hash] = data;
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
commit_block(block_data) {
|
||||||
|
if (pbft && !this.is_bad_) {
|
||||||
|
var block = new Block();
|
||||||
|
block.set_data(block_data);
|
||||||
|
let self = this;
|
||||||
|
block.on('consensus completed', (data) => {
|
||||||
|
self.last_block_ = data;
|
||||||
|
self.save_last_block();
|
||||||
|
});
|
||||||
|
this.pbft_.make_consensus(block);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.last_block_ = block_data;
|
||||||
|
this.save_last_block();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get_height() {
|
||||||
|
return this.last_block_.height;
|
||||||
|
}
|
||||||
|
get_block(hash) {
|
||||||
|
// query block with hash value
|
||||||
|
// todo
|
||||||
|
for (var i = 0; i < this.chain_.length; ++i) {
|
||||||
|
if (this.chain_[i] == hash) {
|
||||||
|
return this.chain_[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
get_last_block() {
|
||||||
|
return this.last_block_;
|
||||||
|
}
|
||||||
|
get_genesis_block() {
|
||||||
|
return this.generate_block_;
|
||||||
|
}
|
||||||
|
get_amount() {
|
||||||
|
// get the amount of the account
|
||||||
|
return this.account_.get_amount();
|
||||||
|
}
|
||||||
|
get_account_id() {
|
||||||
|
// get the node id
|
||||||
|
return this.account_.get_id();
|
||||||
|
}
|
||||||
|
get_account_keypair() {
|
||||||
|
return this.account_.get_key();
|
||||||
|
}
|
||||||
|
broadcast(data) {
|
||||||
|
this.node_.broadcast(data);
|
||||||
|
}
|
||||||
|
list_peers() {
|
||||||
|
return this.node_.list_peers();
|
||||||
|
}
|
||||||
|
// verify the block is valid
|
||||||
|
verify(block) {
|
||||||
|
// verify the block signature
|
||||||
|
if (!Block.verify_signature(block))
|
||||||
|
return false;
|
||||||
|
// verify consensus
|
||||||
|
if (!this.consensus_.verify(block))
|
||||||
|
return false;
|
||||||
|
// verify transcations
|
||||||
|
let tx = block.transcations;
|
||||||
|
for (var i = 0; i < tx.length; ++i) {
|
||||||
|
// todo (check tx is exist and valid)
|
||||||
|
if (!Transcation.verify(tx[i]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
on_data(msg) {
|
||||||
|
switch (msg.type) {
|
||||||
|
case MessageType.Block:
|
||||||
|
{
|
||||||
|
let block = msg.data;
|
||||||
|
// console.log(`node: ${this.get_account_id()} receive block: height ${block.height}`);
|
||||||
|
// check if exist
|
||||||
|
if (this.pending_block_[block.hash] || this.get_block(block.hash))
|
||||||
|
return;
|
||||||
|
// verify
|
||||||
|
if (!this.verify(block))
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.pending_block_[block.hash] = block;
|
||||||
|
|
||||||
|
// add to chain
|
||||||
|
if (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
|
||||||
|
}
|
||||||
|
// broadcast
|
||||||
|
this.broadcast(msg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageType.Transcation:
|
||||||
|
{
|
||||||
|
// check if exist(pending or in chain) verify, store(into pending) and broadcast
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (pbft && !this.is_bad_) {
|
||||||
|
this.pbft_.processMessage(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 transcations
|
||||||
|
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 transcations
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = BlockChain;
|
60
src/js/consensus/dpos.js
Normal file
60
src/js/consensus/dpos.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
'use strict';
|
||||||
|
var slot = require("./slot.js");
|
||||||
|
class DPos {
|
||||||
|
constructor(blockchain) {
|
||||||
|
this.last_slot_ = -1;
|
||||||
|
this.block_chain_ = blockchain;
|
||||||
|
}
|
||||||
|
|
||||||
|
prepared() {
|
||||||
|
let current_slot = slot.get_slot_number();
|
||||||
|
let current_id = current_slot % slot.delegates;
|
||||||
|
// console.log(current_slot + ' ' + current_id + ' ' + this.block_chain_.get_account_id());
|
||||||
|
|
||||||
|
if (current_id != this.block_chain_.get_account_id())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (current_slot == this.last_slot_)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
this.last_slot_ = current_slot;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
make_consensus(block_data) {
|
||||||
|
let self = this;
|
||||||
|
setImmediate((block) => {
|
||||||
|
let time_stamp = block.get_timestamp();
|
||||||
|
let block_slot = slot.get_slot_number(time_stamp);
|
||||||
|
let target_id = block_slot % slot.delegates;
|
||||||
|
|
||||||
|
let current_slot = slot.get_slot_number();
|
||||||
|
let current_id = current_slot % slot.delegates;
|
||||||
|
|
||||||
|
if (target_id != current_id) {
|
||||||
|
block.emit('consensus failed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target_id != self.block_chain_.get_account_id()) {
|
||||||
|
block.emit('consensus failed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
block.set_consensus_data({
|
||||||
|
"generator_id": self.block_chain_.get_account_id()
|
||||||
|
});
|
||||||
|
block.emit('consensus completed');
|
||||||
|
}, block_data);
|
||||||
|
|
||||||
|
}
|
||||||
|
verify(block) {
|
||||||
|
let time_stamp = block.timestamp;
|
||||||
|
let block_slot = slot.get_slot_number(time_stamp);
|
||||||
|
let id = block_slot % slot.delegates;
|
||||||
|
if (id != block.consensus_data.generator_id)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = DPos;
|
145
src/js/consensus/pbft.js
Normal file
145
src/js/consensus/pbft.js
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
var slot = require("./slot");
|
||||||
|
var Msg = require("../message");
|
||||||
|
var MessageType = require("../message").type;
|
||||||
|
|
||||||
|
var PBFT_N = slot.delegates;
|
||||||
|
var PBFT_F = Math.floor((PBFT_N - 1) / 3);
|
||||||
|
|
||||||
|
var State = {
|
||||||
|
Idle: 0,
|
||||||
|
Prepare: 1,
|
||||||
|
Commit: 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Pbft {
|
||||||
|
constructor(blockchain) {
|
||||||
|
this.block_chain_ = blockchain;
|
||||||
|
|
||||||
|
this.pending_block_ = {};
|
||||||
|
this.prepare_info_ = null;
|
||||||
|
this.commit_infos_ = {};
|
||||||
|
this.state_ = State.Idle;
|
||||||
|
|
||||||
|
this.prepare_hash_cache_ = {};
|
||||||
|
this.commit_hash_cache_ = {};
|
||||||
|
this.current_slot_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
make_consensus(block) {
|
||||||
|
let time_stamp = block.get_timestamp();
|
||||||
|
let block_slot = slot.get_slot_number(time_stamp);
|
||||||
|
if (block_slot > this.current_slot_) {
|
||||||
|
this.clear_state_();
|
||||||
|
}
|
||||||
|
this.pending_block_[block.get_hash()] = block;
|
||||||
|
|
||||||
|
if (this.state_ === State.Idle) {
|
||||||
|
this.current_slot_ = block_slot;
|
||||||
|
this.state_ = State.Prepare;
|
||||||
|
this.prepare_info_ = {
|
||||||
|
"height": block.get_height(),
|
||||||
|
"hash": block.get_hash(),
|
||||||
|
"votesNumber": 1,
|
||||||
|
"votes": {}
|
||||||
|
};
|
||||||
|
this.prepare_info_.votes[this.block_chain_.get_account_id()] = true;
|
||||||
|
var self = this;
|
||||||
|
setImmediate(() => {
|
||||||
|
// console.log(`node: ${self.block_chain_.get_account_id()} \t[prepared] broadcast prepare msg to peer: ${self.block_chain_.list_peers()}`);
|
||||||
|
self.block_chain_.broadcast(Msg.prepare({
|
||||||
|
"height": block.get_height(),
|
||||||
|
"hash": block.get_hash(),
|
||||||
|
"signer": self.block_chain_.get_account_id()
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_state_() {
|
||||||
|
this.state_ = State.Idle;
|
||||||
|
this.prepare_info_ = null;
|
||||||
|
this.commit_infos_ = {};
|
||||||
|
this.pending_block_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
commit(hash) {
|
||||||
|
var block = this.pending_block_[hash];
|
||||||
|
block.emit('consensus completed', block.toObject());
|
||||||
|
this.clear_state_();
|
||||||
|
}
|
||||||
|
|
||||||
|
processMessage(msg) {
|
||||||
|
var key = msg.data.hash + ':' + msg.data.height + ':' + msg.data.signer;
|
||||||
|
switch (msg.type) {
|
||||||
|
case MessageType.Prepare:
|
||||||
|
// 如果从未收到过这个prepare消息,则转播出去,否则不处理,防止无限广播
|
||||||
|
// hash+height其实就是这个消息的ID(n),而signer就是该消息的副本来源 i
|
||||||
|
if (!this.prepare_hash_cache_[key]) {
|
||||||
|
this.prepare_hash_cache_[key] = true;
|
||||||
|
this.block_chain_.broadcast(msg);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 如果当前为prepare状态,且收到的prepare消息是同一个id的消息(关于同一个block的)则这个消息的投票数加1
|
||||||
|
// 超过2f+1票后就进入commit状态,发送commit消息
|
||||||
|
if (this.state_ === State.Prepare &&
|
||||||
|
msg.data.height === this.prepare_info_.height &&
|
||||||
|
msg.data.hash === this.prepare_info_.hash &&
|
||||||
|
!this.prepare_info_.votes[msg.data.signer]) {
|
||||||
|
this.prepare_info_.votes[msg.data.signer] = true;
|
||||||
|
this.prepare_info_.votesNumber++;
|
||||||
|
if (this.prepare_info_.votesNumber > 2 * PBFT_F) {
|
||||||
|
// console.log(`node: ${this.block_chain_.get_account_id()} \t[commit] broadcast commit msg to peer: ${this.block_chain_.list_peers()}`);
|
||||||
|
this.state_ = State.Commit;
|
||||||
|
var commitInfo = {
|
||||||
|
"height": this.prepare_info_.height,
|
||||||
|
"hash": this.prepare_info_.hash,
|
||||||
|
"votesNumber": 1,
|
||||||
|
"votes": {}
|
||||||
|
};
|
||||||
|
commitInfo.votes[this.block_chain_.get_account_id()] = true;
|
||||||
|
this.commit_infos_[commitInfo.hash] = commitInfo;
|
||||||
|
this.block_chain_.broadcast(Msg.commit({
|
||||||
|
"height": this.prepare_info_.height,
|
||||||
|
"hash": this.prepare_info_.hash,
|
||||||
|
"signer": this.block_chain_.get_account_id()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case MessageType.Commit:
|
||||||
|
if (!this.commit_hash_cache_[key]) {
|
||||||
|
this.commit_hash_cache_[key] = true;
|
||||||
|
this.block_chain_.broadcast(msg);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// prepare消息是只能处理一个,但是commit消息却是可以处理多个,哪一个先达成共识就先处理哪个,然后剩下没处理的都清空
|
||||||
|
var commit = this.commit_infos_[msg.data.hash];
|
||||||
|
if (commit) {
|
||||||
|
if (!commit.votes[msg.data.signer]) {
|
||||||
|
commit.votes[msg.data.signer] = true;
|
||||||
|
commit.votesNumber++;
|
||||||
|
if (commit.votesNumber > 2 * PBFT_F) {
|
||||||
|
// console.log(`node: ${this.block_chain_.get_account_id()} \t[commited] do commit block}`);
|
||||||
|
this.commit(msg.data.hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.commit_infos_[msg.data.hash] = {
|
||||||
|
hash: msg.data.hash,
|
||||||
|
height: msg.data.height,
|
||||||
|
votesNumber: 1,
|
||||||
|
votes: {}
|
||||||
|
};
|
||||||
|
this.commit_infos_[msg.data.hash].votes[msg.data.signer] = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Pbft;
|
189
src/js/consensus/peercoin.cc
Normal file
189
src/js/consensus/peercoin.cc
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
bool CheckStakeKernelHash(unsigned int nBits,
|
||||||
|
const CBlockHeader &blockFrom,
|
||||||
|
unsigned int nTxPrevOffset,
|
||||||
|
const CTransaction &txPrev,
|
||||||
|
const COutPoint &prevout,
|
||||||
|
unsigned int nTimeTx,
|
||||||
|
uint256 &hashProofOfStake,
|
||||||
|
bool fPrintProofOfStake)
|
||||||
|
{
|
||||||
|
if (nTimeTx < txPrev.nTime)// Transaction timestamp violation
|
||||||
|
return error("CheckStakeKernelHash() : nTime violation");
|
||||||
|
|
||||||
|
unsigned int nTimeBlockFrom = blockFrom.GetBlockTime();
|
||||||
|
if (nTimeBlockFrom + nStakeMinAge > nTimeTx)// Min age requirement
|
||||||
|
return error("CheckStakeKernelHash() : min age violation");
|
||||||
|
|
||||||
|
//目标值使用nBits
|
||||||
|
CBigNum bnTargetPerCoinDay;
|
||||||
|
bnTargetPerCoinDay.SetCompact(nBits);
|
||||||
|
int64 nValueIn = txPrev.vout[prevout.n].nValue;
|
||||||
|
// v0.3 protocol kernel hash weight starts from 0 at the 30-day min age
|
||||||
|
// this change increases active coins participating the hash and helps
|
||||||
|
// to secure the network when proof-of-stake difficulty is low
|
||||||
|
int64 nTimeWeight = min((int64)nTimeTx - txPrev.nTime, (int64)STAKE_MAX_AGE) -
|
||||||
|
(IsProtocolV03(nTimeTx) ? nStakeMinAge : 0);
|
||||||
|
//计算币龄,STAKE_MAX_AGE为90天
|
||||||
|
CBigNum bnCoinDayWeight = CBigNum(nValueIn) * nTimeWeight / COIN / (24 * 60 * 60);
|
||||||
|
// Calculate hash
|
||||||
|
CDataStream ss(SER_GETHASH, 0);
|
||||||
|
//权重修正因子
|
||||||
|
uint64 nStakeModifier = 0;
|
||||||
|
int nStakeModifierHeight = 0;
|
||||||
|
int64 nStakeModifierTime = 0;
|
||||||
|
if (IsProtocolV03(nTimeTx))// v0.3 protocol
|
||||||
|
{
|
||||||
|
if (!GetKernelStakeModifier(blockFrom.GetHash(), nTimeTx, nStakeModifier, nStakeModifierHeight, nStakeModifierTime, fPrintProofOfStake))
|
||||||
|
return false;
|
||||||
|
ss << nStakeModifier;
|
||||||
|
}
|
||||||
|
else// v0.2 protocol
|
||||||
|
{
|
||||||
|
ss << nBits;
|
||||||
|
}
|
||||||
|
|
||||||
|
//计算proofhash
|
||||||
|
//即计算hash(nStakeModifier + txPrev.block.nTime + txPrev.offset + txPrev.nTime + txPrev.vout.n + nTime)
|
||||||
|
ss << nTimeBlockFrom << nTxPrevOffset << txPrev.nTime << prevout.n << nTimeTx;
|
||||||
|
hashProofOfStake = Hash(ss.begin(), ss.end());
|
||||||
|
if (fPrintProofOfStake)
|
||||||
|
{
|
||||||
|
if (IsProtocolV03(nTimeTx))
|
||||||
|
printf("CheckStakeKernelHash() : using modifier 0x%016" PRI64x " at height=%d timestamp=%s for block from height=%d timestamp=%s\n",
|
||||||
|
nStakeModifier, nStakeModifierHeight,
|
||||||
|
DateTimeStrFormat(nStakeModifierTime).c_str(),
|
||||||
|
mapBlockIndex[blockFrom.GetHash()]->nHeight,
|
||||||
|
DateTimeStrFormat(blockFrom.GetBlockTime()).c_str());
|
||||||
|
printf("CheckStakeKernelHash() : check protocol=%s modifier=0x%016" PRI64x " nTimeBlockFrom=%u nTxPrevOffset=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n",
|
||||||
|
IsProtocolV05(nTimeTx) ? "0.5" : (IsProtocolV03(nTimeTx) ? "0.3" : "0.2"),
|
||||||
|
IsProtocolV03(nTimeTx) ? nStakeModifier : (uint64)nBits,
|
||||||
|
nTimeBlockFrom, nTxPrevOffset, txPrev.nTime, prevout.n, nTimeTx,
|
||||||
|
hashProofOfStake.ToString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check if proof-of-stake hash meets target protocol
|
||||||
|
//判断是否满足proofhash < 币龄x目标值
|
||||||
|
if (CBigNum(hashProofOfStake) > bnCoinDayWeight * bnTargetPerCoinDay)
|
||||||
|
return false;
|
||||||
|
if (fDebug && !fPrintProofOfStake)
|
||||||
|
{
|
||||||
|
if (IsProtocolV03(nTimeTx))
|
||||||
|
printf("CheckStakeKernelHash() : using modifier 0x%016" PRI64x " at height=%d timestamp=%s for block from height=%d timestamp=%s\n",
|
||||||
|
nStakeModifier, nStakeModifierHeight,
|
||||||
|
DateTimeStrFormat(nStakeModifierTime).c_str(),
|
||||||
|
mapBlockIndex[blockFrom.GetHash()]->nHeight,
|
||||||
|
DateTimeStrFormat(blockFrom.GetBlockTime()).c_str());
|
||||||
|
printf("CheckStakeKernelHash() : pass protocol=%s modifier=0x%016" PRI64x " nTimeBlockFrom=%u nTxPrevOffset=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n",
|
||||||
|
IsProtocolV03(nTimeTx) ? "0.3" : "0.2",
|
||||||
|
IsProtocolV03(nTimeTx) ? nStakeModifier : (uint64)nBits,
|
||||||
|
nTimeBlockFrom, nTxPrevOffset, txPrev.nTime, prevout.n, nTimeTx,
|
||||||
|
hashProofOfStake.ToString().c_str());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int static GetNextTargetRequired(const CBlockIndex *pindexLast, bool fProofOfStake)
|
||||||
|
{
|
||||||
|
if (pindexLast == NULL)
|
||||||
|
return bnProofOfWorkLimit.GetCompact();// genesis block
|
||||||
|
|
||||||
|
const CBlockIndex *pindexPrev = GetLastBlockIndex(pindexLast, fProofOfStake);
|
||||||
|
if (pindexPrev->pprev == NULL)
|
||||||
|
return bnInitialHashTarget.GetCompact();// first block
|
||||||
|
const CBlockIndex *pindexPrevPrev = GetLastBlockIndex(pindexPrev->pprev, fProofOfStake);
|
||||||
|
if (pindexPrevPrev->pprev == NULL)
|
||||||
|
return bnInitialHashTarget.GetCompact();// second block
|
||||||
|
|
||||||
|
int64 nActualSpacing = pindexPrev->GetBlockTime() - pindexPrevPrev->GetBlockTime();
|
||||||
|
|
||||||
|
// ppcoin: target change every block
|
||||||
|
// ppcoin: retarget with exponential moving toward target spacing
|
||||||
|
CBigNum bnNew;
|
||||||
|
bnNew.SetCompact(pindexPrev->nBits);
|
||||||
|
//STAKE_TARGET_SPACING为10分钟,即10 * 60
|
||||||
|
//两个区块目标间隔时间即为10分钟
|
||||||
|
int64 nTargetSpacing = fProofOfStake ? STAKE_TARGET_SPACING : min(nTargetSpacingWorkMax, (int64)STAKE_TARGET_SPACING * (1 + pindexLast->nHeight - pindexPrev->nHeight));
|
||||||
|
//nTargetTimespan为1周,即7 * 24 * 60 * 60
|
||||||
|
//nInterval为1008,即区块间隔为10分钟时,1周产生1008个区块
|
||||||
|
int64 nInterval = nTargetTimespan / nTargetSpacing;
|
||||||
|
//计算当前区块目标值
|
||||||
|
bnNew *= ((nInterval - 1) * nTargetSpacing + nActualSpacing + nActualSpacing);
|
||||||
|
bnNew /= ((nInterval + 1) * nTargetSpacing);
|
||||||
|
|
||||||
|
if (bnNew > bnProofOfWorkLimit)
|
||||||
|
bnNew = bnProofOfWorkLimit;
|
||||||
|
|
||||||
|
return bnNew.GetCompact();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool CheckStakeKernelHashV2(CBlockIndex *pindexPrev,
|
||||||
|
unsigned int nBits,
|
||||||
|
unsigned int nTimeBlockFrom,
|
||||||
|
const CTransaction &txPrev,
|
||||||
|
const COutPoint &prevout,
|
||||||
|
unsigned int nTimeTx,
|
||||||
|
uint256 &hashProofOfStake,
|
||||||
|
uint256 &targetProofOfStake,
|
||||||
|
bool fPrintProofOfStake)
|
||||||
|
{
|
||||||
|
if (nTimeTx < txPrev.nTime)// Transaction timestamp violation
|
||||||
|
return error("CheckStakeKernelHash() : nTime violation");
|
||||||
|
|
||||||
|
//目标值使用nBits
|
||||||
|
CBigNum bnTarget;
|
||||||
|
bnTarget.SetCompact(nBits);
|
||||||
|
|
||||||
|
//计算币数x目标值
|
||||||
|
int64_t nValueIn = txPrev.vout[prevout.n].nValue;
|
||||||
|
CBigNum bnWeight = CBigNum(nValueIn);
|
||||||
|
bnTarget *= bnWeight;
|
||||||
|
|
||||||
|
targetProofOfStake = bnTarget.getuint256();
|
||||||
|
|
||||||
|
//权重修正因子
|
||||||
|
uint64_t nStakeModifier = pindexPrev->nStakeModifier;
|
||||||
|
uint256 bnStakeModifierV2 = pindexPrev->bnStakeModifierV2;
|
||||||
|
int nStakeModifierHeight = pindexPrev->nHeight;
|
||||||
|
int64_t nStakeModifierTime = pindexPrev->nTime;
|
||||||
|
|
||||||
|
//计算哈希值
|
||||||
|
//即计算hash(nStakeModifier + txPrev.block.nTime + txPrev.nTime + txPrev.vout.hash + txPrev.vout.n + nTime)
|
||||||
|
CDataStream ss(SER_GETHASH, 0);
|
||||||
|
if (IsProtocolV3(nTimeTx))
|
||||||
|
ss << bnStakeModifierV2;
|
||||||
|
else
|
||||||
|
ss << nStakeModifier << nTimeBlockFrom;
|
||||||
|
ss << txPrev.nTime << prevout.hash << prevout.n << nTimeTx;
|
||||||
|
hashProofOfStake = Hash(ss.begin(), ss.end());
|
||||||
|
|
||||||
|
if (fPrintProofOfStake)
|
||||||
|
{
|
||||||
|
LogPrintf("CheckStakeKernelHash() : using modifier 0x%016x at height=%d timestamp=%s for block from timestamp=%s\n",
|
||||||
|
nStakeModifier, nStakeModifierHeight,
|
||||||
|
DateTimeStrFormat(nStakeModifierTime),
|
||||||
|
DateTimeStrFormat(nTimeBlockFrom));
|
||||||
|
LogPrintf("CheckStakeKernelHash() : check modifier=0x%016x nTimeBlockFrom=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n",
|
||||||
|
nStakeModifier,
|
||||||
|
nTimeBlockFrom, txPrev.nTime, prevout.n, nTimeTx,
|
||||||
|
hashProofOfStake.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now check if proof-of-stake hash meets target protocol
|
||||||
|
//判断是否满足proofhash < 币数x目标值
|
||||||
|
if (CBigNum(hashProofOfStake) > bnTarget)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (fDebug && !fPrintProofOfStake)
|
||||||
|
{
|
||||||
|
LogPrintf("CheckStakeKernelHash() : using modifier 0x%016x at height=%d timestamp=%s for block from timestamp=%s\n",
|
||||||
|
nStakeModifier, nStakeModifierHeight,
|
||||||
|
DateTimeStrFormat(nStakeModifierTime),
|
||||||
|
DateTimeStrFormat(nTimeBlockFrom));
|
||||||
|
LogPrintf("CheckStakeKernelHash() : pass modifier=0x%016x nTimeBlockFrom=%u nTimeTxPrev=%u nPrevout=%u nTimeTx=%u hashProof=%s\n",
|
||||||
|
nStakeModifier,
|
||||||
|
nTimeBlockFrom, txPrev.nTime, prevout.n, nTimeTx,
|
||||||
|
hashProofOfStake.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
74
src/js/consensus/pos.js
Normal file
74
src/js/consensus/pos.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
class Pos {
|
||||||
|
constructor(blockchain) {
|
||||||
|
this.difficulty_ = 4294967295;// ~uint64(0) >> 32
|
||||||
|
this.time_stamp_ = (new Date()).getTime();
|
||||||
|
this.state_ = "Idle";
|
||||||
|
this.block_chain_ = blockchain;
|
||||||
|
}
|
||||||
|
prepared() {
|
||||||
|
return this.state_ == "Idle";
|
||||||
|
}
|
||||||
|
calc_difficulty(block) {
|
||||||
|
let prev = this.block_chain_.get_last_block();
|
||||||
|
if (!prev)
|
||||||
|
return this.difficulty_;
|
||||||
|
let prev_prev = this.block_chain_.get_hash(prev.hash);
|
||||||
|
if (!prev_prev)
|
||||||
|
return prev.difficulty;
|
||||||
|
|
||||||
|
let TargetSpacing = 10 * 60;
|
||||||
|
let TargetTimespan = 7 * 24 * 60 * 60;
|
||||||
|
let Interval = TargetTimespan / TargetSpacing;
|
||||||
|
let ActualSpacing = prev.timestamp - prev_prev.timestamp;
|
||||||
|
this.difficulty_ = prev.difficulty * ((Interval - 1) * TargetSpacing + 2 * ActualSpacing) / ((TargetSpacing + 1) * TargetSpacing);
|
||||||
|
}
|
||||||
|
make_consensus(block_data) {
|
||||||
|
this.state_ = "Busy";
|
||||||
|
this.calc_difficulty(block_data);
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
setImmediate(function make_proof(block) {
|
||||||
|
let time_period = self.time_stamp_ - block_data.get_timestamp();
|
||||||
|
if (time_period > 3600 * 1000) {
|
||||||
|
self.state_ = "Idle";
|
||||||
|
block.emit('consensus failed');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let amount = self.block_chain_.get_amount();
|
||||||
|
block.set_consensus_data({
|
||||||
|
"difficulty": self.difficulty_,
|
||||||
|
"timestamp": self.time_stamp_,
|
||||||
|
"amount": amount
|
||||||
|
});
|
||||||
|
|
||||||
|
var hash = block.calc_block_hash();
|
||||||
|
if (parseInt(hash, 16) < self.difficulty_ * amount) {
|
||||||
|
self.state_ = "Idle";
|
||||||
|
block.emit('consensus completed');
|
||||||
|
} else {
|
||||||
|
setTimeout(make_proof, 1000, block);
|
||||||
|
}
|
||||||
|
}, block_data);
|
||||||
|
}
|
||||||
|
verify(block) {
|
||||||
|
let hash = block.calc_block_hash();
|
||||||
|
if (hash != block.get_hash())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let difficulty = block.get_consensus_data().difficulty;
|
||||||
|
let amount = block.get_consensus_data().amount;
|
||||||
|
|
||||||
|
// todo: check the amount within the block timestamp
|
||||||
|
|
||||||
|
let target = difficulty * amount;
|
||||||
|
if (parseInt(hash, 16) < target) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Pos;
|
118
src/js/consensus/pow.js
Normal file
118
src/js/consensus/pow.js
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
class Pow {
|
||||||
|
constructor(blockchain) {
|
||||||
|
this.difficulty_ = 10000;
|
||||||
|
this.nonce_ = 0;
|
||||||
|
this.state_ = "Idle";
|
||||||
|
this.block_chain_ = blockchain;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calc_difficulty(block) {
|
||||||
|
// this.difficulty_ = parseInt("0001000000000000000000000000000000000000000000000000000000000000", 16);
|
||||||
|
// // this.difficulty_ = parseInt("0000100000000000000000000000000000000000000000000000000000000000", 16);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// make_consensus(block_data) {
|
||||||
|
// this.state_ = "Busy";
|
||||||
|
// this.calc_difficulty(block_data);
|
||||||
|
|
||||||
|
// let self = this;
|
||||||
|
// setImmediate((block) => {
|
||||||
|
// self.nonce_ = 0;
|
||||||
|
// while (self.nonce_ < Number.MAX_SAFE_INTEGER) {
|
||||||
|
// block.set_consensus_data({
|
||||||
|
// "difficulty": self.difficulty_,
|
||||||
|
// "nonce": self.nonce_
|
||||||
|
// });
|
||||||
|
|
||||||
|
// var hash = block.calc_block_hash();
|
||||||
|
// if (parseInt(hash, 16) < self.difficulty_) {
|
||||||
|
// block.emit('consensus completed');
|
||||||
|
// self.state_ = "Idle";
|
||||||
|
// break;
|
||||||
|
// } else
|
||||||
|
// self.nonce_ += 1;
|
||||||
|
// }
|
||||||
|
// block.emit('consensus failed');
|
||||||
|
// self.state_ = "Idle";
|
||||||
|
// }, block_data);
|
||||||
|
|
||||||
|
// }
|
||||||
|
// verify(block) {
|
||||||
|
// let hash = block.calc_block_hash();
|
||||||
|
// if (hash != block.get_hash())
|
||||||
|
// return false;
|
||||||
|
|
||||||
|
// let difficulty = block.get_consensus_data().difficulty;
|
||||||
|
// if (parseInt(hash, 16) < difficulty) {
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
prepared() {
|
||||||
|
return this.state_ == "Idle";
|
||||||
|
}
|
||||||
|
calc_difficulty(block) {
|
||||||
|
// adaption
|
||||||
|
let prev = this.block_chain_.get_last_block();
|
||||||
|
if (!prev.difficulty) {
|
||||||
|
this.difficulty_ = 10000;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var time_period = block.get_timestamp() - prev.timestamp;
|
||||||
|
if (time_period < 3000) {
|
||||||
|
this.difficulty_ = prev.difficulty + 9000;
|
||||||
|
} else if (prev.difficulty < 3000) {
|
||||||
|
this.difficulty_ = prev.difficulty + 9000;
|
||||||
|
} else {
|
||||||
|
this.difficulty_ = prev.difficulty - 3000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
make_consensus(block_data) {
|
||||||
|
this.state_ = "Busy";
|
||||||
|
this.calc_difficulty(block_data);
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
setImmediate((block) => {
|
||||||
|
self.nonce_ = 0;
|
||||||
|
let target = Number.MAX_SAFE_INTEGER / self.difficulty_ * 100;
|
||||||
|
while (self.nonce_ < Number.MAX_SAFE_INTEGER) {
|
||||||
|
block.set_consensus_data({
|
||||||
|
"difficulty": self.difficulty_,
|
||||||
|
"nonce": self.nonce_
|
||||||
|
});
|
||||||
|
|
||||||
|
var hash = block.calc_block_hash();
|
||||||
|
hash = hash.toString('utf8').substring(0, 16);
|
||||||
|
if (parseInt(hash, 16) < target) {
|
||||||
|
block.emit('consensus completed');
|
||||||
|
self.state_ = "Idle";
|
||||||
|
break;
|
||||||
|
} else
|
||||||
|
self.nonce_ += 1;
|
||||||
|
}
|
||||||
|
block.emit('consensus failed');
|
||||||
|
self.state_ = "Idle";
|
||||||
|
}, block_data);
|
||||||
|
|
||||||
|
}
|
||||||
|
verify(block) {
|
||||||
|
let hash = block.calc_block_hash();
|
||||||
|
if (hash != block.get_hash())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
let difficulty = block.get_consensus_data().difficulty;
|
||||||
|
let target = Number.MAX_SAFE_INTEGER / difficulty * 100;
|
||||||
|
hash = hash.toString('utf8').substring(0, 16);
|
||||||
|
|
||||||
|
if (parseInt(hash, 16) < target) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Pow;
|
||||||
|
|
23
src/js/consensus/slot.js
Normal file
23
src/js/consensus/slot.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
'use strict';
|
||||||
|
const delegates = 20;
|
||||||
|
const interval = 3;// second
|
||||||
|
|
||||||
|
function get_time(time) {
|
||||||
|
if (time === undefined) {
|
||||||
|
time = (new Date()).getTime();
|
||||||
|
}
|
||||||
|
var base_time = new Date(1548988864492).getTime();
|
||||||
|
return Math.floor((time - base_time) / 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_slot_number(time_stamp) {
|
||||||
|
time_stamp = get_time(time_stamp);
|
||||||
|
return Math.floor(time_stamp / interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
interval,
|
||||||
|
delegates,
|
||||||
|
get_time,
|
||||||
|
get_slot_number
|
||||||
|
};
|
12
src/js/genesis_block.json
Normal file
12
src/js/genesis_block.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"version": 0,
|
||||||
|
"height": 1,
|
||||||
|
"previous_hash": null,
|
||||||
|
"timestamp": 1550049140488,
|
||||||
|
"merkle_hash": null,
|
||||||
|
"generator_publickey": "18941c80a77f2150107cdde99486ba672b5279ddd469eeefed308540fbd46983",
|
||||||
|
"hash": "d611edb9fd86ee234cdc08d9bf382330d6ccc721cd5e59cf2a01b0a2a8decfff",
|
||||||
|
"block_signature": "603b61b14348fb7eb087fe3267e28abacadf3932f0e33958fb016ab60f825e3124bfe6c7198d38f8c91b0a3b1f928919190680e44fbe7289a4202039ffbb2109",
|
||||||
|
"consensus_data": {},
|
||||||
|
"transcations": []
|
||||||
|
}
|
19
src/js/message.js
Normal file
19
src/js/message.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var MessageType = {
|
||||||
|
Connection: 0,
|
||||||
|
Block: 1,
|
||||||
|
Transcation: 2,
|
||||||
|
PrePrepare: 3,
|
||||||
|
Prepare: 4,
|
||||||
|
Commit: 5
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
type: MessageType,
|
||||||
|
connection: (data) => { return { type: MessageType.Connection, data: data }; },
|
||||||
|
block: (data) => { return { type: MessageType.Block, data: data }; },
|
||||||
|
preprepare: (data) => { return { type: MessageType.PrePrepare, data: data }; },
|
||||||
|
prepare: (data) => { return { type: MessageType.Prepare, data: data }; },
|
||||||
|
commit: (data) => { return { type: MessageType.Commit, data: data }; }
|
||||||
|
};
|
109
src/js/network.js
Normal file
109
src/js/network.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var net = require("net");
|
||||||
|
var Msg = require("./message");
|
||||||
|
var EventEmitter = require('events').EventEmitter;
|
||||||
|
var Promise = require("bluebird");
|
||||||
|
|
||||||
|
var PORT = 8000;
|
||||||
|
|
||||||
|
class Node extends EventEmitter {
|
||||||
|
constructor(id) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.id_ = id;
|
||||||
|
this.peers_ = {};
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
this.server_ = net.createServer((socket) => {
|
||||||
|
socket.setEncoding('utf8');
|
||||||
|
socket.on('data', (data) => { self.on_data(data, socket); });
|
||||||
|
socket.on('end', () => { self.remove_peer(socket); });
|
||||||
|
});
|
||||||
|
this.server_.listen(PORT + id);
|
||||||
|
}
|
||||||
|
async start() {
|
||||||
|
for (var i = 0; i < 5; ++i) {
|
||||||
|
var remote_id = Math.floor(Math.random() * 20); // [0, 20)
|
||||||
|
if (remote_id !== this.id_ && !this.peers_[remote_id]) {
|
||||||
|
let self = this;
|
||||||
|
// console.log(`${this.id_}-->${remote_id}`);
|
||||||
|
var socket = net.createConnection({ port: (PORT + remote_id) });
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
socket.on('connect', () => {
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
// console.log(`id: ${self.id_} connected to remote_id: ${remote_id}`);
|
||||||
|
let data = Msg.connection(self.id_);
|
||||||
|
self.send(socket, data);
|
||||||
|
self.add_peer(socket, remote_id);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
on_data(data, socket) {
|
||||||
|
try {
|
||||||
|
var arr = data.split("\r\n");
|
||||||
|
for (var i = 0; i < arr.length; ++i) {
|
||||||
|
if (arr[i] == '') continue;
|
||||||
|
let obj = JSON.parse(arr[i]);
|
||||||
|
if (obj.type == Msg.type.Connection) {
|
||||||
|
// if data is connection info, add peer and response
|
||||||
|
let remote_id = obj.data;
|
||||||
|
this.add_peer(socket, remote_id);
|
||||||
|
// console.log(`node ${this.id_} receive connection: ${remote_id}`);
|
||||||
|
// console.log(`${this.id_}-->${remote_id}`);
|
||||||
|
} else {
|
||||||
|
// else emit msg to blockchain
|
||||||
|
this.emit("message", obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log("=========================");
|
||||||
|
console.log(`node: ${this.id_}\t receive msg error`);
|
||||||
|
console.log(err);
|
||||||
|
console.log(err.message);
|
||||||
|
console.log(data);
|
||||||
|
console.log(arr.length);
|
||||||
|
console.log("=========================");
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
send(socket, data) {
|
||||||
|
if (typeof data === 'object') {
|
||||||
|
data = JSON.stringify(data);
|
||||||
|
}
|
||||||
|
socket.write(data + "\r\n");
|
||||||
|
}
|
||||||
|
broadcast(data) {
|
||||||
|
for (var index in this.peers_) {
|
||||||
|
let socket = this.peers_[index];
|
||||||
|
this.send(socket, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_peer(socket, remote_id) {
|
||||||
|
if (!this.peers_[remote_id]) {
|
||||||
|
this.peers_[remote_id] = socket;
|
||||||
|
// console.log(`${this.id_}-->${remote_id}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
remove_peer(socket) {
|
||||||
|
for (var index in this.peers_) {
|
||||||
|
if (this.peers_[index] == socket) {
|
||||||
|
delete this.peers_[index];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list_peers() {
|
||||||
|
let peer_ids = [];
|
||||||
|
for (var index in this.peers_) {
|
||||||
|
peer_ids.push(index);
|
||||||
|
}
|
||||||
|
return peer_ids;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Node;
|
20
src/js/package.json
Normal file
20
src/js/package.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"name": "js",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "node.js",
|
||||||
|
"dependencies": {
|
||||||
|
"babel-eslint": "^10.0.1",
|
||||||
|
"bluebird": "^3.5.3",
|
||||||
|
"ed25519": "0.0.4",
|
||||||
|
"eslint": "^5.13.0",
|
||||||
|
"eslint-plugin-html": "^5.0.0",
|
||||||
|
"js-sha256": "^0.9.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
8
src/js/test/create_block.js
Normal file
8
src/js/test/create_block.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var BlockChain = require("../blockchain");
|
||||||
|
var Consensus = require("../consensus/pow");
|
||||||
|
|
||||||
|
let blockchain = new BlockChain(Consensus);
|
||||||
|
|
||||||
|
console.log(blockchain.get_last_block());
|
60
src/js/test/create_genesis.js
Normal file
60
src/js/test/create_genesis.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var ed = require('ed25519');
|
||||||
|
|
||||||
|
var password = 'I am genesis!';
|
||||||
|
|
||||||
|
var hash = crypto.createHash('sha256').update(password).digest();
|
||||||
|
var keypair = ed.MakeKeypair(hash);
|
||||||
|
|
||||||
|
let genesis = {
|
||||||
|
"version": 0,
|
||||||
|
"height": 1,
|
||||||
|
"previous_hash": null,
|
||||||
|
"timestamp": 1550049140488,
|
||||||
|
"merkle_hash": null,
|
||||||
|
"generator_publickey": keypair.publicKey.toString('hex'),
|
||||||
|
"hash": null,
|
||||||
|
"block_signature": null,
|
||||||
|
"consensus_data": {},
|
||||||
|
"transcations": []
|
||||||
|
};
|
||||||
|
|
||||||
|
function prepare_data() {
|
||||||
|
let tx = "";
|
||||||
|
genesis.transcations.forEach(val => {
|
||||||
|
tx += val.toString('utf8');
|
||||||
|
});
|
||||||
|
let data = genesis.version.toString()
|
||||||
|
+ genesis.height.toString()
|
||||||
|
+ genesis.previous_hash
|
||||||
|
+ genesis.timestamp.toString()
|
||||||
|
+ genesis.merkle_hash
|
||||||
|
+ genesis.generator_publickey
|
||||||
|
+ JSON.stringify(genesis.consensus_data)
|
||||||
|
+ tx;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calc_hash(data) {
|
||||||
|
return crypto.createHash('sha256').update(data).digest('hex');
|
||||||
|
}
|
||||||
|
function calc_block_hash() {
|
||||||
|
return calc_hash(prepare_data());
|
||||||
|
}
|
||||||
|
function sign(keypair) {
|
||||||
|
var hash = calc_block_hash();
|
||||||
|
return ed.Sign(Buffer.from(hash, 'utf-8'), keypair).toString('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
genesis.hash = calc_block_hash();
|
||||||
|
genesis.block_signature = sign(keypair);
|
||||||
|
var res = ed.Verify(Buffer.from(genesis.hash, 'utf-8'), Buffer.from(genesis.block_signature, 'hex'), keypair.publicKey);
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
console.log(genesis);
|
||||||
|
console.log(JSON.stringify(genesis));
|
||||||
|
}
|
34
src/js/test/dpos.js
Normal file
34
src/js/test/dpos.js
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var ed = require('ed25519');
|
||||||
|
var BlockChain = require("../blockchain");
|
||||||
|
var Consensus = require("../consensus/dpos");
|
||||||
|
var Promise = require("bluebird");
|
||||||
|
|
||||||
|
var password = 'I am tester!';
|
||||||
|
|
||||||
|
var hash = crypto.createHash('sha256').update(password).digest();
|
||||||
|
var keypair = ed.MakeKeypair(hash);
|
||||||
|
|
||||||
|
let blockchains = [];
|
||||||
|
for (var i = 0; i < 20; ++i) {
|
||||||
|
let blockchain = new BlockChain(Consensus, keypair, i);
|
||||||
|
blockchain.start();
|
||||||
|
blockchains.push(blockchain);
|
||||||
|
}
|
||||||
|
// test1
|
||||||
|
setTimeout(() => {
|
||||||
|
for (var i = 0; i < 20; ++i) {
|
||||||
|
console.log(`${i} --> ${blockchains[i].list_peers()}`);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
// // test2
|
||||||
|
// function print_blockchian() {
|
||||||
|
// for (i = 0; i < 20; ++i) {
|
||||||
|
// blockchains[i].print();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// setInterval(print_blockchian, 10000);
|
39
src/js/test/dpos_pbft.js
Normal file
39
src/js/test/dpos_pbft.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var ed = require('ed25519');
|
||||||
|
var BlockChain = require("../blockchain");
|
||||||
|
var Consensus = require("../consensus/dpos");
|
||||||
|
var Promise = require("bluebird");
|
||||||
|
|
||||||
|
var password = 'I am tester!';
|
||||||
|
|
||||||
|
var hash = crypto.createHash('sha256').update(password).digest();
|
||||||
|
var keypair = ed.MakeKeypair(hash);
|
||||||
|
|
||||||
|
let blockchains = [];
|
||||||
|
for (var i = 0; i < 20; ++i) {
|
||||||
|
let blockchain;
|
||||||
|
if (i == 10)
|
||||||
|
blockchain = new BlockChain(Consensus, keypair, i, true);
|
||||||
|
else
|
||||||
|
blockchain = new BlockChain(Consensus, keypair, i);
|
||||||
|
blockchain.start();
|
||||||
|
blockchains.push(blockchain);
|
||||||
|
}
|
||||||
|
// // test1
|
||||||
|
// setTimeout(() => {
|
||||||
|
// for (var i = 0; i < 20; ++i) {
|
||||||
|
// console.log(`${i} --> ${blockchains[i].list_peers()}`);
|
||||||
|
// }
|
||||||
|
// }, 3000);
|
||||||
|
|
||||||
|
// test2
|
||||||
|
function print_blockchian() {
|
||||||
|
console.log("--------------------------------");
|
||||||
|
for (i = 0; i < 20; ++i) {
|
||||||
|
blockchains[i].print();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setInterval(print_blockchian, 10000);
|
13
src/js/test/network.js
Normal file
13
src/js/test/network.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var Node = require("../network");
|
||||||
|
|
||||||
|
var nodes = [];
|
||||||
|
for (var i = 0; i < 20; ++i) {
|
||||||
|
let node = new Node(i);
|
||||||
|
nodes.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < 1; ++i) {
|
||||||
|
nodes[i].start();
|
||||||
|
}
|
33
src/js/test/pow.js
Normal file
33
src/js/test/pow.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var ed = require('ed25519');
|
||||||
|
var BlockChain = require("../blockchain");
|
||||||
|
var Consensus = require("../consensus/pow");
|
||||||
|
var Promise = require("bluebird");
|
||||||
|
|
||||||
|
let blockchain = new BlockChain(Consensus);
|
||||||
|
|
||||||
|
// console.log(blockchain.get_last_block().hash);
|
||||||
|
|
||||||
|
var password = 'I am tester!';
|
||||||
|
|
||||||
|
var hash = crypto.createHash('sha256').update(password).digest();
|
||||||
|
var keypair = ed.MakeKeypair(hash);
|
||||||
|
|
||||||
|
async function create_block(prev_time) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
blockchain.generate_block(keypair, () => {
|
||||||
|
console.log(`|${blockchain.get_last_block().hash}|${blockchain.get_last_block().timestamp - prev_time}|`);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
for (var i = 0; i < 20; ++i) {
|
||||||
|
let prev_time = blockchain.get_last_block().timestamp;
|
||||||
|
await create_block(prev_time);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
13
src/js/transcation.js
Normal file
13
src/js/transcation.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
class Transcation {
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static verify(tx) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Transcation;
|
Loading…
Reference in New Issue
Block a user