前言
在入门笔记(二)中,介绍了Fabric的交易流程,其中涉及“交易提案”、“提案响应”、“交易信封”等概念,它们到底包含了什么内容?它们之间又有什么关系?深入探索其中的细节将对Fabric的交易流程有一个全新的认知。本节的参考内容如下:
《区块链原理、设计与应用》第一版(电子书)
《区块链原理、设计与应用》第二版
《深度探索区块链:Hyperledger技术与应用》
一、消息结构介绍
Fabric中大量采用了gRPC消息在不同组件之间进行通信交互,在交易流程的第一阶段中,涉及如下三个消息结构:
- SignedProposal:发送给背书节点的交易提案;
- ProposalResponse:背书节点返回的提案响应;
- Envelope:提交给排序者的交易信封。
1.1. 交易提案
消息结构SignedProposal定义在fabric-protos-go项目的peer/proposal.pb.go文件中。它包含两个字段:ProposalBytes和Signature。
ProposalBytes使用的是Proposal结构,包含三个字段:Header、Payload和Extension。Signature则携带了调用者的签名信息。
- Header
Header包含了ChannelHeader(通道头)和SignatureHeader(签名头)。- ChannelHeader描述与通道和交易相关的信息,包括:
- Type:消息结构的类型,根据类型的不同,Proposal结构中的Payload可以解码为不同的结构。对于SignedProposal,Type只能是ENDORSER_TRANSACTION或CONFIG。
- Version:消息协议的版本,一般为0。
- Timestamp:消息创建的时间戳。
- ChannelID:消息所关联的通道ID。
- TxID:交易的ID,由交易发起者创建。
- Epoch:所属的世代,指定为所属区块的高度。
- Extension(可选):扩展域。当Type是ENDORSER_TRANSACTION时,这里的Extension使用的是ChaincodeHeaderExtension结构,包括:
- PayloadVisibility:ProposalBytes中的Payload在最终交易和账本中的可见程度。
- ChaincodeID:调用的链码ID,由Path、Name、Version指定。
- SignatureHeader描述签名者的信息,包括:
- Nonce:随机数,用于防止重放攻击。
- Creator:调用者的身份证书,用于验证签名是否有效。
- ChannelHeader描述与通道和交易相关的信息,包括:
- Payload
Payload使用的是ChaincodeProposalPayload结构,包含了TransientMap和Input。- TransientMap:用于私有数据的瞬态字段。
- Input:输入,使用的是ChaincodeInvocationSpec结构,携带了调用的链码ID和输入参数等信息。
- Extension
背书节点收到交易提案后,会验证调用者的签名信息是否有效。包括以下步骤:
- 预验证调用者证书的有效性:从SignatureHeader(签名头)的Creator字段中解码证书,过滤掉证书过期的情况。
- 验证证书是否可信(即,证书是否由受信任的CA签名)。
- 使用证书中的公钥验证Signature中的用户签名是否有效。
- 使用SignatureHeader(签名头)的nonce字段检测重放攻击。
1.2. 提案响应
消息结构ProposalResponse定义在fabric-protos-go项目的peer/proposal.pb.go文件中。它包含五个字段:Version、Timestamp、Payload、Response和Endorsement。
- Payload
Payload使用的ProposalResponsePayload结构,包含了ProposalHash和Extension。- ProposalHash:记录背书提案的哈希值,该字段将交易提案和提案响应两者对应起来,实现异步系统的记账功能和安全诉求。
- Extension:这里的Extension使用的是ChaincodeAction结构,包括:
- Results:使用的TxRwSet结构,代表调用链码产生的读写集。
- Events:使用的ChaincodeEvent结构,代表调用链码产生的事件。
- Response:调用链码的结果,记录了背书操作是否成功。包含Status(比如200代表正常)、Message和Payload。
- ChaincodeID:调用的链码ID。
- Response
调用链码的结果,与Payload中的Response相同。 - Endorsement
Endorsement包含了背书节点的证书和签名等信息。
1.3. 交易信封
消息结构Envelope定义在fabric-protos-go项目的common/common.pb.go文件中。它包含两个字段:Payload和Signature。
Payload包含两个字段:Header和Data。Signature同样携带了调用者的签名信息。
- Header
这里的Header与交易提案中的Header一致,包含了ChannelHeader(通道头)和SignatureHeader(签名头)。ChannelHeader描述与通道和交易相关的信息,SignatureHeader描述签名者的信息。
注:交易信封是Fabric中最基本的消息结构,与交易提案和提案响应不同,它的使用范围更多样,因此其Payload中的Header.ChannelHeader.Type的可选项比SignedProposal更多。
- Data
同样的,对交易信封而言,其Payload中的Data根据使用情况的不同,有多种不同的结构可选,包括:Transaction、ConfigEnvelope和ConfigUpdateEnvelope。在这里,使用的是Transaction结构,它包含了一个列表TransactionAction:- TransactionAction:其中的每一个元素都包含:
- Header:使用的是SignatureHeader结构,与上面的SignatureHeader一致,描述签名者的信息。
- Payload:使用的ChaincodeActionPayload结构,记录了交易的核心信息。包括:
- ChaincodeProposalPayload:与交易提案中的同名结构一致,包含了TransientMap和Input。不同的是,TransientMap强制设置为nil,目的是避免在区块中出现一些敏感信息(即,私有数据)。
- Action:使用的是ChaincodeEndorsedAction结构,其中又包括:
- Payload:ProposalResponsePayload结构,与提案响应中的同名结构一致,包含了ProposalHash、TxRwSet等内容。
- Endorsement:与交易响应中的同名结构一致,包含了背书节点的证书和签名等信息。不同的是,这里是一个列表,代表多个背书节点的背书。
- TransactionAction:其中的每一个元素都包含:
二、对应关系
它们的对应关系如下,相同颜色代表完全相同的内容,除了Envelope中的Endorsement部分变成了数组,代表多个背书节点的背书签名。
附录:交易流程中的完整消息结构示意
交易提案
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
}
提案响应
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
}
}
交易信封
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
}