13. XPoA共识

13.1. 介绍

XPoA是 XuperChain 对PoA的一种实现,其基本思想是在节点中动态设定一组验证节点,验证节点组在预设的时间段内进行组内轮流出块(称之为轮值),即其余节点在某特定验证节点V出块的时间段内统一将交易发送给V,交易由该验证节点V打包成区块。

XPoA支持动态变更验证节点,可以通过指令修改现有的验证节点组,包括对当前验证节点组进行删除和添加操作。在该算法中,预设时间段包括确定单个区块的出块时间,以及验证节点单次轮值出块数量。 同样,XPoA通过Chained-BFT算法来保证轮值期间的安全性。

详细操作请参考 XPoA使用文档

13.2. 技术细节

在XPoA中,网络中的节点有两种角色,分别是“普通节点”和“验证节点”:

  1. 普通节点:普通节点仅对验证节点进行验证,计算当前时间点下验证节点地址是否于计算结果吻合。

  2. 验证节点:进行区块打包工作;在更改验证节点组过程中,多数验证节点需确定更改结果添加和删除操作方能生效。

修改验证组规则

验证组信息通过合约调用进行修改,流程主要有以下几点:

  1. 在收到该信息后,验证节点通过签名信息确认交易真实性

  2. 验证节点在UtxoVM中进行系统调用并更新当前验证人集合读写集

  3. 验证人集合并不会立即影响当前共识,在三个区块后集合才能生效

验证节点间轮值

每一轮的时间由配置xuper.json指定,在单轮时间段内,区块打包由目前验证节点组中的节点按顺序轮流完成。在通过合约发起验证节点变更后,变更会在三个区块后才触发,然后验证节点按照新的验证组继续进行轮值。

https://raw.githubusercontent.com/aucusaga/LearnXuperchainPicRep/master/XPoA/XPoA.jpg

调度代码具体实现如下:

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
    }

调度流程如下:

https://raw.githubusercontent.com/aucusaga/LearnXuperchainPicRep/master/XPoA/minerScheduling.jpg

拜占庭容错

XPoA验证节点轮值过程中,采取了 Chained-Bft 防止矿工节点的作恶。

13.3. 整体代码

XPoA实现主要在 consensus/xpoa 路径下,其主要是通过智能合约的方式实现的,合约在 contractsdk/cpp/example/xpoa_validates/src 路径下,主要有以下几个合约方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
/*XPoA添加一个新的候选人节点*/
DEFINE_METHOD(Hello, add_validate) {
    ...
}
/*XPoA删除一个候选人节点*/
DEFINE_METHOD(Hello, del_validate) {
    ...
}
/*XPoA更新一个候选人节点信息*/
DEFINE_METHOD(Hello, update_validate) {
    ...
}
/*查询当前候选人节点信息*/
DEFINE_METHOD(Hello, get_validates) {
    ...
}

核心接口如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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
}