此外,如果交易中对一个key改写多次,则只保留最后的修改值。如果交易中读取一个key的值,即使交易在读取之前更新了该key的值,读取到的也会是之前提交过的而不是刚更新的。换句话说,不能读取到同一交易中修改的值。

如前所述,key的version只记录在read setwrite set只包含key及对应新value。

对于read set的version的实现有很多种方案,最基本要求就是为key生成一个非重复标识符。例如用递增的序号作为version。在目前的代码实现中我们使用了blockchain height作为version方案,就是用交易的height作为该交易所修改的key的version。交易height由一个结构表示(见下面Height struc),其中TxNum表示这个tx在block中的height(译注:也就是交易在区块中的顺序)。该方案相较于递增序号有很多优点—主要是这样的version可以很好地利用到诸如statedb、交易模拟和校验这些模块中。

此外,如果模拟交易中执行了批量查询(range query),批量查询结果会被放到read-write set中的query-info

下面是一个假设的交易模拟生成的read-write set示例,简单起见,示例中使用了递增序号作为version。

在验证阶段,如果read set中每个key的version都与stateDB中对应worldState(假设所有之前的有效交易,包括同一个block中的交易,都已经提交完成,即已更新ledger)的version相匹配,则认为此交易有效。

如果read-write set中包含query-info,则还要对此执行额外的校验。该校验确保在此批量查询的结果范围内没有key被增删改。换句话说,如果在验证阶段重新执行该批量查询(模拟期间执行的交易)应该产生与模拟交易期间相同的结果。此校验确保交易在提交时出现幻读会被认为无效。注意,这个幻读保护仅限于Chaincode的GetStateByRangeGetStateByPartialCompositeKey两个方法(译注:此处文档上提到的是和GetQueryResult两个方法,但在代码里的注释却不是这样,此处以代码为准。详见fabric/examples/chaincode/go/marbles02/marbles_chaincode.go)。而其他批量查询方法(如:GetQueryResult)会有幻读风险,因此这种查询应该只用于不会被提交到ordering的只读交易,除非app能保证交易模拟和交易验证提交两阶段之间结果集稳定。

如果交易验证通过,committer就会用write set更新worldState。在更新阶段,write set中的每个key在worldState中对应的value都会被更新,然后worldState中这些key的version也会随着更新。

本节通过示例场景帮助理解read-write set。存在一个key设为k,在worldState中由元组(k,var,val)表示,其中verk的最新的version,valk的value。

现在有五个交易,分别是T1,T2,T3,T4,T5,这五个交易的模拟过程是针对相同的worldSate快照,下面的代码片段显示了模拟交易的worldState快照以及每个交易执行读写的顺序。

  1. T1验证成功,因为它没有read操作。之后在worldState中的和k2会被更新成(k1,2,v1'), (k2,2,v2')
  2. T3验证成功,因为它没有read操作。之后在worldState中的k2会被更新成(k2,3,v2'')
  3. T4验证失败,因为它读取的k2在之前的交易T1中被修改了
  4. T5验证成功,因为它读取的k5没有在之前的任何交易中修改

译注:

原文示例交易需要进一步阐述

  1. 值得注意的是,T1…T5在同一个区块和不同区块的处理方式不同。

    • 如果读取的key在此区块前面的交易中已经有update,则直接置此交易为失效
    • 如果读取的key在本区块前面的交易没有做update,则需要判断state中的版本和commit的版本是否一致,如果不一致,则置为失效交易

    为了尽量避免失效交易,application和chaincode需要进行精心设计,避免同一个资产交易信息尝试在一个区块上进行update操作。如果避免不到,可以适度重试交易。