HyperLedger的共识之交易背书的基本流程(二)

作者: Elli Androulaki, Christian Cachin, Angelo De Caro, Konstantinos Christidis, Chet Murthy, Binh Nguyen, Alessandro Sorniotti, and Marko Vukolić

翻译:梧桐树

原文:https://github.com/hyperledger/fabric/blob/master/proposals/r1/Next-Consensus-Architecture-Proposal.md

著权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文约4300字+,阅读(观看)需要24分钟

下面我们概要性的介绍一个交易的高层请求流程。

备注:注意后面的协议并不假定所有的交易都是确定性的,允许不确定性的交易。

2.1. 客户端创建一个交易并发送给自己选择的一个请求节点

要调用一个交易,客户端发送如下的消息给请求节点spID。

,其中:

  • tx=,其中:

    • clientID是提交客户端的ID,

    • chaincodeID指的是交易所属的链码,

    • txPayload是发出的交易本身的有效载荷,

    • clientSig是客户端tx消息中其他项的签名。

  • retryFlag是一个布尔值,告诉请求节点万一交易失败了要不要重传,

调用交易和部署交易的txPayload是不一样的(即,调用交易会引用一个部署相关的系统链码)。如果是调用交易,txPayload只有一个项:

  • invocation = ,其中:

    • operation代表链码的操作(函数)和参数,

    • metadata代表调用相关的属性。

如果是部署交易,会有两个项:

  • chainCode = ,其中:

    • source代表链码的源码路径,

    • metadata代表链码和应用相关的属性。

  • policies包含了所有Peer节点都能访问的链码策略,比如背书策略。

待办:确定是否要在客户端显示的包含本地/逻辑时间(时间戳)。

2.2. 请求节点准备一个交易并发送给背书者获取背书

请求节点收到客户端发来的消息后,首先要验证客户端的签名clientSig,然后就准备交易。请求节点会临时的执行交易(txPayload),过程是通过执行交易关联的链码(chaincodeID)和拷贝请求节点本地保存的状态。

执行的结果是,请求节点会计算状态更新(stateUpdate)和版本依赖(verDep),这在DB语言中又叫MVCC+postimage。

还记得状态是键值对(k/v)组成的吧。所有的k/v条目都是版本化的,就是说,每个条目都包含排过序的版本信息,每个键更新的时候都会递增版本信息。Peer节点解析链码访问交易记录的所有键值对,可以读可以写,它还没有更新它的状态。更具体的说:

  • verDep是一个元组verDep=(readset,writeset)。给定请求节点执行交易前的一个状态s:

    • 对交易读取的每个键k,把(k,s(k).version)加入到readset中。

    • 对交易修改的每个键k,把(k,s(k).version)加入到writeset中。

  • 另外,对交易修改的每个键k的新值v',把(k,v')加入到stateUpdate中。v'也可以是新值相对旧值s(k).value的增量。

实现时可以把verDep.writeset和stateUpdate放到同一个数据结构中。

然后,tran-proposal := (spID,chaincodeID,txContentBlob,stateUpdate,verDep),其中:txContentBlob是链码/交易相关的信息,目的是能标识tx(比如,txContentBlob=tx.txPayload)。更详细的信息第6部分会介绍。

所有节点都会用tran-proposal的加密哈希来作唯一交易标识符tid(即:tid=HASH(tran-proposal))。

然后请求节点就把交易(tran-proposal)发送给链码关联的背书者。背书节点是根据解析策略,Peer节点的可用性和与请求节点的连通性来选择的。比如,可以把交易发送给指定chaincodeID所有的背书节点。有可能,有的背书节点是离线的,还有一些会拒绝对交易进行背书。请求节点尽量用可用的背书节点满足策略要求。

请求节点spID给背书节点epID发送的交易消息是:

潜在优化:实现上可以优化下tx.chaincodeID和tran-proposal.chaincodeID里重复的chaincodeID,tx.txPayload和tran-proposal.txContentBlob里重复的txPayload。

最后,请求节点在内存中保存tran-proposal和tid,等待背书节点的回复。

其他设计:这里请求节点和背书节点是直接通信的。这可以是共识服务的一个功能,在这种情况下,需要确定faric需不需要对它们的通信采用原子广播交付保证,还是用简单的p2p通信。这时共识服务也要负责根据策略收集背书再发送给请求节点。

待办:需要确定请求节点和背书节点之间的通信:用p2p还是通过共识服务。

2.3. 背书节点接收交易并给交易背书

