add basic blockchain implementaion in node.js

This commit is contained in:
yjjnls 2019-03-04 21:16:32 +08:00
parent d2e94b75f6
commit 05a373e36b
22 changed files with 1425 additions and 0 deletions

2
src/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
package-lock.json
node_modules

11
src/js/Readme.md Normal file
View 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
View 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
View 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
View 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
// todotx 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
View 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
View 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其实就是这个消息的IDn而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;

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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"
}

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

View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,13 @@
'use strict';
class Transcation {
constructor() {
}
static verify(tx) {
return true;
}
}
module.exports = Transcation;