作者:fisco-dev
弹性联盟链共识框架是为了弥补pbft/raft的共识仅对对等节点生效,而无法根据业务逻辑做出一些特化的缺陷所做出的措施。
因为在纯正的pbft/raft的共识模型中,首先就假设每个节点都是平等的,即没有节点具有特殊的业务性质。在联盟链中这样的性质有时并不能满足一些业务场景的要求,故而需要一套在正常的共识流程之外(保证正常共识)继续对共识流程做出限制的规则。这就是弹性联盟链共识框架方案开发的初始原因。
例如如下场景:
在一个10(3f+1)个节点组件的联盟链场景中,使用pbft共识算法进行共识。其中10个节点分别由A,B,C三个机构分别持有,A机构有4个节点,B机构有3个节点,C机构有3个节点。根据pbft的性质,达成共识只需要7个节点就足够了(2f+1),因为pbft对于所有节点都是平等的,所以只需要收够10个节点中的任意7个即可。如果一次投票中的7个节点全部都是A机构和B机构的节点,那么就相当于C机构没有参加共识但是共识仍然还是通过了。所以在这样的情况下就需要额外的规则来限制共识条件,以满足业务层面上的共识要求。
注:这是一个危险的功能,请在正确认识其原理的情况下使用,否则可能轻易导致共识无法运行下去造成链的作废!请一定仔细阅读《多机构弹性共识方案设计》章节及这个章节中的《4.其他注意事项》章节中的内容。
在现有的方案探究及对区块链共识本质的理解下,我们划分对“共识”的界限,认为共识分为以下两类:
这篇文章中的描述,认为
区块链中其实存在两个独立层面的共识,其一可称为“输入共识”,其含义是各节点对指令的顺序及内容达成共识,类似传统数据通信的会话层,不牵涉业务操作;其二可称为“输出共识”,其含义是业务系统受输入的驱动,状态不断发生跃迁,同时产生一系列输出,此时各参与方对业务系统进入的状态及产生的输出达成共识 。此非孤明之见,Corda 区分交易的uniqueness和validity二者[17], Polkadot区分cononicality与validity二者,都应是有见于此。 虽然这两层共识在设计上可以分离,但不同区块链对此的回应差别很大,将其说为区块链设计时面临的一大抉择也不为过。总的来说有三种方式:
第一种方式是只提供输入共识,此以Factom[18]项目为例。Factom只对数据内容和顺序进行共识,把对数据的后续检验及处理交给其他应用程序完成。由于不做输出共识,这种区块链很可能不内置智能合约功能。
第二种方式是以紧耦合的方式同时提供输入共识和输出共识,典型案例如以太坊。以太坊的区块头中不仅包含交易根,也包含状态根,通过一套机制同时达成对交易和终态的共识。
第三种方式是以分离的方式同时提供输入共识和输出共识,典型案例如Fabric 1.0[19]的设计。在Fabric 1.0中,输入共识在Orderer之间达成,Orderer只看到数据,并不理解任何业务含义,输出共识则通过Endorsor、Committer和应用层CheckPoint在一定程度上实现。
因现有的模型继承于以太坊体系,我们认为事实上这里的“输入共识”和“业务共识”都归属于共识框架层面的“数据共识”,表示对于相同的一笔交易,对于这笔交易及其执行的结果,应该在所有的副本上都是一致的,并通过共识算法保证这个要求成立。其中:
在这两个条件下,所谓“作恶”的可能性为:
现有的共识算法下:
以上,我们归结为“数据共识”
注意这里的业务共识不是上一章中引用内容中的“业务共识”的概念。这里的业务共识专门指代: 在业务层面上对数据共识 结果的数据 进行解读的共识
以存证为例:假设有3家机构,3家机构按照存证的业务模式进行协调
以对用户信用评分为例:假设有3个公司联合对用户信用评级,评级方式是以每个用户为一个合约,合约中有一个数组,每家公司向数组中写入对改用户的分数,最后大家取平均分作为用户评分。
以上,我们归结为“业务共识”
在定义了业务共识的基础上,我们引入弹性联盟链共识框架方案,即在现有的共识上添加可以由用户编写的规则来限制共识是否成功。
目前这个功能只用于PBFT,RAFT还未支持。
注:弹性联盟链共识框架方案是在基础共识的模型上新添加的规则,也就是说需要先满足基础的共识,再满足共识框架规定的规则。
在联盟链的语境下,我们一般认为:联盟链的节点是由各个参与区块链的**“机构”来控制,在物理上认为节点只是组成区块链的组件,在逻辑上认为参与区块链的应该是机构**。
所以我们认为参与业务共识的基本元素应该为机构和这个机构的个数(也就是不再以节点为共识判定的元素),这样的模型可以覆盖比较大量的场景。
等等类似的场景。以上的场景都可以编写为“规则”来加强现有的共识体系。由于在不同的场景下需要不同的规则,所以我们需要提供一个框架来让用户可以根据自己业务的需要来部署不同的规则以达到目的。
如图所示,以PBFT为例,在执行完pbft的最后阶段时,我们将收集到的签名作为参数来调用一个特殊的系统合约进行“规则”的判定。当判定成功时,表示满足规则,判定失败时,表示收集到的签名还未满足。若还会继续收到共识的包那么会继续进行判定,若是共识包都发送完全,但是仍然未满足条件,那么这个块是无法完成共识阶段,也无法落地的。
举例:
若4个节点分别归属于4个机构,然后规则现在设定为必须有A机构的签名才算共识完成。那么比如一个节点收到了除A机构的另两个节点的共识包的时候,按照pbft的模型,加上自己的一票总共3票的时候就已经共识完成了。但是由于弹性联盟链共识框架的限制,此时会判定当前的共识没有完成,需要继续收到A机构的共识包才可判定共识完成。
这个功能的添加是通过系统合约来实现。
机构的名字指定是在节点注册
babel-node tool.js NodeAction registerNode xxx.json
中 xxx.json 中的 Agencyinfo
字段,当这个字段一致时,标识这两个节点为同一个机构。
若是在已有的链上需要启用这个功能,那么需要重新部署系统合约(或重新部署NodeAction.sol合约替换现有的节点管理合约并执行setSystemAddr()
将系统合约设置进去),之后重新注册节点进入系统,然后再部署规则(见后文)。
只需要按照文档正常部署系统合约即可,在systemcontractv2
目录下运行:
bable-node deploy.js
所有和该功能相关的合约与脚本工具都位于systemcontractv2
目录下
用户需要根据自己的需要,编写自己的规则。编写规则的文件为在systemcontractv2
目录下的ConsensusControl.sol
文件中的函数:
ConsensusControl.sol:
pragma solidity ^0.4.11;
import "ConsensusControlAction.sol";
contract ConsensusControl is ConsensusControlAction {
// 构造函数一定需要存在
function ConsensusControl (address systemAddr) ConsensusControlAction (systemAddr) {
}
// 用户编写自己规则的函数,true代表满足规则,false代表不满足规则,参数 bytes32[] info 代表已经收到的机构的列表, uint[] num 代表已经收到的共识包中这些机构分别的个数(与info是对应关系)
function control(bytes32[] info, uint[] num) external constant returns (bool) {
return true;
}
// 初始化时的回调
function init(address systemAddr) internal {}
// NodeAction 加入节点的回调,true代表同意这个节点加入,false代表拒绝这个节点加入
// babel-node tool.js NodeAction registerNode xxxx.json
function beforeAdd(bytes32 agency) internal returns (bool) {
return true;
}
// NodeAction 退出节点的回调,true代表同意这个节点退出,false代表拒绝这个节点退出
// babel-node tool.js NodeAction cancelNode xxxx.json
function beforeDel(bytes32 agency) internal returns (bool) {
return true;
}
}
用户通过编写填充这个合约中的函数来达成自己目的。
我们以 4 个节点构成链举例,其中 A 机构由2个节点,B机构1个节点,C机构1个节点。
在这个框架中:
ConsensusControlAction
会提供两个成员变量 bytes32[] public agencyList
和 mapping (bytes32 => uint) public agencyCountMap
分别代表当前系统中应该具备的机构列表agencyList
和这些机构应该有的数量。所以agencyList=['A', 'B', 'C']
, agencyCountMap={'A':2, 'B':1, 'C':1}
control
函数代表规则,用户通过编写这个规则来根据输入参数判定true或false来决定是否满足自己的业务共识规则,参数 bytes32[] info
代表已经收到的机构的列表, uint[] num
代表已经收到的共识包中这些机构分别的个数(与info是对应关系)。比如当前触发这次判定的时候,收到了3个共识包,其中2个是A机构的,1个是B机构的,那么 info=['A','B']
, num=[2,1]
init
代表刚部署该合约时触发的回调beforeAdd
代表注册新节点时,可以通过这个函数处理是否可以让这个节点加入,比如需要限制每个机构的最大数量beforeDel
代表节点退出时,可以通过这个函数处理是否可以让这个节点退出,比如规则为某个机构必须在时才能通过,那么这个函数可以限制这个机构至少要有一个在区块链中才行,否则这条链就无法共识了。control init beforeAdd beforeDel
这几个函数,并重新部署。按照上述介绍,我们列举两种可能的场景的编写规则方式:
pragma solidity ^0.4.11;
import "ConsensusControlAction.sol";
/**
* 要求所有机构都有签名
*/
contract ConsensusControl is ConsensusControlAction {
function ConsensusControl (address systemAddr) ConsensusControlAction (systemAddr) {
}
function control(bytes32[] info, uint[] num) external constant returns (bool) {
uint count = 0;
for(uint i=0; i < info.length; i++) {
//agencyCountMap[info[i]] != 0 存在这个机构
// num[i] != 0 这个机构传进来个数大于0
if (agencyCountMap[info[i]] != 0 && num[i] != 0) {
count += 1;
}
}
if (count < agencyList.length)
return false;
else
return true;
}
// init beforeAdd beforeDel 在这里不起作用,所以不进行覆写
}
如指定的机构叫做 AgencyA
pragma solidity ^0.4.11;
import "ConsensusControlAction.sol";
/**
* 要求 AgencyA 必须有签名
*/
contract ConsensusControl is ConsensusControlAction {
string private agencyName = "AgencyA";
function ConsensusControl (address systemAddr) ConsensusControlAction (systemAddr) {
}
function control(bytes32[] info, uint[] num) external constant returns (bool) {
bool isexisted = false;
for(uint i=0; i < info.length; i++) {
if (info[i] == stringToBytes32(agencyName) && num[i] > 0) {
isexisted = true;
break;
}
}
return isexisted;
}
// 使用 beforeDel 限制 AgencyA 的节点退出
function beforeDel(bytes32 agency) internal returns (bool) {
// reject del the last "AgencyA" node
if (agency == stringToBytes32(agencyName)){
var count = agencyCountMap[agency];
if (count - 1 <= 0) {
return false;
}
}
return true;
}
}
我们提供了 ConsensusControlTool.js
工具来控制规则,改脚本有3个指令'deploy', 'turnoff' 和 'list'
当经过1步我们指定好或修改好规则时候,执行
babel-node ConsensusControlTool.js deploy
可以把1中的规则合约进行部署。若是想替换规则也同样执行这个指令
当我们需要关闭共识规则时,执行
babel-node ConsensusControlTool.js turnoff
当我们需要列出当前系统中的机构列表及机构数目时,执行
babel-node ConsensusControlTool.js list
这个模块相关的日志全部以 [ConsensusControl]
作为开头。其中在合约中也可以编写日志输出(详细使用见合约日志输出相关文章),如:
pragma solidity ^0.4.11;
import "ConsensusControlAction.sol";
import "LibEthLog.sol";
contract ConsensusControl is ConsensusControlAction {
using LibEthLog for *;
function ConsensusControl (address systemAddr) ConsensusControlAction (systemAddr) {
LibEthLog.DEBUG().append("[ConsensusControl] ######### current agency list ######## length:").append(agencyList.length).commit();
// for (uint i=0; i < agencyList.length; i++) {
// LibEthLog.DEBUG().append("[ConsensusControl]").append(i)
// .append(":")
// .append(bytes32ToString(agencyList[i]))
// .append(":")
// .append(agencyCountMap[agencyList[i]])
// .commit();
// }
// LibEthLog.DEBUG().append("[ConsensusControl] ######### end ########:").commit();
}
}
即可输出debug日志。
bytes32
的限制(solidity的限制)该功能的添加不会影响到已有的功能,与已有的数据相兼容,所以当编译出新的执行文件的时候,直接替换已有的执行文件重启后即可。但是这样不会具备弹性联盟链共识框架这个功能,只是保证不会影响其他功能。
若希望在已有的链上启用这个新的功能,则要求重新部署系统合约,或至少重新部署 NodeAction.sol
及 ConsensusControlMgr.sol
,并将这两个合约重新注册到已有的系统合约中,之后才能使用 ConsensusControlTool.js
工具
也就是说在已有的链中替换了新版本的执行文件并希望启用该功能,需要:
NodeAction.sol
之外的原来的系统合约所管理的合约组件重新注册到新的系统合约中(首先先获取原系统合约管理组件的地址及对应关系,并参考 systemcontractv2/deploy.js 文件的写法重新注册到新系统合约中),之后重新按照原来添加节点的方式重新添加节点恢复到原来的连接状态NodeAction.sol
及 ConsensusControlMgr.sol
(参考 systemcontractv2/deploy.js 文件),并将这两个合约重新注册到已有的系统合约中,并重新按照原来添加节点的方式重新添加节点恢复到原来的连接状态此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。