Hyperledger Fabric原理详解与实战10

2,154 阅读6分钟

本文主要内容如下,爱看不看~

再述交易过程

这次,我们再把交易过程说得更细致和深入一些。

交易时序图
交易时序图

交易提案

第1步,交易提案。前面我们称呼为“交易请求”,但官方的叫法是transaction proposal

交易提案由客户端发起,它包含了一个请求:要求调用某个链码函数,用来查询或更新数字资产。

同时,也包含了函数的参数,还有发起者本人的签名。

举个例子:账户A向账户B转账50,账户A当前状态数据库中的值为:

{
    "accountA":{
        "value":{
            "balance":100
        },
        "version":1
    }
}

账户B当前状态数据库中的值为:

{
    "accountB":{
        "value":{
            "balance":10
        },
        "version":1
    }
}

注意到,accountX这个key有一个属性version,这个后面有大用。当然,这里只是示例,实际格式并不是这样的。

另外,注意到,客户端是给两个背书节点都发送了请求,这是为什么?

背书策略

客户端需要向哪些peer发送交易提案是由背书策略(Endorsement policy)决定的。

每个链码都会设置一条背书策略(或者采用Channel的默认设置)。

例如:AND(MCorp.peer, DB.peer),意思是说,每一次调用该链码,必须经过MCorp组织中任意背书节点 DB组织中任意背书节点的执行。否则,不满足策略,执行结果无效,后续流程中将无法通过验证。

交易时序图中示意的就是这条策略。

模拟交易

第2步,模拟交易(simulate the transaction)。前文我们称之为执行交易(execute the transaction),官方文档上这两种叫法都有。

在这一步中,主要做了两件事:

  1. 校验提案:格式检查、安全检查(如重放攻击保护)、签名验证、权限验证等。

  2. 执行交易:根据提案中的说明,调用对应的函数获得读写集。

第3步,返回给客户端的交易结果中主要包含两部分:读写集和签名。

签名好理解,谁执行的,谁就把自己的签名加上。

什么是读写集

在交易执行过程中,读取过的键值的集合称为读集;要更新或删除的的键值集合称为写集。

读集中并不包含键的值,而是当前状态的版本号。

例如:

ReadSet = [{"key""accountA""version"1}, {"key""accountB""versin"1}]
// 读集,包含键名和当前状态版本号

WriteSet = [{"key""accountA""value": {"balance"90}}, {"key""accountB""value": {"balance"20}}]
// 写集,包含键名称和更新后的值

结果比对与验证

第4步,客户端收到peer返回的结果后,会做两件事情:

  1. 比对来自不同peer的读写集是否一致。不一致则交易失败。
  2. 确认是否已满足背书策略,即:是否收到足够多的有效的返回结果。如果满足,则可以组合交易结果发送给orderer。

在这一步中,如果仅仅只是读取数据,那么整个流程就可以结束了,读取的结果由客户端自行处理。

如果只读交易也想加入到区块链的话,那就和其他交易一样提交给orderer。

Tips

客户端并不关心peer如何产生读写集,只关心来自不同peer的读写集是否相同。

也就是说,相同的交易请求,peer上运行的链码可以不相同。

比如:peer0.m.com链码是用go编写的,而peer0.db.com的链码是由Java编写的。只要它们能产生相同结果集,这些都不会影响最终交易结果。

发送交易(Broadcasts)

第5步,客户端将收到的执行交易的结果组装成交易消息(transaction message),发送给orderer服务。

交易消息结构如下图T4所示:

  • H4,消息头(Header):交易的一些元数据,如:链码名称和版本等。

  • S4,签名(Signature):客户端的签名。 用于检查交易是否被篡改。

  • P4,交易提案(Proposal):包含要调用的链码函数名和参数信息。

  • R4,响应(Response):交易执行后产生的读写集。

  • E4,背书(Endorsements):peer执行交易后对读写集的签名。根据背书策略,可能有多个。

打包交易

第6步,orderer将交易组装成区块:

区块结构示意图如下图B2所示:

  • H2,区块头(Hearder):包括块序号、当前块Hash值和前一个块的Hash值。

  • D2,区块数据(Data):包含多个交易消息。

  • M2,区块元数据(Metadata):包含块创建者的证书和签名;标识块中每笔交易有效或无效的位图。 与块数据和头字段不同,此部分不参与块哈希计算。

分发区块(Delivered)

第7步,orderer将新区块发送给各个主节点。

第8步,新区块在peer集群中互相传播。

更新区块链

第9步,校验区块,并更新世界状态和区块链。

这一步中,每个peer会做如下操作:

  1. 对新区块进行哈希和签名的验证。

  2. 按顺序读取块中交易,验证交易签名、背书和是否满足背书策略,验证失败则标识为无效交易。如果验证通过,则进行如下操作:

    a. 比对交易中读集key的版本号,如果版本号相同,说明该key值从该交易发起以来没有被修改过,可以对其更新。否则,该交易为无效交易。

    b. 版本号相同的情况下,将对应的key值更新为写集中新的值,并将key的版本号+1。

  3. 完成所有交易的验证和更新操作后,将新区块添加到对应链的尾端,并产生一个事件。

  4. 整个交易完成。

第10步,客户端可选择监听该事件,并做出响应操作。

读写集验证举例

ReadSet = [{"key""accountA""version"1}, {"key""accountB""versin"1}]

WriteSet = [{"key""accountA""value": {"balance"90}}, {"key""accountB""value": {"balance"20}}]
/* 
以上是这次交易的读写集。
如果peer在检查该交易时,”accountA"和"accountB"的version值仍然为1,那么状态数据将会修改为:
 {
    "accountA":{
        "value":{
            "balance":90
        },
        "version":2
    }
}

{
    "accountB":{
        "value":{
            "balance":20
        },
        "version":2
    }
}

如果peer在检查该交易时,”accountA"或"accountB"的version值不为1。
则说明,在该交易发起后,其他交易已经对这两个key值进行过修改。
该交易无效。
*/

NOTE

如果读集为空,那么状态数据会直接被更新为写集中的值。

我是2流程序员,我们下次再贱!

本文使用 mdnice 排版