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

Hyperledger Fabric从源码分析区块

程序员文章站 2022-05-13 19:58:45
...

学习区块链的这段时间内,一直在想区块本身到底包含了哪些数据,今天就Fabric的源码来分析一下,一个区块底层到底包含了哪些数据。

1. 凭感觉猜想一个区块中可能会有哪些数据

首先会想到,区块本身存储的数据,它里面存储着一些交易。

其次,区块之间是以哈希连接成区块链的,按我之前的学习知识来看,它会需要包含上一个区块的hash。

另外,当然不能少了它本身数据的哈希。

还有很重要的一点,需要有一个区块的序号,是用来标记区块高度的。

暂时能想要的就是这些,看些fabric源码 release-1.4版本中的区块结构

2. 源码的区块结构

// This is finalized block structure to be shared among the orderer and peer
// Note that the BlockHeader chains to the previous BlockHeader, and the BlockData hash is embedded
// in the BlockHeader.  This makes it natural and obvious that the Data is included in the hash, but
// the Metadata is not.
type Block struct {
	Header               *BlockHeader   `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
	Data                 *BlockData     `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
	Metadata             *BlockMetadata `protobuf:"bytes,3,opt,name=metadata,proto3" json:"metadata,omitempty"`
  
  // 下面三个字段是protobuf搞的,暂时不用管
	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
	XXX_unrecognized     []byte         `json:"-"`
	XXX_sizecache        int32          `json:"-"`
}

源码中Block被分为三个主要模块

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

2.1 BlockHeader

// BlockHeader is the element of the block which forms the block chain
// The block header is hashed using the configured chain hashing algorithm
// over the ASN.1 encoding of the BlockHeader
type BlockHeader struct {
	Number               uint64   `protobuf:"varint,1,opt,name=number,proto3" json:"number,omitempty"`
	PreviousHash         []byte   `protobuf:"bytes,2,opt,name=previous_hash,json=previousHash,proto3" json:"previous_hash,omitempty"`
	DataHash             []byte   `protobuf:"bytes,3,opt,name=data_hash,json=dataHash,proto3" json:"data_hash,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

BlockHeader是构成区块链的Block字段。它主要有三个字段组成

  1. Number,区块序号,用来标识区块高度
  2. PreviousHash,前一个区块的哈希
  3. DataHash,当前区块中包含的所有交易的hash,也就是BlockData的hash

这里主要将一下这两个哈希字段的作用。

DataHash表示的是BlockData的hash,并不包含BlockMetaData区块元数据部分,也就是区块包含的交易的哈希。

block.Header.DataHash = block.Data.Hash()
// 这条赋值语句可以很清晰的看到,Header字段的DataHash,就是Block.Data的Hash

PreviousHash表示的是前一个区块的哈希,在验证区块哈希的时候这个字段就会派上用场用来验证哈希值。

func (ci *ChainInspector) validateHashPointer(block *common.Block, prevHash []byte) {
	if prevHash == nil {
		return
	}
  // 判断block的PreviousHash是否和传进来的prevHash相等
	if bytes.Equal(block.Header.PreviousHash, prevHash) {
		return
	}
	ci.Logger.Panicf("Claimed previous hash of block [%d] is %x but actual previous hash is %x",
		block.Header.Number, block.Header.PreviousHash, prevHash)
}

看一下prevHash是怎么得到的

// prevHash调用BlockHeader的Hash方法
prevHash = block.Header.Hash()

// Hash方法对BlockHeader的Bytes方法得到的结果进行哈希
// 那么只要看下Bytes方法是怎么哈希的就好了
func (b *BlockHeader) Hash() []byte {
	return util.ComputeSHA256(b.Bytes())
}

func (b *BlockHeader) Bytes() []byte {
  // 加入了Header字段中的PreviousHash和DataHash
	asn1Header := asn1Header{
		PreviousHash和: b.PreviousHash,
		DataHash:     b.DataHash,
	}
  // 判断Header字段中的Number字段是否合法,如果合法就加到asn1结构字段中
	if b.Number > uint64(math.MaxInt64) {
		panic(fmt.Errorf("Golang does not currently support encoding uint64 to asn1"))
	} else {
		asn1Header.Number = int64(b.Number)
	}
  //对结果进行序列化得到[]byte对象返回
	result, err := asn1.Marshal(asn1Header)
	if err != nil {
		// Errors should only arise for types which cannot be encoded, since the
		// BlockHeader type is known a-priori to contain only encodable types, an
		// error here is fatal and should not be propogated
		panic(err)
	}
	return result
}

例如当前区块2,它的PreviousHash字段存储的就是区块1的哈希,那么根据源码分析可以看到下面类似的公式。

区块2的PreviousHash = 区块1的哈希 = hash(区块1的PreviousHash+区块1的DataHash+区块1的Number)

2.2 BlockData

type BlockData struct {
	Data                 [][]byte `protobuf:"bytes,1,rep,name=data,proto3" json:"data,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

BlockData只有一个字段Data,它的类型是[ ] [ ] byte,是一个字节类型的二维切片,[ ]byte存储的是一个交易,那么[ ] [ ] byte存储的就是一组交易。

这组交易数据在区块创建的时候就会被写入

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
}