链码tran-proposal.chaincodeID对应的背书节点收到通过PROPOSE消息发送来的交易后,执行如下步骤:

  • 背书节点验证签名tx.clientSig,检查tx.chaincodeID==tran-proposal.chaincodeID是否相等。

  • 背书节点模拟交易(用tx.txPayload),验证状态更新和依赖信息都是正确的。如果所有的都是有效的,它就对(TRANSACTION-VALID, tid)进行数字签名,生成epSig。然后背书节点发送消息给请求节点(tran-proposal.spID)。

  • 如果背书者模拟交易是失败了,有几种情况:

    a. 如果背书者获取到的状态更新和tran-proposal.stateUpdates里的不一样,它就对(TRANSACTION-INVALID, tid, INCORRECT_STATE)进行签名并发送给请求节点。

    b. 如果背书者发现了比tran-proposal.verDeps更新的数据版本,它就对(TRANSACTION-INVALID, tid, STALE_VERSION)进行签名并发送给请求节点。

    c. 如果背书者因为其他的一些原因(内部的背书策略、交易错误等)不想对交易进行背书,它就对(TRANSACTION-INVALID, tid, REJECTED)进行签名并发送给请求节点。

注意背书者在这一步还没有改变状态,状态的更新也不会写日志。

其他设计:对无效交易,背书节点可以不通知请求节点,不用显示的发送TRANSACTION-INVALID通知。

其他设计:背书节点把TRANSACTION-VALID/TRANSACTION-INVALID消息及其签名发送给共识服务。

待办:确定采用上面的哪种设计。

2.4. 请求节点收集交易的背书并通过共识服务广播出去

请求节点会一直等待,直到接收的消息和对(TRANSACTION-VALID, tid)进行的签名,足够判断这个交易提议(transaction proposal)是背书过(可能包含它自己的签名)的。这个过程依赖背书策略(再看看第3部分)。如果满足背书策略,交易就是背书过了,注意这会儿它还没有提交(committed)。从背书节点收集到的能确定交易是背书过的签名就叫背书(endorsement),请求节点把它们存储到endorsement里。

如果请求节点没有收集到交易提议的背书,它就丢弃掉这个交易并通知提交客户端。如果提交客户端设置(看步骤1和SUBMIT消息)了retryFlag,请求节点可能(根据请求节点的策略)会对交易进行重试(步骤2)。

有了有效背书的交易,我们开始使用fabric的共识服务。请求节点使用broadcast(blob)调用共识服务,其中blob=(tran-proposal, endorsement)。

2.5. 共识服务发布交易给Peer节点

当出现一个deliver(seqno, prevhash, blob)事件,一个Peer节点更新所有序号小于seqno消息的状态,过程是这样的:

  • Peer节点根据链码(blob.tran-proposal.chaincodeID)的策略检查blob.endorsement是否有效(这个步骤可以不用等到更新序号小于seqno的状态这个时候)。

  • Peer节点同时验证依赖blob.tran-proposal.verDep是有效的。

根据状态更新选择的一致性内容(consistency property)或者“隔离保证(isolation guarantee)”不同,依赖验证有多种实现方式。比如,可串行性(serializability)可以要求每个readset和writeset里键对应的版本号必须和状态里面键的版本号相同,并抛弃掉不能满足这个要求的交易。另外一个例子,快照隔离(snapshot isolation )要求writeset里所有的键,状态里的键和依赖数据里的版本号都是一样的。数据库著作里包含了更多的隔离保证。

待办:确定坚持可串行性还是允许链码指定隔离级别。

  • 如果所有的检查都通过了,这个交易就被认为是有效的(valid)或者提交的(committed)。这就是说,一个Peer节点添加一个交易到总账上,然后会在区块链状态上执行blob.tran-proposal.stateUpdates。只有提交的交易才会修改状态。

  • 如果有任何检查失败了,交易就是无效的,Peer节点会丢弃这个交易。重要的是要注意无效的交易是没有提交的,不会修改状态,也不会被记录。

另外,请求节点会通知客户端丢弃的交易。如果提交客户端设置(看步骤1和SUBMIT消息)了retryFlag,请求节点可能(根据请求节点的策略)会对交易进行重试(步骤2)。图1. 交易流程图解(通用情况路径)

文章发布只为分享区块链技术内容,版权归原作者所有,观点仅代表作者本人,绝不代表区块链兄弟赞同其观点或证实其描述。

本文由 区块链技术网 作者:作者: Elli Androulaki, Christian Cachin, Angelo De Caro, Konstantinos Christidis, Chet Murthy, Binh Nguyen, Alessandro Sorniotti, and Marko Vukolić 发表,其版权均为 区块链技术网 所有,文章内容系作者个人观点,不代表 区块链技术网 对观点赞同或支持。如需转载,请注明文章来源。