DPR Chain 详细设计
一、共识
1. 共识简介
DPR Chain 所采用的共识协议结合了实用拜占庭容错算法(PBFT)和权益证明算法(POS),能容忍1/3(不包含)的故障节点(指1/3的 Power,而非1/3的共识节点 Validator),且不会分叉。每个共识节点 Validator 都配置有一个共识权重 Power,通过共识权重 Power 来确定下一高度的出块节点 Proposer,并最终由 Proposer 来打包交易并出块。
共识算法给每个区块赋予一个增量高度 height,在某一高度中只存在一个有效的区块,区块链从高度为0的创世纪块开始,由一个共识节点集合 ValidatorSet 投票产生下一个区块。在投票产生某一高度的区块的过程中,在正式提交 commit 某一高度的区块之前,至少需要经过一轮投票来达成共识。每一轮都会产生一个与上一轮不同的提议者 proposer,该提议者 proposer 在当轮以广播的形式提出一个提议 proposal,提议 proposal 经过验证者 Validator的集体投票,来决定是否最终提交该区块或者进入下一轮。在提议的区块真正被提交 commit 之前,验证者们需要进行两轮投票(pre-vote 和 pre-commit), 通过一个简单的锁机制用来阻止少于总数1/3的拜占庭节点攻击。为了应对单个拜占庭故障节点,DPR Chain 网络至少需要包括4个共识节点。每个共识节点拥有一对非对称密钥,其中私钥用来进行数字签名,公钥用来标识自己的身份ID。共识节点们从公共的初始状态开始,初始状态包含了一份共识节点列表。所有的提议和投票都需要各自的私钥签名,便于其他验证者进行公钥验证。下面这张图是共识算法的状态转换图:

