上一篇分享了超级帐本的系统逻辑架构和网络节点架构,本篇主要分享超级帐本的典型交易流程。html
下图所示为Hyperledger Fabric 1.0典型的交易流程图。算法
从上一节的网络节点架构中,咱们已经了解到基于Hyperledger Fabric 1.0的区块链应用中涉及几个节点角色:应用程序、背书节点、排序服务节点和主节点。在图3-4中,假定各节点已经提早颁发好证书,且已正常启动,并加入已经建立好的通道。后面的步骤介绍在已经实例化了的链码通道上从发起一个调用交易到最终记帐的全过程。数据库
一、建立交易提案并发送给背书节点编程
使用应用程序构造交易提案,SignedProposal的结构以下所示:数组
SignedProposal: {
ProposalBytes(Proposal): {
Header: {
ChannelHeader: {
Type: "HeaderType_ENDORSER_TRANSACTION",
TxId: TxId,
Timestamp: Timestamp,
ChannelId: ChannelId,
Extension(ChaincodeHeaderExtension): {
PayloadVisibility: PayloadVisibility,
ChaincodeId: {
Path: Path,
Name: Name,
Version: Version
}
},
Epoch: Epoch
},
SignatureHeader: {
Creator: Creator,
Nonce: Nonce
}
},
Payload: {
ChaincodeProposalPayload: {
Input(ChaincodeInvocationSpec): {
ChaincodeSpec: {
Type: Type,
ChaincodeId: {
Name: Name
},
Input(ChaincodeInput): {
Args: []
}
}
},
TransientMap: TransientMap
}
}
},
Signature: Signature
}
复制代码
咱们来看看上面的结构,SignedProposal是封装了Proposal的结构,添加了调用者的签名信息。背书节点会根据签名信息验证其是不是一个有效的消息。Proposal由两个部分组成:消息头和消息结构。消息结构详细的解释参考后面的章节。这里简单讲一下消息头。缓存
消息头(Header)也包含两项内容。bash
通道头(ChannelHeader):通道头包含了与通道和链码调用相关的信息,好比在哪一个通道上调用哪一个版本的链码。TxId是应用程序本地生成的交易号,跟调用者的身份证书相关,能够避免交易号的冲突,背书节点和记帐节点都会校验是否存在重复交易。网络
签名头(SignatureHeader):签名头包含了调用者的身份证书和一个随机数,用于消息的有效性校验。架构
应用程序构造好交易提案请求后,选择背书节点执行并进行背书签名。背书节点是链码背书策略里指定的节点。有一些背书节点是离线的,其余的背书节点能够拒绝对交易进行背书,也能够不背书。应用程序能够尝试使用其余可用的背书节点来知足策略。应用程序以何种顺序给背书节点发送背书请求是没有关系的,正常状况下背书节点执行后的结果是一致的,只有背书节点对结果的签名不同。并发
二、背书节点模拟交易并生成背书签名
背书节点在收到交易提案后会进行一些验证,包括:
交易提案的格式是否正确;
交易是否提交过(重复攻击保护);
交易签名有效(经过 MSP);
交易提案的提交者在当前通道上是否已受权有写权限。
验证经过后,背书节点会根据当前帐本数据模拟执行链码中的业务逻辑并生成读写集(RwSet),其中包含响应值、读写集等。在模拟执行时帐本数据不会更新。然后背书节点对这些读写集进行签名成为提案响应(Proposal Response),而后返回给应用程序。
ProposalResponse的结构以下:
ProposalResponse: {
Version: Version,
Timestamp: Timestamp,
Response: {
Status: Status,
Message: Message,
Payload: Payload
},
Payload(ProposalResponsePayload): {
ProposalHash: ProposalHash,
Extension(ChaincodeAction): {
Results(TxRwSet): {
NsRwSets(NsRwSet): [
NameSpace: NameSpace,
KvRwSet: {
Reads(KVRead): [
Key: Key,
Version: {
BlockNum: BlockNum,
TxNum: TxNum
}
],
RangeQueriesInfo(RangeQueryInfo): [
StartKey: StartKey,
EndKey: EndKey,
ItrExhausted: ItrExhausted,
ReadsInfo: ReadsInfo
],
Writes(KVWrite): [
Key: Key,
IsDelete: IsDelete,
Value: Value
]
}
]
},
Events(ChaincodeEvent): {
ChaincodeId: ChaincodeId,
TxId: TxId,
EventName: EventName,
Payload: Payload
}
Response: {
Status: Status,
Message: Message,
Payload: Payload
},
ChaincodeId: ChaincodeId
}
},
Endorsement: {
Endorser: Endorser,
Signature: Signature
}
}
复制代码
返回的ProposalResponse中包含了读写集、背书节点签名以及通道名称等信息。
三、收集交易的背书
应用程序收到ProposalResponse后会对背书节点签名进行验证,全部节点接收到任何消息后都是须要先验证消息合法性的。若是链码只进行帐本查询,应用程序会检查查询响应,但不会将交易提交给排序服务节点。若是链码对帐本进行Invoke操做,则须提交交易给排序服务进行帐本更新,应用程序会在提交交易前判断背书策略是否知足。若是应用程序没有收集到足够的背书就提交交易了,记帐节点在提交验证阶段会发现交易不能知足背书策略,标记为无效交易。
如何选择背书节点呢?目前fabric-sdk-go默认的实现是把配置文件选项channels.mychannel.peers(其中的mychannel须要替换成实际的通道名称)里的节点所有添加为背书节点,须要等待全部背书节点的背书签名。应用程序等待每一个背书节点执行的超时时间是经过配置文件选项client.peer.timeout.connection设置的,配置文件的示例给出的是3秒,根据实际状况调整,若是没有设置就是5秒的默认值。
四、构造交易请求并发送给排序服务节点
应用程序接收到全部的背书节点签名后,根据背书签名调用SDK生成交易,广播给排序服务节点。生成交易的过程比较简单,确认全部的背书节点的执行结果彻底一致,再将交易提案、提案响应和背书签名打包生成交易。交易的结构以下:
Envelope: {
Payload: {
Header: {
ChannelHeader: {
Type: "HeaderType_ENDORSER_TRANSACTION",
TxId: TxId,
Timestamp: Timestamp,
ChannelId: ChannelId,
Extension(ChaincodeHeaderExtension): {
PayloadVisibility: PayloadVisibility,
ChaincodeId: {
Path: Path,
Name: Name,
Version: Version
}
},
Epoch: Epoch
},
SignatureHeader: {
Creator: Creator,
Nonce: Nonce
}
},
Data(Transaction): {
TransactionAction: [
Header(SignatureHeader): {
Creator: Creator,
Nonce: Nonce
},
Payload(ChaincodeActionPayload): {
ChaincodeProposalPayload: {
Input(ChaincodeInvocationSpec): {
ChaincodeSpec: {
Type: Type,
ChaincodeId: {
Name: Name
},
Input(ChaincodeInput): {
Args: []
}
}
},
TransientMap: nil
},
Action(ChaincodeEndorsedAction): {
Payload(ProposalResponsePayload): {
ProposalHash: ProposalHash,
Extension(ChaincodeAction): {
Results(TxRwSet): {
NsRwSets(NsRwSet): [
NameSpace: NameSpace,
KvRwSet: {
Reads(KVRead): [
Key: Key,
Version: {
BlockNum: BlockNum,
TxNum: TxNum
}
],
RangeQueriesInfo(RangeQueryInfo): [
StartKey: StartKey,
EndKey: EndKey,
ItrExhausted: ItrExhausted,
ReadsInfo: ReadsInfo
],
Writes(KVWrite): [
Key: Key,
IsDelete: IsDelete,
Value: Value
]
}
]
},
Events(ChaincodeEvent): {
ChaincodeId: ChaincodeId,
TxId: TxId,
EventName: EventName,
Payload: Payload
}
Response: {
Status: Status,
Message: Message,
Payload: Payload
},
ChaincodeId: ChaincodeId
}
},
Endorsement: [
Endorser: Endorser,
Signature: Signature
]
}
}
]
}
},
Signature: Signature
}
复制代码
咱们来看交易信封的几个对应关系:
Envelope.Payload.Header同交易提案SignedProposal.Proposal.Header;
Envelope.Payload.Data.TransactionAction.Header是交易提案的提交者的身份信息,同SignedProposal.Proposal.Header.SignatureHeader和Envelope.Payload.Header.SignatureHeader是冗余的;
Envelope.Payload.Data.TransactionAction.Payload.ChaincodeProposalPayload同交易提案的SignedProposal.Proposal.Payload.ChaincodeProposalPayload,惟一不一样的是,TransientMap强制设置为nil,目的是避免在区块中出现一些敏感信息;
Envelope.Payload.Data.TransactionAction.Payload.Action.Payload结构,其实和Proposal
Response.Payload结构彻底同样;
Envelope.Payload.Data.TransactionAction.Payload.Action.Endorsement变成了数组,表明多个背书节点的背书签名。
整个信封Envelope的Signature是交易提交者对整个Envelope.Payload的签名。应用程序能够把生成的交易信封内容发送给任意选择的几个排序服务节点。
五、排序服务节点以对交易进行排序并生成区块
排序服务不读取交易的内容,若是在生成交易信封内容的时候伪造了交易模拟执行的结果,排序服务节点也不会发现,但会在最终的交易验证阶段校验出来并标记为无效交易。排序服务要作得很简单,先是接收网络中全部通道发出的交易信息,读取交易信封的Envelope.Payload.Header.ChannelHeader.ChannelId以获取通道名称,按各个通道上交易的接收时间顺序对交易信息进行排序,生成区块。
六、排序服务节点以广播给组织的主节点
排序服务节点生成区块之后会广播给通道上不一样组织的主节点。
七、记帐节点验证区块内容并写入区块
背书节点是动态角色,只要参与交易的背书就是背书节点,哪些交易选择哪些节点做为背书节点是由应用程序选择的,这须要知足背书策略才能生效。全部的背书节点都属于记帐节点。全部的Peer节点都是记帐节点,记录的是节点已加入通道的帐本数据。记帐节点接收到的是排序服务节点生成的区块,验证区块交易的有效性,提交到本地帐本后再产生一个生成区块的事件,监听区块事件的应用程序能够进行后续的处理。
若是接收到的区块是配置区块,则会更新缓存的配置信息。记帐节点的处理流程如图所示。
交易数据的验证
区块数据的验证是以交易验证为单位的,每次对区块进行验证时都会生成一个交易号的位图TxValidationFlags,它记录每一个交易号的交易验证状态,只有状态为TxValidationCode_VALID才是有效的。位图也会写入到区块的元数据BlockMetadataIndex_TRANSACTIONS_FILTER中。交易验证的时候会检查如下内容:
是否为合法的交易:交易格式是否正确,是否有合法的签名,交易内容是否被篡改;
记帐节点是否加入了这个通道。
基本的验证经过之后会提交给VSCC进行背书策略的验证。
记帐节点与VSCC
链码的交易是隔离的,每一个交易的模拟执行结果读写集TxRwSet都包含了交易所属的链码。为了不错误地更新链码交易数据,在交易提交给系统链码VSCC验证交易内容以前,还会对链码进行校验。下面这些交易都是非法的:
链码的名称或者版本为空;
交易消息头里的链码名称Envelope.Payload.Header.ChannelHeader.Extension.ChaincodeId. Name和交易数据里的链码名称Envelope.Payload.Data.TransactionAction.Payload.ChaincodeProposalPayload.Input.ChaincodeSpec.ChaincodeId.Name不一致;
链码更新当前链码数据时,生成读写集的链码版本不是LSCC记录的最新版本;
应用程序链码更新了LSCC(生命周期管理系统链码)的数据;
应用程序链码更新了不可被外部调用的系统链码的数据;
应用程序链码更新了不可被其余链码调用的系统链码的数据;
调用了不可被外部调用的系统链码。
基于状态数据的验证和MVCC检查
交易经过VSCC检查之后,就进入记帐流程。kvledger还会对读写集TxRwSet进行MVCC(Multi-Version Concurrency Control)检查。
kvledger实现的是基于键值对(key-value)的状态数据模型。对状态数据的键有3种操做:
读状态数据;
写状态数据;
删除状态数据。
对状态数据的读操做有两种形式:
基于单一键的读取;
基于键范围的读取。
MVCC检查只对读数据进行校验,基本逻辑是对模拟执行时状态数据的版本和提交交易时状态数据的版本进行比较。若是数据版本发生变化或者某个键的范围数据发生变化,就说明这段时间以内有别的交易改变了状态数据,当前交易基于原有状态的处理就是有问题的。因为交易提交是并行的,因此在交易未打包生成区块以前,并不能肯定最终的执行顺序。若是交易执行的顺序存在依赖,在MVCC检查的时候就会出现依赖的状态发生了变化,其实是数据出现了冲突。图所示为基于状态的数据验证的流程图。
写集合自己包含了写和删除的数据,有一个状态位标识是否删除数据。为了提高效率,状态数据库的提交是批处理的,整个区块交易的状态数据同时提交,这也保证了整个区块的状态数据要么都提交成功,要么都提交失败。这时只会出现记录的帐本数据和状态数据库不一致,不会出现区块的状态数据不一致的状况。当帐本数据和状态数据库不一致时,能够经过状态数据库的检查点来标记。
无效交易的处理
伪造的交易会致使无效交易,正常的交易也可能出现无效交易。MVCC检查的是背书节点在模拟执行的时候,环境是否和记帐节点提交交易时的环境一致,这里的环境是指状态数据库里数据的三元组(key、value、version)是否彻底一致。若是正常提交的交易在这个过程当中涉及的数据发生了变化,那么也会出现检查失败从而致使无效交易。在这种状况下,须要在上层的应用程序有一些补偿措施,好比调整交易打包的配置,从新提交失败的交易等。
在目前版本的实现中,无效交易也会保留在区块中,能够经过区块记录的元数据肯定哪些是无效交易。无效交易的读写集不会提交到状态数据库中,不会致使状态数据库发生变化,只是会占用区块的大小,占用记帐节点的硬盘空间。后续的版本会实现帐本的精简,过滤掉无效交易。
八、在组织内部同步最新的区块
下篇预告:深度探索Hyperledger技术与应用之超级帐本的策略管理
京东有购:https://item.jd.com/12279369.html
深度探索区块链
Hyperledger技术与应用
区块链
张增骏,董宁,朱轩彤,陈剑雄 著
本书由超级帐本执行董事Brian Behlendorf领衔推荐,区块链一线落地实践团队、Hyperleger会员智链骨干团对撰写。深刻讲解Hyperledger Fabric 1.0的架构、执行逻辑、核心功能实现、从零部署,并以票据案例为例,讲解具体开发实践,穿插开发所需的最佳实践和遇到的问题解决。
机械工业
出版社
华章科技是机械出版社的旗下品牌,出版了“计算机科学丛书”等近30个经典套系,在各个细分领域均处于领导地位,其中《Java编程思想》、《算法导论》、《编译原理》、《数据挖掘:概念与技术》、《深刻理解计算机系统》、《深刻理解Java虚拟机》等著做犹如计算机图书领域的璀璨明珠,长销不衰!
HiBlock秉承开放、协做、透明、连接、分享的价值观,致力打造区块链开发者社区。专一于在开发者中推广区块链,帮助开发者真正掌握区块链技术和应用。
本文内容节选自《深度探索区块链:Hyperledger技术与应用》一书的第3章《超级帐本的系统架构》。
本书做者:张增骏,董宁,朱轩彤,陈剑雄
感谢机械工业出版社华章分社的支持和分享。
活动推荐
**主题:**5月25-27日,Blockathon2018北京站,招募100名开发者一块儿挑战区块链开发。
开发者免费,报名需审核。识别下图二维码或点击“阅读原文”便可报名参加。
点击“阅读原文”便可报名。