mirror of
https://github.com/yjjnls/awesome-blockchain.git
synced 2025-01-12 14:29:25 -05: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