Hyperledger Fabric从源码分析区块

学习区块链的这段时间内,一直在想区块本身到底包含了哪些数据,今天就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,区块的提交时间哈希

猜你喜欢

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