2.3 BlockMetaData

type BlockMetadata struct {
	Metadata             [][]byte `protobuf:"bytes,1,rep,name=metadata,proto3" json:"metadata,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

BlockMetaData只有一个字段Metadata,它的类型是[ ] [ ] byte,是一个字节类型的二维切片,那么这个切片里存储的到底是哪些数据,就要跟踪源码去看看了。

还是看上面这个NewBlock函数,第一行它调用了common.NewBlock(blockNum, previousHash)来创建一个block,看下这个函数的源码

// NewBlock construct a block with no data and no metadata.
func NewBlock(seqNum uint64, previousHash []byte) *Block {
	block := &Block{}
	block.Header = &BlockHeader{}
	block.Header.Number = seqNum
	block.Header.PreviousHash = previousHash
	block.Data = &BlockData{}

	var metadataContents [][]byte
	for i := 0; i < len(BlockMetadataIndex_name); i++ {
    // 这部分在构造metadata的切片,但是他们没有数据
    // 切片的长度是BlockMetadataIndex_name
		metadataContents = append(metadataContents, []byte{})
	}
	block.Metadata = &BlockMetadata{Metadata: metadataContents}

	return block
}

// BlockMetadataIndex_name顾名思义,是区块元数据的下标名,这里有5个下标
// 那么也就意味着元数据里面包含5条数据,根据name我们就可以推测出5个数据到底是什么
var BlockMetadataIndex_name = map[int32]string{
	0: "SIGNATURES",
	1: "LAST_CONFIG",
	2: "TRANSACTIONS_FILTER",
	3: "ORDERER",
	4: "COMMIT_HASH",
}

根据上面的源码分析,可以推测出BlockMetadata存储的是哪些数据

  1. SIGNATURES,区块签名

  2. LAST_CONFIG,最新配置区块的区块号,区块更新以后需要知道上一个区块的配置,加入10号区块往后发生了更新,那么这里就会存储10

  3. TRANSACTIONS_FILTER,主要是存储了一些交易的vaild/invalid信息,假如世界状态数据库发生了问题,或者是查询交易是否合法的时候,不用再去验证一遍是否合法,通过这个数据可以就可以查到。因此这个数据段只做一个过滤的操作,过滤合法的交易,非法的交易,它本身的结构类似一个标志位,如果是合法的就是0,如果是非法的就是其他对应的数字,这个标志位对应在每个交易的存储下标上。

  4. Orderer,Orderer配置信息,包括orderer的签名及证书等待,因为排序节点在广播区块时,可能是不同的orderer去广播同一个区块,组织 A的锚节点可能从orderer1中拿到了区块,组织B的锚节点可能从orderer2中拿到了区块,但是orderer1和orderer2分别用自己的私钥对其进行了签名,如果把这个数据存储在BlockData字段,那么可能就会导致BlockData数据不一致

  5. COMMIT_HASH,区块的提交时间哈希

相关标签: 区块链 区块链