分布式账本技术通过在不同节点之间达成共识,记录相同的账本数据,这是区块链技术的基础。超级账本采用Endorsement/Consensus模型,模拟执行和区块验证是在不同角色的节点中分开执行的。
模拟执行是并发的,这样可以提高扩展性和吞吐量:
每个Peer节点会维护四个DB,分别为:
超级账本包含以下元素:
账本编号(LedgerID)的数据存储在LevelDB中,记录了有哪些账本以及其全局唯一的编号。
账本数据(Ledger)以二进制文件的格式进行保存,每个账本数据保存在不同的目录下。账本数据的存储并没有使用数据库,而是采用的基于文件的系统。所以,它的所有操作都是通过区块文件管理器(blockfileMgr)实现的。
committer节点负责维护节点本地的账本数据,通过Gossip模块从排序服务接收到区块后,会保存区块并建立区块的索引,然后保存到数据库中。
链码是可以并行执行的,执行过程并不影响当前的状态数据库。其实现方法是在最新账本上生成一个账本数据的模拟器,模拟执行过程生成的数据会写入模拟器的writeMap中,读取的数据会写入到readMap中,然后根据writeMap和readMap生成读写集。由于每次链码执行都会生成一个新的模拟器,多个链码并行执行并不会互相影响,模拟执行的结果也不会影响到当前的状态数据库,因为生成的读写集在committer节点提交交易的时候,只有验证通过以后才会记录到账本中。
在endorse节点模拟执行交易的过程中,会生成读写集(Read-Write Set)。
下面是一个读写集的简单示例,版本号为了描述简单用数字表示:
<TxReadWriteSet>
<NsReadWriteSet name="chaincode1">
<read-set>
read-set>
<write-set>
write-set>
NsReadWriteSet>
<TxReadWriteSet>
committer节点根据读写集中的读集合来验证交易,根据写集合来更新键的版本和值。
在验证阶段,怎么判断交易的合法性呢?
如果交易通过了上面的检查,那么提交节点会根据写集合来更新世界状态(World State),会遍历写集合中的每个键,更新世界状态里对应的键值和版本号。
为了便于理解来看一个例子。假设一个键值对在世界状态里用一个三元组来表示:(key,verion,value)
,也就是键key最新版本version的值是value。现在有5笔交易T1-T5,他们都基于同一个世界状态的快照进行模拟:
World state: (k1,1,v1), (k2,1,v2), (k3,1,v3), (k4,1,v4), (k5,1,v5)
T1 -> Write(k1, v1'), Write(k2, v2')
T2 -> Read(k1), Write(k3, v3')
T3 -> Write(k2, v2'')
T4 -> Write(k2, v2'''), read(k2)
T5 -> Write(k6, v6'), read(k5)
假设按照T1到T5的顺序依次进行排序:
(k1,1,v1), (k2,1,v2)
为(k1,2,v1'), (k2,2,v2')
(k2,3,v2'')
(多次写以最后一次为准)。个人猜测:committer节点接受排序服务的区块和读写集,如果交易验证不通过就不会被记录到区块文件中,做一个验证能够防止“双花”。尽管排序服务会对交易进行一个排序,但是只能保障应用程序提交交易的顺序,并不能保障没有恶意提交交易的存在。
状态数据记录的是交易执行的结果,最新的状态代表了channel上所有键的最新值,所以称为World State。为了提高链码执行的效率,所有键的最新值都存储到状态数据库中。
对于状态数据库本身插件化的设计,目前支持LevelDB和CouchDB。LevelDB和CouchDB都支持基本的链码操作,比如获取和设置键值,基于键进行查询等等。
LevelDB:
Fabric默认的数据库,采用C++编写的高性能嵌入式数据库。其基本操作是基于键值对的,应该是Nosql数据库。
CouchDB:
文档型数据库,提供Restful的API操作。CouchDB中的文档是无模式的,并不要求文档具有某种特定的结构,支持JSON和字节数组的操作,可以支持复杂的查询。
历史数据记录了每个状态数据的历史信息,可用于区块提交的恢复。
区块的提交过程分为3个步骤: