账本结构
1.区块链数据,这是用文件系统存储在Committer节点上的。区块链中存储了Transaction的读写集。
2.为了检索区块链的方便,所以用LevelDB对其中的Transaction进行了索引。
3.ChainCode操作的实际数据存储在State Database中,这是一个Key Value的数据库,默认采用的LevelDB,现在1.0也支持使用CouchDB作为State Database
交易流程
1.客户端构造交易提案
客户端构造交易提案,发送给一个或多个Peer节点,交易提案中包含本次交易要调用的合约标识、合约方法和参数信息以及客户端签名等。
Peer节点模拟执行交易
Peer节点收到交易提案后,会模拟执行交易,然后将原始交易提案和执行结果打包到一起,进行签名并发回给客户端,其中在模拟执行交易期间产生的数据修改不会写到账本上。
客户端打包
客户端收到各个Peer的应答后,打包到一起组成一个交易并签名,发送给Orderer。
共识排序,生成新区块
Orderer对接收到的交易进行共识排序,然后按照区块生成策略,将一批交易打包到一起,生成新的区块,发送给Peer节点。
交易校验
Peer节点收到区块后,会对区块中的每笔交易进行校验,检查交易依赖的输入输出是否符合当前区块链的状态,完成后将区块写入账本,并修改K-V状态数据。
示例
example2
{"Args":["invoke","a","b","10"]}
Avalbytes, err := stub.GetState(A)
Bvalbytes, err := stub.GetState(B)
err = stub.PutState(A, []byte(strconv.Itoa(Aval)))
err = stub.PutState(B, []byte(strconv.Itoa(Bval)))
1.客户端SDK把'{"Args":["invoke","a","b","10"]}'这些参数发送到endorser peer节点,
2.endorser peer会与ChainCode的docker实例通信,并为其提供模拟的State Database的读写集,也就是说ChainCode会执行完逻辑,但是并不会在stub.PutState的时候写数据库。
3.endorser把这些读写集连同签名返回给Client SDK。
4.SDK再把读写集发送给Orderer节点,Orderer节点是进行共识的排序节点,在测试的情况下,只启动一个orderer节点,没有容错。在生产环境,要进行Crash容错,需要启用Zookeeper和Kafka。在1.0中移除了拜占庭容错,没有0.6的PBFT,也没有传说中的SBFT,不得不说是一个遗憾。
5.Orderer节点只是负责排序和打包工作,处理的结果是一个Batch的Transactions,也就是一个Block,这个Block的产生有两种情况,一种情况是Transaction很多,Block的大小达到了设定的大小,而另一种情况是Transaction很少,没有达到设定的大小,那么Orderer就会等,等到大小足够大或者超时时间。这些设置是在configtx.yaml中设定的。
6.打包好的一堆Transactions会发送给Committer Peer提交节点,
7.提交节点收到Orderer节点的数据后,会先进行VSCC校验,检查Block的数据是否正确。接下来是对每个Transaction的验证,主要是验证Transaction中的读写数据集是否与State Database的数据版本一致。验证完Block中的所有Transactions后,提交节点会把吧Block写入区块链。然后把所有验证通过的Transaction的读写集中的写的部分写入State Database。另外对于区块链,本身是文件系统,不是数据库,所有也会有把区块中的数据在LevelDB中建立索引。