What’s inside the Block in Hyperledger Fabric?

What’s inside the Block in Hyperledger Fabric?

Deeptiman Pattnaik's photo
Deeptiman Pattnaik
·Jan 19, 2020·

5 min read

Subscribe to my newsletter and never miss my upcoming articles

In this article, I’ll try to demonstrate a BlockReader application written Go programming language that extracts the various segments of a block structure in Hyperledger Fabric.

Introduction

BlockReader application extract and showcase the complete data structure of a Block that contains several transactions. The application will require a transaction id to query the ledger to retrieve the associated block. Then the application will follow the Block data structure to read the content of the Block.

Github:https://github.com/Deeptiman/blockreader

What’s inside the Block

There are three sections of a Block that store transaction information in the ledger.

type Block struct {
      Header   *BlockHeader   
      Data     *BlockData     
      Metadata *BlockMetadata 
  }

BlockHeader

The header consist of Block number, copy of the previous block hash and the current block hash

type BlockHeader struct {
      Number         uint64
      PreviousHash    []byte
      DataHash        []byte
}

BlockData

The Data fields of a block are the essentials segment that contents the transaction details sorted in the byte array. We’ll extract hidden elements of the Data byte array in the course of this article.

type BlockData struct {
     Data [][]byte 
}

BlockMetaData

The MetaData fields contain the created time of the block, certificate details and signature of the block writer.

type BlockMetadata struct {
  Metadata   [][]byte
}

Envelope

The transaction is stored inside the envelope containing the transaction payload and sorted by the Ordering Service. The extraction process starts from the Envelope struct.

type Envelope struct {
     Payload   []byte 
     Signature []byte
}

Get Envelope from BlockData

func GetEnvelopeFromBlock(data []byte) (*common.Envelope, error){

  var err error
  env := &common.Envelope{}
  if err = proto.Unmarshal(data, env); err != nil {
    return nil, errors.Wrap(err, "error unmarshaling Envelope")
  }

  return env, nil
}

Payload

The Payload struct contains the ChannelHeader and SignatureHeader as Header fields.

type Payload struct {
     Header *Header 
     Data   []byte 
}

Get Payload from Envelope

payload := &common.Payload{}
err = proto.Unmarshal(envelope.Payload, payload)
if err != nil {
   return errors.WithMessage(err,"unmarshaling Payload error: ")
}

Payload Header

ChannelHeader It contains the basic channel information and chaincode info.

type ChannelHeader struct {
   Type      int32
   Version   int32 
   Timestamp *google_protobuf.Timestamp 
   ChannelId string 
   TxId      string 
   Epoch     uint64 
   Extension []byte 
}

/*

Type: It denotes the transaction type used. ["MESSAGE", "CONFIG", "CONFIG_UPDATE", "ENDORSER_TRANSACTION",
 "ORDERER_TRANSACTION", "DELIVER_SEEK_INFO", "CHAINCODE_PACKAGE"]

Version: The version number for protobuf used for serialization/de-serialization of the structures.

ChannelId: Channel name for the network

TxId: The transaction id for processing the transaction

Epoch: The fields are currently unused.

Extension: It contents Chaincode information that marshalled the ChaincodeHeaderExtension structure.

*/

Get the ChannelHeader from the Payload

channelHeader := &common.ChannelHeader{}
err := proto.Unmarshal(payload.Header.ChannelHeader, channelHeader)
if err != nil {
  return ChannelHeader{}, errors.WithMessage(err,"unmarshaling Channel Header error: ")
}

SignatureHeader This structure contains the Creator details that holds Mspid, identity certificate for the member service provider. The Creator field is the marshalled object of msp.SerializedIdentity.

type SignatureHeader struct {
   Creator []byte
   Nonce   []byte
}
signatureHeader := &common.SignatureHeader{}
err := proto.Unmarshal(payload.Header.SignatureHeader, signatureHeader)
if err != nil {
   return SignatureHeader{}, errors.WithMessage(err,"unmarshaling Signature Header error: ")
}

Get SignatureHeader from the Payload

signatureHeader := &common.SignatureHeader{}
err := proto.Unmarshal(payload.Header.SignatureHeader, signatureHeader)
if err != nil {
   return SignatureHeader{}, errors.WithMessage(err,"unmarshaling Signature Header error: ")
}

Get Creator from SignatureHeader

type SerializedIdentity struct {
   Mspid   string
   IdBytes []byte 
}