共识算法的状态转换周期为:NewHeight -> Propose -> Prevote -> Precommit -> Commit 五个阶段。上述每个状态都被称为一个 Step,首尾的 NewHeight 和 Commit 这两个 Steps 被称为特殊的 Step,代表一个新区块的开始和提交。每一轮开始期间,存在一个用来计时的本地同步时钟,如果验证者在TimeoutPropose时间内没有收到提议,验证者将参与投票来决定是否跳过当前提交者。TimeoutPropose会随着轮数的增加而增加。每轮收到提议以后,进入完全异步模式。之后验证者的每一个网络决定需要得到2/3以上验证者的同意。这样降低了对同步时钟的依赖或者网络的延迟。中间三个 Steps 则被称为一轮 Round,是共识算法的核心原理所在。一个块的最终提交Commit可能需要多个 Round 过程,这是因为有许多原因可能会导致当前 Round 不成功(比如出块节点 Offline,提出的块是无效块,收到的 Prevote 或者 Precommit 票数不够2/3等)。
共识算法可以大致分为以下部分:
- 提议(Proposal):在每一轮(Round)中,新区块的提议者必须是有效的,并且广播给其他验证者。如果在一定时间内没有收到当轮提议(proposal),当前提议者将被后面的提议者接替。
- 投票(Vote):两阶段的投票基于优化的拜占庭容错。它们分别被称作预投票(pre-vote)和预提交(pre-commit)。对于同一个区块同一轮如果存在超过2/3的预提交(pre-commit)则对应产生一个提交(commit)。
- 锁(Lock):在拜占庭节点数少于节点总数的1/3的情况下,锁机制可以确保没有两个验证者在同一高度提交(commit)了两个不同的区块。锁机制确保了在当前高度验证者的下一轮预投票或者预提交依赖于这一轮的预投票或者预提交。
2. 提议
每轮Round开始于一个提议(proposal),提议者从内存池(Mempool)选取一批交易进而构成了一个区块,该区块随后被嵌套在ProposalMsg中,最后提议者广播ProposalMsg。
提议者通过一个简单并且相对固定的的Roubd Robin算法轮流坐庄,所以每一轮只有一个有效且被所有验证者公认的提议者。如果验证者收到了之前更低轮次的提议或者提议来自于非法的提议者,该提议将被拒绝。Round Robin算法基于validator的power,每轮选择power最大的validator选为proposer,然后将proposer的power减去所有其他validators的初始power总和,未选中的validtors各自将自己的power值累加上自己初始的power值。
提议者的轮流坐庄对于拜占庭容错是必要的。比如,对于raft算法,如果选举出来的leader是拜占庭,并且leader与其他节点网络连接状态良好,该leader可以完全控制整个网络,网络节点的安全和正常运转将无从得到保障。DPR Chain共识算法通过投票和锁的机制确保了系统的安全性。如果一个提议者在限定时间内没有处理任何交易,排在其后的提议者将会接替他。
3. 投票
一旦验证者从网络中收到了一份完整的提议(proposal ),他对该提议进行预投票(pre-vote)签名,并且广播到网络中。如果验证者在ProposalTimeout时间内没有接收到一个有效的提议,其对该提议的预投票为空(nil)。
在存在拜占庭节点的异步环境中,单阶投票,即每个验证者对每个提议只投一次,不能足以确保整个系统的安全。本质上,因为验证者可能做出一些不诚实的行为,并且消息的到达时间没有任何保障,一个不诚实的验证者可以与其他验证者进行协作来提交(commit)一个区块,然而其他没有看到这个提交区块的验证者进入了新的一轮,并提交(commit)了一个不同的区块。
一个单阶的投票允许验证者互相沟通他们知道的关于该提议的信息。但是为了容忍拜占庭故障,他们也需要互相告诉对方他们自己了解到的其他验证者声称了解到的关于该提交的信息。换句话说,二阶段提交确保了足够的验证者见证了第一阶段的结果。
对于某个区块的非空预投票是为网络提交(commit)区块已做好准备的投票。空预投票是为网络直接进入下一轮的投票。在理想的一轮中,超过2/3的验证者为该提议进行了预投票。在任意一轮中,区块具有的超过2/3的预投票被称作一个波尔卡(polka)。超过2/3的空预投票成为空波尔卡(nil-polka)。
当一个验证者收到了一个波尔卡(polka),他接受到了一个信号,即网络准备提交该区块,作为一个验证者签名并且广播预提交(pre-commit)的背书。有时,由于网络的不同时性,验证者可能没有收到对应的波尔卡或者波尔卡根本就不存在。在这种情况下,验证者没有对应的波尔卡为这个预提交背书,此时预提交为空。也就是说,在没有收到波尔卡背书的情况下,签名一个预提交被看作是一个恶意行为。
预提交(pre-commit)是关于提交(commit)一个块的投票。空预提交则投票进入到下一轮。如果验证者收到2/3以上验证者的预提交,则其在本地提交该块,计算结果状态,并移动到下一高度的第0轮。如果验证者接收到超过2/3的空预提交,则投票进入下一轮。
4. 锁
多轮投票的安全问题是棘手的,必须避免同一高度不同轮数分别提交两个不同区块的情形。在DPR Chain中,这个问题可以通过锁机制得到解决。锁机制的大致定位在波尔卡附近。本质上,预提交必须有一个波尔卡为其背书,验证者被锁定在其最近预提交(pre-commit)的区块上。
锁定规则:
- 预投票锁(Prevote-the-Lock):验证者只能预投票(pre-vote)他们被锁定的区块。这样就阻止验证者在上一轮中预提交(pre-commit)一个区块,之后又预投票了下一轮的另一个区块。
- 波尔卡解锁(Unlock-on-Polka ):验证者只有在看到更高一轮(相对于其当前被锁定区块的轮数)的波尔卡之后才能释放该锁。这样就允许验证者解锁,如果他们预提交了某个区块,但是这个区块网络的剩余节点不想提交,这样就保护了整个网络的运转,并且这样做并没有损害网络安全性。
简单来说,验证者可以被看作锁在任意高度-1轮的nil-block上,所以波尔卡解锁意味着验证者不能预提交一个新高度的区块直到他们看见一个波尔卡。
这些规则可以以例子的形式被更直观的理解。考虑4个验证者,A,B,C,D,假设有一个第R轮关于blockX的提议。现在假设blockX已经有一个波尔卡,但是A看不见它,预提交(pre-commit)为空,然而其他人对blockX进行了预提交。进一步假设只有D看见了所有的预提交,然而其他人并没有看见D的预提交(他们只看见他们的预提交和A的空预提交)。D现在将要提交(commit)这个区块,然而其他人进入到R+1轮。由于任何验证者都可能是新的提议者,如果他们提议并投票了一个新的区块blockY,他们可能提交这个区块。可是D已经提交了bockX,因此损害了系统的安全性。注意,这里并没有任何拜占庭行为,仅仅是不同时性。

锁定解决了这个问题通过强迫验证者粘附在他们预提交(pre-commit)的区块上,因为其他的验证者可能居于这个预提交进行了提交(如上例中的D)。本质上,在任何一个节点一旦存在超过2/3预提交(pre-commit),整个网络被锁定在这个区块上,也就是说在下一轮中无法产生一个不同块的波尔卡。这是预投票锁的直接动机。
当然这里必须有相应的解锁方式。假设在某一轮中,A和B预提交(pre-commit)了blockX,与此同时C和D的预提交为空。因此所有的验证者进入到下一轮,预提议(pre-vote)blockY。假设A是拜占庭,为blockY也进行了预投票(不考虑其被锁在blockX上),导致了一个波尔卡。假设B并没有看见这个波尔卡,预提交为空,此时A下线,C,D预提交bolckY。他们进入到下一轮,但是B仍然被锁定在blockX上,C和D被锁定在blockY上。这时因为A下线了,他们将永远得不到一个波尔卡。因此即使在拜占庭节点少于1/3的情况下,这里网络的正常运转仍然受到了影响。



