欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Hyperledger Fabric从源码分析交易

程序员文章站 2024-01-27 14:22:28
...

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

  1. BlockHeader, 区块头
  2. BlockData,区块数据
  3. BlockMetaData,区块元数据

上篇文章中已经对区块头,区块数据,区块元数据各个字段的内容进行了分析。我们知道BlockData字段是一个切片,存储的是一条条交易,但是并没有对交易具体的结构进行剖析。那么这篇文章就来分析一下一条交易里面会有哪些东西。

1. 先猜想一下一条交易会有哪些东西


老规矩,先猜想一下一条交易会有哪些数据信息。下面猜想的肯定有对的,也有不对的,这也是一个学习的思路。

先来看下一个交易流程图,方便我们做猜想,我在之前的文章 Hyperledger Fabric的网络拓扑图与交易流程中画了一张图来演示Fabric交易的整体流程,现在再把这张图拿出来看一下。
Hyperledger 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. 思维导图总结


Hyperledger Fabric从源码分析交易

相关标签: 区块链