参考书籍:《深度探索区块链:Hyperledger技术与应用》 @著 张增骏 董宁 朱轩彤 陈剑雄
1 流程图例 1.1 介绍fabric目录结构bccsp - 加密、签名、以及证书等待 密码学相关
bddtests - 行为驱动开发
common - 公共库 之前的first-network文章中用到了其中的 configtxgen cryptogen
core - fabric 核心代码库
devenv - 基于Vagrant开发环境
docs - 文档
event - 事件监听相关 (fabric的异步处理)
gossip - 数据通信模块 用于组织内部区块同步
gottools - makefile 用于编译
images - docker 镜像打包
msp - 成员管理 Membership Service Provider
orderer - 排序服务管理
peer - 节点
proposals - 新功能更新 旧功能舍弃的一个提案模块 开源社区通过后版本更新
protos - 通讯协议 fabric 中使用到的 各种通讯报文协议
2.交易过程
这里假设各个节点已经颁发好各种证书,正常启动,并已经加入到channel中。
2.1 创建交易提案并发送给背书节点应用程序构造交提案(SignedProposal),SignedProposal是对Proposal的封装,添加了调用者的签名信息。背书节点会通过签名信息验证消息是否有效。Proposal有两部分组成,消息头和消息结构。
以下是Proposal的结构
type Proposal struct {
// 提案头
Header []byte `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
// 负载消息 根据提案头的类型来确定具体的类型
Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"`
// 可选拓展,可能是ChaincodeAction 信息
Extension []byte `protobuf:"bytes,3,opt,name=extension,proto3" json:"extension,omitempty"`
}
客户端构造的SignedProposal的结构
SignedProposal: { // 带有应用程序签名的提案 ,需要提交到背书节点背书
ProposalBytes(Proposal): { // 提案数据
Header: { // 提案头 包含 ChannelHeader - 通道头 和 SignatureHeader-签名头
ChannelHeader: { // 通道头
Type: "HeaderType_ENDORSER_TRANSACTION", // 头类型- 背书交易
TxId: TxId, // 应用程序生成的交易号,与调用者的身份证书相关,可以避免交易号的冲突
Timestamp: Timestamp, // 时间戳
ChannelId: ChannelId, // 通道id 确定消息发往哪个通道
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
}
其中消息头包括了
通道头:包含了与通道和链码调用相关的信息,比如:在哪个通道调用哪个链码版本。TxId是应用程序生成的交易号,跟调用者的身份证书相关,可以避免交易号的冲突,背书节点和记账节点都会检查交易是否重复提交。 签名头:签名头里包含了调用者的身份证书和一个随机数,用于验证消息的有效性。应用程序构造好交易提案后,选择交易背书节点执行并进行背书签名。背书节点是链码背书策略里面指定的节点。如果指定背书节点不存在 ,应用程序可以选择其他节点进行背书来满足 背书策略。正常情况下,背书节点执行结果是一致的,不同的背书节点只有对结果的前面不一样。
2.2 背书节点模拟交易并生成背书签名当背书节点在收到交易提案后会进行验证 包括
交易提案格式是否正确 是否重复交易 交易签名是否有效(MSP) 交易提案的提交者在当前通道上是否有写权限验证完成后,背书节点会根据当前账本数据模拟执行链码中的业务逻辑,并生成读写集(RwSet),其中包涵响应值,读写集等信息。在模拟执行的过程中,账本数据不会更新。之后,背书节点对读写集进行签名 生成提案响应(Propsal Response),返回给应用程序
Propsal Response 结构:
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
}
}
摘录来自: 张增骏. “深度探索区块链:Hyperledger技术与应用 (区块链技术丛书)。
2.3 收集交易的背书
当应用节点收到ProposalResponse之后 会对背书节点的签名进行验证,验证通过后 对消息进行处理 这里分为 读和写两种情况
读操作:如果链码只对账本进行了query操作,应用程序会获取到查询响应,就结束了,不会继续想orderer节点提交交易 写操作:如果链码执行了invoke操作,则需要判断是否满足背书策略,判断通过后向orderer节点提交交易,判断不通过则是无效交易,进行标记。如何选择背书节点?
在1.0的版本中,fabric-go-sdk的默认实现是吧配置文件选项channels.mychannel.peers(mychannel是通道名称)里的节点全部作为背书节点。应用程序等待背书结果的超时时间配置为client.peer.timeout.connection 配置未见示例给出3s 默认为5s。
应用程序接收到所有背书节点的签名之后,根据背书签名调用SDK生成交易,提交到orderer节点。
确认了所有背书节点返回的执行结果一致之后将交易提案,提案响应和背书签名打包成交易。
交易结构
Envelope
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: { // 跟交易提案中的ChaincodeProposalPayload相关
Input(ChaincodeInvocationSpec): {
ChaincodeSpec: {
Type: Type,
ChaincodeId: {
Name: Name
},
Input(ChaincodeInput): {
Args: []
}
}
},
TransientMap: nil // 不同的是这里是nil 避免在区块中出现敏感数据
},
Action(ChaincodeEndorsedAction): {
Payload(ProposalResponsePayload): { // 跟ProposalResponse的负载相同
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
}
摘录来自: 张增骏. “深度探索区块链:Hyperledger技术与应用 (区块链技术丛书)。
2.5 排序服务节点对交易进行排序并生成区块
orderer生成区块的过程:
获取到channel中所有的交易信封,读取交易中的Evenlope.Payload.Header.ChannelHeader.ChannelId以获取channel名称,按照各个通道上交易提交的时间顺序对交易进行排序,生成区块。
orderer节点不读取交易内容,如果在生成交易信封时伪造了交易,在orderer节点也不会被发现,但是会在最终的交易验证阶段被校验出来并标记为无效交易。
2.6 排序服务节点广播生成的区块给组织的主节点orderer节点生成了区块之后 会广播给通道上不同组织的主节点。
2.7 记账节点验证区块内容并在本地写入区块当主节点从orderer节点获取到区块链之后其处理过程如下
摘录来自: 张增骏. “深度探索区块链:Hyperledger技术与应用 (区块链技术丛书)。
2.7.1 交易数据验证区块数据的验证,是以交易验证为单位的,每次验证都会生成一个交易号的位图TxValidationFlags,他记录每个交易号的交易状态。只有交易状态为TxValidationCode_VALID才是有效交易,位图也会写入到区块的元数据BlockMetadataIndex_TRANSACTIONS_FILTER中。交易验证会检查一下内容
是否是合法交易:格式、签名、交易内容是否被篡改 记账节点是否加入当前通道链码交易是隔离的。每个交易的模拟执行结果读写集TxRwSet都包含了交易所属的链码,为了避免错误的更新链码交易数据,在交易提交给系统链码VSCC验证交易内容之前,还会对链码进行校验。
下面的交易都是非法的
交易通过VSCC验证之后,进入记账流程。kvledger还会对读写集(TxRwSet)进行MVCC(Multi-Version Concurrency Control)检查。
kvledger实现的是基于Key-Value的状态数据模型。对状态数据的操作有三种
MVCC检查只对读数据进行校验,基本逻辑是对模拟执行时状态数据和提交交易时的状态数据进行比较。如果某个键或者某个键范围的数据发生了变化,就说明这段时间内有其他交易修改了数据,造成了数据冲突。
写集合本身包含了写数据和删除数据,有一个标志是否删除的状态位,为了提升效率,状态数据库的提交是批处理的,整个区块的交易一起提交。所以只会出现账本数据和状态数据不一致的情况,不会出现区块内状态数据不一致。
在账本数据与状态数据不一致时会通过状态数据库的检查点来标记
摘录来自: 张增骏. “深度探索区块链:Hyperledger技术与应用 (区块链技术丛书)。
2.7.4 无效交易处理伪造的交易会产生无效交易,正常的交易也会产生无效交易。
MVCC检查的是背书节点在模拟执行时的结果 与 记账节点提交交易市的状态数据是否一致。如果正常提交的交易在这个过程中涉及了数据的变化,那么也会出现检查失败而导致无效交易。这种情况需要应用程序来补偿。比如从新发起交易。
交易验证通过后,区块会记录在本地账本,同时通过gossip在组织内广播。