Hyperledger Fabric从源码分析交易
在上一章Hyperledger Fabric从源码分析区块结构中提到了区块的概念,并从源码角度对区块的结构进行了剖析,知道一个简单的区块包含了下面几个部分
- BlockHeader, 区块头
- BlockData,区块数据
- BlockMetaData,区块元数据
上篇文章中已经对区块头,区块数据,区块元数据各个字段的内容进行了分析。我们知道BlockData字段是一个切片,存储的是一条条交易,但是并没有对交易具体的结构进行剖析。那么这篇文章就来分析一下一条交易里面会有哪些东西。
1. 先猜想一下一条交易会有哪些东西
老规矩,先猜想一下一条交易会有哪些数据信息。下面猜想的肯定有对的,也有不对的,这也是一个学习的思路。
先来看下一个交易流程图,方便我们做猜想,我在之前的文章 Hyperledger Fabric的网络拓扑图与交易流程中画了一张图来演示Fabric交易的整体流程,现在再把这张图拿出来看一下。
对着交易流程,一步步分析。
首先客户端提交一个交易提案给背书节点,好那么一个交易应该会有客户端,也就是交易发起方的一些基础信息,可能会包括客户端证书签名等等信息。
之后交易到达背书节点,背书节点会模拟执行交易并进行签名。好那么这条交易也应该会有一个背书节点的签名信息。
客户端拿到模拟执行结果以后向排序节点提交交易,但是排序节点是分通道的,到底是发送给哪个通道呢,所以交易应该也会带一个具体发送到哪个通道的信息
再加上交易本身的一些数据,可能包括交易类型,交易双方地址,交易数据(比如A向B转账10),这些可以称做交易的基础数据。
之后交易就被排序节点打包成区块了,因此我们对交易内容的分析到这里就可以了。上面的说的都是一些我还没看任何资料的一些理解,下面看看源码看看一条交易里面到底有什么。
2. 源码的交易结构
还是来看看之前分析区块时的一个函数NewBlock
func NewBlock(env []*common.Envelope, blockNum uint64, previousHash []byte) *common.Block {block := common.NewBlock(blockNum, previousHash)for i := 0; i < len(env); i++ {// 一条交易数据txEnvBytes, _ := proto.Marshal(env[i])// 在创建区块的时候就写入交易数据block.Data.Data = append(block.Data.Data, txEnvBytes)}block.Header.DataHash = block.Data.Hash()utils.InitBlockMetadata(block)block.Metadata.Metadata[common.BlockMetadataIndex_TRANSACTIONS_FILTER] = lutils.NewTxValidationFlagsSetValue(len(env), pb.TxValidationCode_VALID)return block
}
可以看到传参的时候了一个Envelope类型的数据,这是一个切片,然后又将该切片的数据序列化以后复制给了BlockData,因为BlockData中存储的是交易数据,因此可以推断Envelope类型就是交易的结构原型。下面看看这个结构
// Envelope wraps a Payload with a signature so that the message may be authenticated
type Envelope struct {// A marshaled PayloadPayload []byte // A signature by the creator specified in the Payload headerSignature []byte XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}
Envelope直译是信封的意思,可以形象地理解,一条交易是一个信封,在排序节点排序的时候不会去拆开信封看,也就是说排序节点不会关心交易的数据,它的作用就是排序(共识)。而当交易被打包成区块分发的各个记账节点时,记账节点就会去拆开这个信封,也就是会去关心交易的数据,因为它要记账,另外还有做验证这之类的操作。所以Fabric中用Envelope这个单词来表示一个交易。它内部有两个字段
- Payload,直译过来就是有效载荷,抽象一下,payload 可以理解为一系列信息中最为关键的信息,也就是说交易的关键信息都存储在Payload当中,它是一个序列化的字节数组
- Signature,签名信息,根据注释信息可以知道,这个签名就是Payload header中指定的交易的创建者的签名,应该就是之前分析时说的客户端的签名
PayLoad字段解析
下面的重要任务就是对Payload这个字段进行分析了,因此要对Payload序列化之前的结构进行分析。我在源码中找到了一处关于Payload的赋值的地方
updateResult := &cb.Envelope{Payload: utils.MarshalOrPanic(&cb.Payload{Header: &cb.Header{ChannelHeader: utils.MarshalOrPanic(&cb.ChannelHeader{Type: int32(cb.HeaderType_CONFIG),ChannelId: chainID,}),SignatureHeader: utils.MarshalOrPanic(&cb.SignatureHeader{Creator: signerSerialized,Nonce: utils.CreateNonceOrPanic(),}),},Data: utils.MarshalOrPanic(&cb.ConfigEnvelope{LastUpdate: chCrtEnv,}),}),}
上面这条赋值语句不仅让我们找到了Payload字段序列化之前的结构体Payload,在Payload字段内部的一些字段序列化之前的信息也可以找到,这样就一锅端了很好,下面就一起来看下这几个结构体。
// Payload is the message contents (and header to allow for signing)
type Payload struct {// Header is included to provide identity and prevent replayHeader *Header // Data, the encoding of which is defined by the type in the headerData []byte XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}
Header字段
首先看下Payload结构体,它有两个字段
- Header,Payload的头部字段信息
- Data,Payload的具体数据,说到底,它就是交易transaction的序列化信息
看下Header字段包含了哪些信息,包括它各字段的序列化对象
type Header struct {// 顾名思义,交易通道的HeaderChannelHeader []byte // 顾名思义,交易签名的HeaderSignatureHeader []byte XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}// Header is a generic replay prevention and identity message to include in a signed payload
type ChannelHeader struct {// HeaderType,之前说的Payload中Data字段会与这个Type有关// 它包含下面几个类型,具体每个Heaeder类型是做什么的这里暂时不说了// const (// HeaderType_MESSAGE HeaderType = 0// HeaderType_CONFIG HeaderType = 1// HeaderType_CONFIG_UPDATE HeaderType = 2// HeaderType_ENDORSER_TRANSACTION HeaderType = 3// HeaderType_ORDERER_TRANSACTION HeaderType = 4// HeaderType_DELIVER_SEEK_INFO HeaderType = 5// HeaderType_CHAINCODE_PACKAGE HeaderType = 6// HeaderType_PEER_ADMIN_OPERATION HeaderType = 8// HeaderType_TOKEN_TRANSACTION HeaderType = 9//)Type int32 // Version表示消息协议的版本// 在创世区块中被这么赋值 msgVersion = int32(1)Version int32 // Timestamp表示这条消息创建时的本地时间Timestamp *timestamp.Timestamp // ChannelId表示这条消息绑定的通道的IDChannelId string // TxID表示交易的唯一标识符,唯一IDTxId string // Epoch直译表示新纪元,epoch表示生成此Header的纪元,他是根据Block height定义的Epoch uint64 // Extension是根据Type字段可以附加的扩展部分Extension []byte// TlsCertHash表示客户端TLS证书的哈希,如果使用的是mutual TLSTlsCertHash []byte XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}type SignatureHeader struct {// Creator 表示消息的创建者,他是一个序列化的身份信息Creator []byte // Nonce是只使用一次的随机数字,可用于检测replay attacks// Creator和Nonce两个字段可以用来生成一个TxIDNonce []byte XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}
针对上面的描述再做一个总结
ChannelHeader(通道Header)
- Type,Header的type
- Version,消息协议的版本
- Timestamp,消息创建的时间戳
- ChannelID,消息绑定的通道的ID
- TxID,消息的唯一标识符
- Epoch,表示此Header的纪元,根据Block height定义的
- Extension,根据Type字段可以附加的扩展部分
SignatureHeader(签名Header)
- Creator,表示消息的创建者,他是一个序列化的身份信息
- Nonce,他是一个随机数,可用于检测replay attacks,Creator和Nonce两个字段可以用来生成一个TxID
Header字段到这里就分析完了
Data字段
Data具体的反序列化对象,根据ChannelHeader中的Type不同而不同。
Data的序列化与反序列化过程,不同的交易类型会导致Data的元数据不同,即当Payload.Data赋值的时候,他可能是不同的结构体对象序列化之后的字节切片。
但是在验证交易需要获取交易信息的时候,又被反序列化成了一个统一的结构对象——Transaction,找一处调用看一下
Transaction
// Transaction
tx, err := utils.GetTransaction(payload.Data)// GetTransaction Get Transaction from bytes
func GetTransaction(txBytes []byte) (*peer.Transaction, error) {tx := &peer.Transaction{}err := proto.Unmarshal(txBytes, tx)return tx, errors.Wrap(err, "error unmarshaling Transaction")
}
可以看到获取是从Payload.Data中反序列化成了一个Transaction对象,因此我们分析Data的时候,就按照Transaction对象分析就可以了。
- Transaction是要被送给排序节点的最终结果
- Transaction包括一个或多个TransactionAction,每个TransactionAction都将一个提案绑定到潜在的多个actions
- Transaction是原子性的,这意味着要么提交Transaction里的所有actions,或者不提交
- 意当一个Transaction包含一个以上的Header时,那么每个Header.Creator字段必须一样
- 一个client可以自由发布多个独立的提案,每个提案都包含他们的Header和要求的payload(ChaincodeProposalPayload)
- 每个提案都被独立背书,生成一个aciton(ProposalResponsePayload),每个背书者都有签名
- 任意数量的独立提案(以及他们的actions)可能包含在一个Transaction中以确保他们是原子的
type Transaction struct {// Actions是TransactionAction的数组// 为了每个Transaction容纳多个acitons,必须要是数组Actions []*TransactionAction XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}
那么下面来看看TransactionAction结构体
TransactionAction
// TransactionAction 绑定一个提案到它的aciton.
// header中的type字段决定了应用于账本的action的type
type TransactionAction struct {// 交易提案action的Header,Proposal的headerHeader []byte // 交易提案action的主要信息// 当type是chaincode时,它是ChaincodeActionPayload的序列化Payload []byte XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}
TransactionAction.Payload
字段就是接下来分析的重点,该字段根据header中的type字段而定义,当type是CHAINCODE时,它是ChaincodeActionPayload
的序列化,那么来看下ChaincodeActionPayload
结构体
ChaincodeActionPayload
// ChaincodeActionPayload是当Header的type设置为CHAINCODE时用于赋值给TransactionAction的Payload的
type ChaincodeActionPayload struct {// 这个字段是 ChaincodeProposalPayload 类型的序列化信息ChaincodeProposalPayload []byte// 应用于账本的actions的列表Action *ChaincodeEndorsedAction XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}
ChaincodeActionPayload
两个模块组成
- ChaincodeProposalPayload,这个字段是
ChaincodeProposalPayload
类型的序列化信息,它包含了原始调用函数的参数信息 - Action,这个字段表示的actions的列表 ,它是一个
ChaincodeEndorsedAction
类型
先来看看ChaincodeProposalPayload
结构体
ChaincodeProposalPayload
// ChaincodeProposalPayload是提案的具体信息,当Header的类型是CHAINCODE时
// 它包含了这次调用的参数
type ChaincodeProposalPayload struct {// 包含了这次调用的参数Input []byte// TransientMap包含一些数据(例如密码材料),可以用来实现某种形式的应用级机密性// 这个字段的内容应该总是从交易中被省略,从分类账中被排除。TransientMap map[string][]byte XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}
ChaincodeProposalPayload
是当Header的type字段是CHAINCODE时,提案的主要信息,主要包含了两个字段
- Input,包含了这次链码调用的一些参数
- TransientMap,包含一些数据(例如密码材料),可以用来实现某种形式的应用级机密性,这个字段的内容应该总是从交易中被省略,从分类账中被排除。
再来看看ChaincodeEndorsedAction
结构体
ChaincodeEndorsedAction
// ChaincodeEndorsedAction 包含了一个指定提案的背书信息
type ChaincodeEndorsedAction struct {// 提案响应信息ProposalResponsePayload []byte// 提案的背书,基本上是背书者在proposalResponsePayload上的签名Endorsements []*Endorsement XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}
ChaincodeEndorsedAction
包含了一个指定提案的背书信息,主要包含了两个字段
- ProposalResponsePayload,提案的响应信息,它是
ProposalResponsePayload
的序列化 - Endorsements, 提案的背书,是一些背书人在
ProposalResponsePayload
上的签名
先来看看ProposalResponsePayload
结构体
ProposalResponsePayload
type ProposalResponsePayload struct {// 提案相应的哈希ProposalHash []byte// 根据Type字段可以附加的扩展部分// 对于CHAINCODE而言,它是ChaincodeAction的序列化信息Extension []byte XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}// ChaincodeAction包含了链码执行生成的events的actions
type ChaincodeAction struct {// 包含了链码执行的读写集Results []byte // 包含了链码调用生成的eventsEvents []byte // 包含了链码调用生成的ResponseResponse *Response// 链码IDChaincodeId *ChaincodeID // 包含了链码调用生成的token expectationTokenExpectation *token.TokenExpectation XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}
最后看下Endorsement
结构体,这主要是一些背书人的身份信息及签名。
Endorsement
type Endorsement struct {// 背书者本身的身份信息Endorser []byte// 背书签名Signature []byte XXX_NoUnkeyedLiteral struct{} XXX_unrecognized []byte XXX_sizecache int32
}
3. 思维导图总结
Hyperledger Fabric从源码分析交易相关推荐
- Hyperledger Fabric从源码分析背书提案过程
在之前的文章中 Hyperledger Fabric从源码分析链码安装过程 Hyperledger Fabric从源码分析链码实例化过程 Hyperledger Fabric从源码分析链码查询与调用 ...
- 以太坊源码分析-交易
以太坊源码分析-交易 机理 先说一点区块链转账的基本概念和流程 用户输入转账的地址和转入的地址和转出的金额 系统通过转出的地址的私钥对转账信息进行签名(用于证明这 笔交易确实有本人进行) 系统对交易信 ...
- BTC源码分析 交易(一)
概念 在<比特币:一种点对点的电子现金系统>中,电子货币被定义为:每一位所有者通过对前一次交易和下一位拥有者的公钥(Public key) 签署一个随机散列的数字签名,并将这个签名附加在这 ...
- go-ethereum-code-analysis 以太坊源码分析
分析go-ethereum的过程,我希望从依赖比较少的底层技术组件开始,慢慢深入到核心逻辑. 目录 go-ethereum代码阅读环境搭建 以太坊黄皮书 符号索引 rlp源码解析 trie源码分析 e ...
- Fabric源码分析-共识模块
正好这些天要有一个需求要帮客户魔改Fabric-v0.6,把一些hyperchain的高级特性移植过去,借此机会把之前看过的源码在梳理一下. 下面就是对Fabric共识模块的源码分析和梳理,代码都是以 ...
- 以太坊C++客户端Aleth源码分析,转账交易和智能合约的入口代码
本文主要记录以太坊C++客户端Aleth的源码分析和相关实验过程和结果.本文将讲解两部分的内容,一是转账交易和智能合约的入口代码在哪里?二是通过实验验证转账交易和智能合约交易这两种不同交易所对应的不同 ...
- BitXHub 跨链插件(Fabric)源码解读
前言 趣链科技的BitXHub跨链平台是业界较为完善的跨链开源解决方案,主要通过中继链.网关和插件机制对跨链流程中的功能.安全性和灵活性等进行了优化.本文对BitXHub的meshplus/pier- ...
- EOS智能合约:system系统合约源码分析
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. eosio.system 概览 笔者使用的IDE是VScode,首先来看eosio.system的源码结构.如下图所示. ...
- 分布式事务 TCC-Transaction 源码分析 —— 项目实战
2019独角兽企业重金招聘Python工程师标准>>> 摘要: 原创出处 http://www.iocoder.cn/TCC-Transaction/http-sample/ 「芋道 ...
最新文章
- mysql update用not in太慢了_MySQL 加锁和死锁解析
- 情人节——微信朋友圈浓浓爱意的9张拼图(HTML版本)
- 【嵌入式】C语言程序调试和宏使用的技巧
- idea git里的用户怎么修改
- linux内核参数的程序,技巧-Linux内核参数调整办法
- java filter 模式,Java设计模式----过滤器模式(挑三拣四)
- 为什么c相电路在前面_Buck电路的多角度分析
- 在大项目中,实施顾问主要负责什么具体工作?
- 标准时间标准Time Zone: GMT,UTC,DST,CST
- 教务管理系统C++全部代码
- Gcc编译选项 -E
- Mono 之 单元测试
- Qt 实现Windows系统Win10 c++音量调节
- 什么是APS高级计划排程系统?APS计划排产有什么功能和作用?
- 约束满足问题(CSPs)和规划问题(Planning)区别
- MEncoder的基础用法—6.5. 编码为MPEG格式
- 【Usaco2009 gold 】拯救奶牛
- FFmpeg多媒体文件处理(ffmpeg打印音视频Meta信息)
- fatal: 引用不是一个树:a27a43...无法在子模组路径 'src/lib/ecl' 中检出 'a27...
- java敏感词关键词过滤