creator := &msp.SerializedIdentity{}
err = proto.Unmarshal(signatureHeader.Creator, creator)
if err != nil {
   return SignatureHeader{}, errors.WithMessage(err,"unmarshaling Creator error: ")
}

Extracting Identity Certificate for the MSP

uEnc := base64.URLEncoding.EncodeToString([]byte(creator.IdBytes))

  certText, err := base64.URLEncoding.DecodeString(uEnc)
  if err != nil {
    return SignatureHeader{}, errors.WithMessage(err,"Error decoding string: ")
  }

  end, _ := pem.Decode([]byte(string(certText)))
  if end == nil {
    return SignatureHeader{}, errors.New("Error Pem decoding: ")
  }
  cert, err := x509.ParseCertificate(end.Bytes)
  if err != nil {
    return SignatureHeader{}, errors.New("failed to parse certificate:: ")
  }    

  certificateJson :=  Certificate{
    Country:    cert.Issuer.Country,
    Organization:    cert.Issuer.Organization,
    OrganizationalUnit:  cert.Issuer.OrganizationalUnit,
    Locality:    cert.Issuer.Locality,
    Province:    cert.Issuer.Province,
    SerialNumber:    cert.Issuer.SerialNumber,
    NotBefore:    cert.NotBefore,
    NotAfter:    cert.NotAfter,                
  }

Payload Data

The Data field of the Payload contains the transaction details for the Block. And the TransactionAction structure holds Header and Payload that follows Signature Header for the submitted transaction and ChainCodeActionPayload containing the Chaincode invoked arguments, type of chaincode and the read/write set for the ChaincodeEndorseTransaction.

TransactionAction

type TransactionAction struct {
    Header  []byte 
    Payload []byte 
}

Header: It’s similar to the earlier SignatureHeader containing the identities details for submitting the transaction. Payload: It is the Marshalled object of ChainCodeActionPayload that contains various Chaincode actions performed during a transaction.

ChainCodeActionPayload

ChaincodeProposalPayload

It contains the Chaincode Input elements used during the transaction

type ChaincodeProposalPayload struct {
   Input    []byte
   TransientMap map[string][]byte
}

Input: It is the marshalled object of ChaincodeInvocationSpec TransientMap: It’s content quite sensitive data related to application-level confidentiality. The details are always removed from the transaction and never gets updated in the ledger.

ChaincodeInvocationSpec

It contents the chaincode information and the arguments used during the invocation.

type ChaincodeInvocationSpec struct {
     ChaincodeSpec  *ChaincodeSpec
}
type ChaincodeSpec struct {
     Type     ChaincodeSpec_Type 
     ChaincodeId *ChaincodeID
     Input       *ChaincodeInput
     Timeout     int32
}
type ChaincodeInput struct {
     Args        [][]byte
     Decorations map[string][]byte
}

Action

It is the marshalled object of ChaincodeEndorsedAction struct that content the Proposal Hash and the Read/Write set used for the Chaincode in the transaction.

type ChaincodeEndorsedAction struct {
     ProposalResponsePayload []byte
     Endorsements  []*Endorsement
}

type ProposalResponsePayload struct {
     ProposalHash []byte
     Extension    []byte   
}

Read/Write Set

The **Extension* field is the marshalled object of ChaincodeAction* that contains the Read and Write operation set performed for the transaction.

type ChaincodeAction struct {
     Results  []byte 
     Events   []byte 
     Response *Response 
}

Results: It’s the marshalled object of TxReadWriteSet struct that contains read/write set, Block Range info and the metadata.

type KVRWSet struct {
  Reads            []*KVRead         
  RangeQueriesInfo []*RangeQueryInfo 
  Writes           []*KVWrite  
  MetadataWrites   []*KVMetadataWrite      
}

type KVRead struct {
  Key     string   
  Version *Version 
}

type Version struct {
  BlockNum uint64 
  TxNum    uint64 
}

type KVWrite struct {
  Key      string 
  IsDelete bool   
  Value    []byte 
}

type RangeQueryInfo struct {
  StartKey     string 
  EndKey       string 
  ItrExhausted bool   
  ReadsInfo isRangeQueryInfo_ReadsInfo 
}

type KVMetadataWrite struct {
  Key      string
  Entries  []*KVMetadataEntry
}

type KVMetadataEntry struct {
  Name     string
  Value    []byte
}

So, this is the overview of extracting the Block data structure for a transaction id and understanding the core elements use cases. Please check the BlockReader application at Github and share your feedback.

I hope you find this article useful :)

Thanks

 
Share this