13. XPoA共识
13.1. 介绍
XPoA是超级链对PoA的一种实现,其基本思想是在节点中动态设定一组验证节点,验证节点组在预设的时间段内进行组内轮流出块(称之为轮值),即其余节点在某特定验证节点V出块的时间段内统一将交易发送给V,交易由该验证节点V打包成区块。
XPoA支持动态变更验证节点,可以通过指令修改现有的验证节点组,包括对当前验证节点组进行删除和添加操作。在该算法中,预设时间段包括确定单个区块的出块时间,以及验证节点单次轮值出块数量。 同样,XPoA通过Chained-BFT算法来保证轮值期间的安全性。
详细操作请参考 XPoA使用文档
13.2. 技术细节
在XPoA中,网络中的节点有两种角色,分别是“普通节点”和“验证节点”:
普通节点:普通节点仅对验证节点进行验证,计算当前时间点下验证节点地址是否于计算结果吻合。
验证节点:进行区块打包工作;在更改验证节点组过程中,多数验证节点需确定更改结果添加和删除操作方能生效。
修改验证组规则
验证组信息通过合约调用进行修改,流程主要有以下几点:
在收到该信息后,验证节点通过签名信息确认交易真实性
验证节点在UtxoVM中进行系统调用并更新当前验证人集合读写集
验证人集合并不会立即影响当前共识,在三个区块后集合才能生效
验证节点间轮值
每一轮的时间由配置xuper.json指定,在单轮时间段内,区块打包由目前验证节点组中的节点按顺序轮流完成。在通过合约发起验证节点变更后,变更会在三个区块后才触发,然后验证节点按照新的验证组继续进行轮值。
调度代码具体实现如下:
func (xpoa *XPoa) minerScheduling(timestamp int64) (term int64, pos int64, blockPos int64) {
...
// 每一轮的时间
termTime := xpoa.xpoaConf.period * int64(len(xpoa.proposerInfos)) * xpoa.xpoaConf.blockNum
// 每个矿工轮值时间
posTime := xpoa.xpoaConf.period * xpoa.xpoaConf.blockNum
// 当前轮数
term = (timestamp-xpoa.termTimestamp)/termTime + 1
// 本轮已过时间
resTime := (timestamp - xpoa.termTimestamp) - (term-1)*termTime
// 当前验证节点所属位置
pos = resTime / posTime
// 当前验证节点所处轮值时间已过时间
resTime = resTime - (resTime/posTime)*posTime
// 当前验证节点已出块数量
blockPos = resTime/xpoa.xpoaConf.period + 1
...
return
}
调度流程如下:
拜占庭容错
XPoA验证节点轮值过程中,采取了 Chained-Bft 防止矿工节点的作恶。
13.3. 整体代码
XPoA实现主要在 consensus/xpoa
路径下,其主要是通过智能合约的方式实现的,合约在 contractsdk/cpp/example/xpoa_validates/src
路径下,主要有以下几个合约方法:
/*XPoA添加一个新的候选人节点*/
DEFINE_METHOD(Hello, add_validate) {
...
}
/*XPoA删除一个候选人节点*/
DEFINE_METHOD(Hello, del_validate) {
...
}
/*XPoA更新一个候选人节点信息*/
DEFINE_METHOD(Hello, update_validate) {
...
}
/*查询当前候选人节点信息*/
DEFINE_METHOD(Hello, get_validates) {
...
}
核心接口如下:
func (xpoa *XPoa) minerScheduling(timestamp int64) (term int64, pos int64, blockPos int64) {
// 轮值时间调度计算规则
...
return
}
func (xpoa *XPoa) getCurrentValidates() ([]*cons_base.CandidateInfo, int64, int64, error) {
// 获取当前验证组信息,若无法查询则使用xuper.json初始化值
...
return candidateInfos.Proposers, confirmedTime, confirmedHeight, nil
}
func (xpoa *XPoa) updateValidates(curHeight int64) (bool, error) {
// 查询当前验证组,判断当前时间点是否需要更新验证组
...
return true, nil
}
func (xpoa *XPoa) updateViews(viewNum int64) error {
// 获取当前验证节点以及下一验证节点,创建下一轮新视图
...
return xpoa.bftPaceMaker.NextNewView(viewNum, nextProposer, proposer)
}
func (xpoa *XPoa) getProposerWithTime(timestamp, height int64) (string, error) {
// 根据当前时间戳计算当前验证节点是谁并返回其地址
...
return xpoa.proposerInfos[pos].Address, nil
}