原理介绍
在PoS中,任何持有币的节点都可以参与挖矿,这样网络中有多少记账节点是不可知的,记账节点之间的网络环境也是不可知的。节点越多,网络越复杂,越容易造成网络分区,从而延长达成共识的时间。
而DPoS将记账权限定在一定数量内,获得记账权的节点才会进行挖矿记账,这样就能极大地提高系统的吞吐量。记账节点通过选举产生,全网投票哪些节点能获得记账权,这个过程我们称为“投票选举”,所以DPoS (Delegated proof of stake) 称为委托权益证明。获得记账权的节点也可以被称为“超级节点”或者见证人,以区分普通用户节点。
由于DPoS记账节点数量不多,那么我们就可以在共识算法中设置出块时间为固定值,通过轮流出块来进行记账。
以上就是DPoS的设计思路,下面以伪代码的形式来描述DPoS的过程。
for round i //分成很多个round,round无限持续
dlist_i = get N delegates sort by votes //根据投票结果选出得票率最高的N个受托人
dlist_i = shuffle(dlist_i) //随机改变顺序
loop //round完了,退出循环
slot = global_time_offset / block_interval
pos = slot % N
if dlist_i[pos] exists in this node //delegate在这个节点
generateBlock(keypair of dlist_i[pos]) //产生block
else
skip
如果某个超级节点在非指定时间产生了区块,则会被认为是无效的。每经过一段时间,超级节点的出块顺序会重新排列,而超级节点也可能重新选举。
下图就是一个理想的轮流记账状态。
如果节点B恶意分叉,那会形成下面的局面:
诚实节点A和C在相同时间内产生的区块数量是坏节点B的两倍,A和C形成的链更长,而诚实节点始终会选择最长的链,从而抵御攻击。
在 DPoS 白皮书中介绍了少数记账节点恶意或故障造成的分叉、网络分区情况下重复出块、少数记账节点重复出块、记账节点数量不足、多数记账节点的联合腐败等各种情况。
代码实现
基本结构
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);
}
}
DPoS的结构十分简单,但是其共识过程与其他方法不同。依据时间戳来确定当前出块的委托人ID,如果轮到当前节点出块,那么就发射对应信号。同时还要将当前节点的ID写入区块中,用于后续验证委托人节点是否在当前时间段出块。
轮流记账
我们假设有20个超级节点,每10s出一个块。由以上内容可知,超级节点需要依据投票选出,并且其出票顺序需要定期更换。而在这里我们将节点的选举与排位留给社区去完成,假设已有20个代理人节点,我们需要依据当前时间来决定谁出块。
'use strict';
const delegates = 20;
const interval = 10;// 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);
}
-
base_time
表示系统启动的时间 -
getSlotNumber
用于计算时间戳偏移量,每经过10s,其结果加1,对其结果取20的余数就可以获得出票节点编号。getSlotNumber不仅可以计算出当前该由谁出票,也可以推导出某个特定时间点该由谁来出票。
劣势
DPoS系统中,少数的超级节点提高了系统的吞吐率,但是也使得DPoS系统更像传统的分布式系统,这也是V神一致批评DPoS的地方。
DPoS系统并不考虑拜占庭问题,它的设计目的就是为了快速记账,而把发生的问题留给社区治理,也就是最终归为投票,但是投票并不能解决所有问题。
代码地址:https://github.com/yjjnls/awesome-blockchain/tree/v0.1.1/src/js
网友评论