本文译自Fabric 1.0 文档,这篇文档详述了当前读写集语义实现的细节。文档地址为:
https://hyperledger-fabric.readthedocs.io/en/latest/readwrite.html
交易模拟和读写集
在一个背书人上进行交易模拟期间,会为交易准备一个读写数据集。读集包含一份唯一键的列表和该键的提交版本,用于交易模拟期间读取。写集包含一份唯一键的列表(键值可能与读集有重叠)和交易写入的对应的新键值。如果交易更新是删除该键,那么会对该键做一个删除标记。
进一步的,如果交易对一个键对应的值进行多次写操作,该键只会保留最后一次写入的值。同样,如果交易读取一个键对应的值,会返回处于已提交状态的值。即使在进行读取操作之前该交易已经更新了该键的值。换句话说就是,不支持在交易中读取该交易写入的数据。
正如前面提到的,键的版本只被记录在读集中。写集仅包含唯一键的列表和交易设置的键的最新值。
有很多方案可以实现版本控制,最简单的方案是给每一个键生成一个独一无二的标识符。例如:用单调递增数字代表版本号就是这样一种方案。在Fabric 1.0 中,我们基于版本控制,使用了正在提交的交易所在的区块链高度作为所有被交易改变的键的最新版本。在此方案中,一笔交易的高度使用一个元组表示(txNumber 是该交易所在区块的高度)。这种方式相比于单调递增数字的方案有很多优势,首要的就是它让状态数据库、交易模拟和验证等组件进行有效的设计选择成为可能。
下面是对读写集例子的说明,为了简便,这里使用单调递增数字代表版本:
另外,如果交易在模拟中进行了range查询操作,查询操作和其结果都将被添加到读写集作为query-info。
交易验证和使用读写集更新世界状态
提交者使用读写集的读集部分校验一笔交易的有效性,使用写集更新相关键的版本和键值。
在验证阶段,如果一笔交易中每个读集中的键与世界状态中对应键的版本号一致,那么该笔交易被认为是有效的–假设先前的有效交易都已经被提交了,包括同一区块中的先前交易。但是如果读写集中包含了一个或者多个query-info,就需要执行额外的验证。
额外的验证要确保在先前的query-info中的结果没有键被插入、删除或者更新。换句话说,如果我们在验证时基于已提交状态重复执行range查询操作,应该得到和我们在交易模拟时观察到的一样的结果。这种验证确保如果一笔交易在提交时观察到幽灵条目,该笔交易应该被标记为无效。注意虚假保护仅限于range查询(比如链码中的GetStateByRange函数),还没有在其他查询操作中实现。其他查询操作对幽灵有风险,所以应该被用于只读的并不提交到ordering的交易,除非应用可以保证模拟和验证/提交时的结果集的稳定性。
如果一笔交易通过了有效性验证,提交者会使用写集更新世界状态。在更新阶段,写集中每个键所对应的世界状态中的该键的值都会被设置为写集中确定的值。并且,世界状态中的键的版本也更新到最新版本。
模拟和验证例子
这节通过小例子来帮助理解读写集语义。为此,键k在世界状态中被表示为(k,ver,val),其中ver是最新版本,val是它的值。
现在假设有T1,T2,T3,T4和T5共5笔交易,所有的模拟都基于同一个世界状态的快照。下面的片段展示了世界状态的快照以及交易模拟和每个交易模拟的一系列读写操作:
现在假定这些交易被排序为T1到T5依次执行:
1、T1通过验证,因为它并没有任何读操作。并且世界状态中k1和k2键的值别更新为(k1,2,v1’), (k2,2,v2’)。
2、T2不能通过验证,因为它读取了k1键的值,该值在T1中被修改了。
3、T3通过验证,因为它并没有任何读操作。并且世界状态中k2的值被更新为(k2,3,v2”)。
4、T4不能通过验证,因为它读取了k2键的值,该值在T1中被修改了。
5、T5通过验证,虽然读取了k5的值,但是k5的值之前交易并未修改过。
注意:目前不支持带有多重读写集的交易。
转自:https://blog.csdn.net/lengconglin/article/details/76863195