解锁的条件是1个波尔卡。一旦B看见了blockY的波尔卡(用来为C和D的关于blockY的预提交背书),他应当能够解锁并预提交(pre-commit)blockY。这是波尔卡解锁的动机,其允许验证者在看见更高轮数波尔卡的时候解锁并且提交对应的新区块。
二、地址
在DPR Chain中有3类地址:节点地址、用户地址、智能合约地址。
节点地址和用户地址采用 secp256k1椭圆曲线算法生成公私钥对,其中节点地址是公钥进行sha256哈希后再进行RIPEMD160算法得到,用户地址则是在节点地址的算法基础上,再通过Bech32算法加上“dpr”地址前缀而得到。节点地址可以转换成用户地址进行资产转移,因为节点地址和用户地址底层的公私钥算法是采用的相同secp256k1椭圆曲线算法。
智能合约地址是通过合约创建者的地址拼接上该地址的nonce,对其进行sha256哈希后进行RIPEMD160算法,再通过Bech32算法加上“dpr”地址前缀得到。该地址不包含私钥,不能通过私钥签名的方式转移该地址上的资产。
地址在存储层均采用16进制编码的byte数组表示,算法同节点地址生成算法。
三、账户
1. 账户模型
DPR Chain账户模型采用余额模型,余额代表了账户中的Gas数量。DPR Chain的账户分为外部账户和合约账户。
外部账户是具有私钥的账户,此类账户谁有私钥,谁就有对该账户的控制权,进而主动发起交易请求。
合约账户是没有私钥的账户,账户地址通过交易发起者地址及其nonce生成,不含公私钥对,即不能通过公私钥对来控制账户。该账户不能主动发起交易,只能接收到外部账户调用后才能发起交易或者调用其他合约账户。
- Address是该账户对应的地址
- Nonce是该账户的地址生成次数,用于生成合约地址及生成DP的标识ID。每生成一次后该数字累加1。
- CodeHash只有当账户为合约账户时才有值,是合约源代码的sha256后的值。
四、区块

五、交易
1. 交易结构
- Message,是交易的具体内容,是protobuf Any类型,不同类型的交易最大的区别就是Message的结构不同。
- TimeoutHeight,代表该笔交易执行失效的最高区块高度(TimeoutHeight块仍可以打包该交易),当区块链到达该高度而该交易仍未执行时,以后的区块也不会再成功执行该笔交易。
- Memo,是用户发起该笔交易的备注,便于用户处理自己特有的业务逻辑。
- GasPrice,指该笔交易的Gas手续费价格,在DPR Chain中该字段为保留字段,用户无需手动设置,即该值为固定值。
- GasLimit,代表该笔交易最大可花费的Gas数量,当交易所需花费的Gas数量大于该值时,交易会执行失败。

六、节点
七、P2P
八、存储
- application.db:应用数据
- blockstore.db:区块数据
- cs.wal:预写日志数据
- evidence.db:验证节点作恶证明数据
- peerstore.db:节点数据
- state.db:区块链状态数据
- tx_index.db:交易索引数据

九、Gas
- 创世文件中配置有Gas的元数据信息。
- Admin可以通过交易的形式修改部分元数据信息:转账开关和白名单。
- Admin可以通过交易的形式修改交易的基础Gas和每个字节对应的Gas单价。
- 打包交易时收取交易发起人的Gas,并由出块节点收取。
- Gas转账及Gas授权转账功能。
- 查询账户的Gas余额。

十、DP
- DpId,DP的标识ID
- Creator,DP的创建者地址
- Owner,DP的拥有者地址
- DpType,DP的类型
- Name,DP的名称
- DpHash,DP的内容hash
- Url,DP的URL
- Extend,DP扩展信息
- ProtocolType,DP的协议信息,目前包含DPR721
- Symbol,DP的代码
- TotalSupply,发行总数量
- TransferStatus,交易状态
十一、DPR721
- DpId,DP的标识ID
- DprId,DPR的标识ID,由链根据DPR信息生成
- BuesinessNo,DPR的业务编号
- DprOwner,DPR的拥有者地址
- RightType,DPR权利类型
- ExpireTime,DPR过期时间
- Status,DPR状态:0-销毁、1-正常
- DprId链上不存在,则直接铸造DPR,并将Status设置为1。
- DprId链上已存在且Status=1,则该笔交易上链失败。
- DprId链上已存在且Status=0,则将该DPR的DprOwner设置成交易中的DprOwner同时将Status设置为1,即DPR721销毁后可以重新铸造,且重新铸造时可以修改原有的拥有者地址。
- From,交易发起者,From地址必须是DPR721拥有者地址。
- To,DPR721接收者地址
- DprId,要转移的DPR标识ID
十二、智能合约
- 任何人都可以在DPR Chain上开发智能合约,这些智能合约的代码存储于DPR Chain的合约账户中。
- DPR Chain的智能合约程序运行在DPR Chain虚拟机上(兼容EVM虚拟机)。
- 合约账户不能主动运行智能合约,要运行一个智能合约,必须由外部账户发起交易,从而启动合约账户中的代码来执行。
- DPR Chain中运行智能合约需要消耗Gas。
十三、权限管理
- Admin可以控制共识节点的加入、踢出、修改共识节点的共识权重。
- Admin可以修改Gas的转账开关
- Admin可以修改Gas转账白名单
十四、区块链接口
- 查询当前连接的节点信息接口
- 广播交易接口
- 查询最新区块接口
- 根据高度查询区块接口
- 根据交易Hash查询交易接口
- 查询交易事件接口