Hyperledger Fabric从源码分析交易

在上一章Hyperledger Fabric从源码分析区块结构中提到了区块的概念,并从源码角度对区块的结构进行了剖析,知道一个简单的区块包含了下面几个部分

  1. BlockHeader, 区块头
  2. BlockData,区块数据
  3. 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 Payload
	Payload []byte 
	// A signature by the creator specified in the Payload header
	Signature            []byte   
	XXX_NoUnkeyedLiteral struct{} 
	XXX_unrecognized     []byte   
	XXX_sizecache        int32    
}

Envelope直译是信封的意思,可以形象地理解,一条交易是一个信封,在排序节点排序的时候不会去拆开信封看,也就是说排序节点不会关心交易的数据,它的作用就是排序(共识)。而当交易被打包成区块分发的各个记账节点时,记账节点就会去拆开这个信封,也就是会去关心交易的数据,因为它要记账,另外还有做验证这之类的操作。所以Fabric中用Envelope这个单词来表示一个交易。它内部有两个字段

  1. Payload,直译过来就是有效载荷,抽象一下,payload 可以理解为一系列信息中最为关键的信息,也就是说交易的关键信息都存储在Payload当中,它是一个序列化的字节数组
  2. 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 replay
	Header *Header 
	// Data, the encoding of which is defined by the type in the header
	Data                 []byte   
	XXX_NoUnkeyedLiteral struct{} 
	XXX_unrecognized     []byte   
	XXX_sizecache        int32    
}

Header字段


首先看下Payload结构体,它有两个字段

  1. Header,Payload的头部字段信息
  2. Data,Payload的具体数据,说到底,它就是交易transaction的序列化信息

看下Header字段包含了哪些信息,包括它各字段的序列化对象

type Header struct {
  // 顾名思义,交易通道的Header
	ChannelHeader        []byte   
  // 顾名思义,交易签名的Header
	SignatureHeader      []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表示这条消息绑定的通道的ID
	ChannelId string 
	// TxID表示交易的唯一标识符,唯一ID
	TxId string 
	// Epoch直译表示新纪元,epoch表示生成此Header的纪元,他是根据Block height定义的
	Epoch uint64 
	// Extension是根据Type字段可以附加的扩展部分
	Extension []byte
  // TlsCertHash表示客户端TLS证书的哈希,如果使用的是mutual TLS
	TlsCertHash          []byte   
	XXX_NoUnkeyedLiteral struct{} 
	XXX_unrecognized     []byte   
	XXX_sizecache        int32    
}

type SignatureHeader struct {
  // Creator 表示消息的创建者,他是一个序列化的身份信息
	Creator []byte 
  // Nonce是只使用一次的随机数字,可用于检测replay attacks
  // Creator和Nonce两个字段可以用来生成一个TxID
	Nonce                []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的header
	Header []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 
  // 包含了链码调用生成的events
	Events []byte 
  // 包含了链码调用生成的Response
	Response *Response
  // 链码ID
	ChaincodeId *ChaincodeID 
  // 包含了链码调用生成的token expectation
	TokenExpectation     *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. 思维导图总结


在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/lvyibin890/article/details/106236077