package metadata import ( "bytes" "errors" "fmt" "time" gogotypes "github.com/gogo/protobuf/types" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/crypto/merkle" tmbytes "github.com/tendermint/tendermint/libs/bytes" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/tendermint/tendermint/version" ) const ( // MaxHeaderBytes is a maximum header size. // NOTE: Because app hash can be of arbitrary size, the header is therefore not // capped in size and thus this number should be seen as a soft max MaxHeaderBytes int64 = 626 // MaxOverheadForBlock - maximum overhead to encode a block (up to // MaxBlockSizeBytes in size) not including it's parts except Data. // This means it also excludes the overhead for individual transactions. // // Uvarint length of MaxBlockSizeBytes: 4 bytes // 2 fields (2 embedded): 2 bytes // Uvarint length of Data.Txs: 4 bytes // Data.Txs field: 1 byte MaxOverheadForBlock int64 = 11 // MaxChainIDLen is a maximum length of the chain ID. MaxChainIDLen = 50 // MaxBlockSizeBytes is the maximum permitted size of the blocks. MaxBlockSizeBytes = 104857600 // 100MB ) // Header defines the structure of a Tendermint block header. // NOTE: changes to the Header should be duplicated in: // - header.Hash() // - abci.Header // - https://github.com/tendermint/spec/blob/master/spec/blockchain/blockchain.md type Header struct { // basic block info Version version.Consensus `json:"version"` ChainID string `json:"chain_id"` Height int64 `json:"height"` Time time.Time `json:"time"` // prev block info LastBlockID BlockID `json:"last_block_id"` // hashes of block data LastCommitHash tmbytes.HexBytes `json:"last_commit_hash"` // commit from validators from the last block DataHash tmbytes.HexBytes `json:"data_hash"` // transactions // hashes from the app output from the prev block ValidatorsHash tmbytes.HexBytes `json:"validators_hash"` // validators for the current block NextValidatorsHash tmbytes.HexBytes `json:"next_validators_hash"` // validators for the next block ConsensusHash tmbytes.HexBytes `json:"consensus_hash"` // consensus params for current block AppHash tmbytes.HexBytes `json:"app_hash"` // state after txs from the previous block // root hash of all results from the txs from the previous block // see `deterministicResponseDeliverTx` to understand which parts of a tx is hashed into here LastResultsHash tmbytes.HexBytes `json:"last_results_hash"` // consensus info EvidenceHash tmbytes.HexBytes `json:"evidence_hash"` // evidence included in the block ProposerAddress crypto.Address `json:"proposer_address"` // original proposer of the block } // Populate the Header with state-derived data. // Call this after MakeBlock to complete the Header. func (h *Header) Populate( version version.Consensus, chainID string, timestamp time.Time, lastBlockID BlockID, valHash, nextValHash []byte, consensusHash, appHash, lastResultsHash []byte, proposerAddress crypto.Address, ) { h.Version = version h.ChainID = chainID h.Time = timestamp h.LastBlockID = lastBlockID h.ValidatorsHash = valHash h.NextValidatorsHash = nextValHash h.ConsensusHash = consensusHash h.AppHash = appHash h.LastResultsHash = lastResultsHash h.ProposerAddress = proposerAddress } // ValidateBasic performs stateless validation on a Header returning an error // if any validation fails. // // NOTE: Timestamp validation is subtle and handled elsewhere. func (h Header) ValidateBasic() error { if h.Version.Block != version.BlockProtocol { return fmt.Errorf("block protocol is incorrect: got: %d, want: %d ", h.Version.Block, version.BlockProtocol) } if len(h.ChainID) > MaxChainIDLen { return fmt.Errorf("chainID is too long; got: %d, max: %d", len(h.ChainID), MaxChainIDLen) } if h.Height < 0 { return errors.New("negative Height") } else if h.Height == 0 { return errors.New("zero Height") } if err := h.LastBlockID.ValidateBasic(); err != nil { return fmt.Errorf("wrong LastBlockID: %w", err) } if err := ValidateHash(h.LastCommitHash); err != nil { return fmt.Errorf("wrong LastCommitHash: %v", err) } if err := ValidateHash(h.DataHash); err != nil { return fmt.Errorf("wrong DataHash: %v", err) } if err := ValidateHash(h.EvidenceHash); err != nil { return fmt.Errorf("wrong EvidenceHash: %v", err) } if len(h.ProposerAddress) != crypto.AddressSize { return fmt.Errorf( "invalid ProposerAddress length; got: %d, expected: %d", len(h.ProposerAddress), crypto.AddressSize, ) } // Basic validation of hashes related to application data. // Will validate fully against state in state#ValidateBlock. if err := ValidateHash(h.ValidatorsHash); err != nil { return fmt.Errorf("wrong ValidatorsHash: %v", err) } if err := ValidateHash(h.NextValidatorsHash); err != nil { return fmt.Errorf("wrong NextValidatorsHash: %v", err) } if err := ValidateHash(h.ConsensusHash); err != nil { return fmt.Errorf("wrong ConsensusHash: %v", err) } // NOTE: AppHash is arbitrary length if err := ValidateHash(h.LastResultsHash); err != nil { return fmt.Errorf("wrong LastResultsHash: %v", err) } return nil } // Hash returns the hash of the header. // It computes a Merkle tree from the header fields // ordered as they appear in the Header. // Returns nil if ValidatorHash is missing, // since a Header is not valid unless there is // a ValidatorsHash (corresponding to the validator set). func (h *Header) Hash() tmbytes.HexBytes { if h == nil || len(h.ValidatorsHash) == 0 { return nil } hpb := h.Version.ToProto() hbz, err := hpb.Marshal() if err != nil { return nil } pbt, err := gogotypes.StdTimeMarshal(h.Time) if err != nil { return nil } pbbi := h.LastBlockID.ToProto() bzbi, err := pbbi.Marshal() if err != nil { return nil } return merkle.HashFromByteSlices([][]byte{ hbz, CdcEncode(h.ChainID), CdcEncode(h.Height), pbt, bzbi, CdcEncode(h.LastCommitHash), CdcEncode(h.DataHash), CdcEncode(h.ValidatorsHash), CdcEncode(h.NextValidatorsHash), CdcEncode(h.ConsensusHash), CdcEncode(h.AppHash), CdcEncode(h.LastResultsHash), CdcEncode(h.EvidenceHash), CdcEncode(h.ProposerAddress), }) } // StringIndented returns an indented string representation of the header. func (h *Header) StringIndented(indent string) string { if h == nil { return "nil-Header" } return fmt.Sprintf(`Header{ %s Version: %v %s ChainID: %v %s Height: %v %s Time: %v %s LastBlockID: %v %s LastCommit: %v %s Data: %v %s Validators: %v %s NextValidators: %v %s App: %v %s Consensus: %v %s Results: %v %s Evidence: %v %s Proposer: %v %s}#%v`, indent, h.Version, indent, h.ChainID, indent, h.Height, indent, h.Time, indent, h.LastBlockID, indent, h.LastCommitHash, indent, h.DataHash, indent, h.ValidatorsHash, indent, h.NextValidatorsHash, indent, h.AppHash, indent, h.ConsensusHash, indent, h.LastResultsHash, indent, h.EvidenceHash, indent, h.ProposerAddress, indent, h.Hash()) } // ToProto converts Header to protobuf func (h *Header) ToProto() *tmproto.Header { if h == nil { return nil } return &tmproto.Header{ Version: h.Version.ToProto(), ChainID: h.ChainID, Height: h.Height, Time: h.Time, LastBlockId: h.LastBlockID.ToProto(), ValidatorsHash: h.ValidatorsHash, NextValidatorsHash: h.NextValidatorsHash, ConsensusHash: h.ConsensusHash, AppHash: h.AppHash, DataHash: h.DataHash, EvidenceHash: h.EvidenceHash, LastResultsHash: h.LastResultsHash, LastCommitHash: h.LastCommitHash, ProposerAddress: h.ProposerAddress, } } // FromProto sets a protobuf Header to the given pointer. // It returns an error if the header is invalid. func HeaderFromProto(ph *tmproto.Header) (Header, error) { if ph == nil { return Header{}, errors.New("nil Header") } h := new(Header) bi, err := BlockIDFromProto(&ph.LastBlockId) if err != nil { return Header{}, err } h.Version = version.Consensus{Block: ph.Version.Block, App: ph.Version.App} h.ChainID = ph.ChainID h.Height = ph.Height h.Time = ph.Time h.Height = ph.Height h.LastBlockID = *bi h.ValidatorsHash = ph.ValidatorsHash h.NextValidatorsHash = ph.NextValidatorsHash h.ConsensusHash = ph.ConsensusHash h.AppHash = ph.AppHash h.DataHash = ph.DataHash h.EvidenceHash = ph.EvidenceHash h.LastResultsHash = ph.LastResultsHash h.LastCommitHash = ph.LastCommitHash h.ProposerAddress = ph.ProposerAddress return *h, h.ValidateBasic() } //------------------------------------- //----------------------------------------------------------------------------- // SignedHeader is a header along with the commits that prove it. type SignedHeader struct { *Header `json:"header"` Commit *Commit `json:"commit"` } // ValidateBasic does basic consistency checks and makes sure the header // and commit are consistent. // // NOTE: This does not actually check the cryptographic signatures. Make sure // to use a Verifier to validate the signatures actually provide a // significantly strong proof for this header's validity. func (sh SignedHeader) ValidateBasic(chainID string) error { if sh.Header == nil { return errors.New("missing header") } if sh.Commit == nil { return errors.New("missing commit") } if err := sh.Header.ValidateBasic(); err != nil { return fmt.Errorf("invalid header: %w", err) } if err := sh.Commit.ValidateBasic(); err != nil { return fmt.Errorf("invalid commit: %w", err) } if sh.ChainID != chainID { return fmt.Errorf("header belongs to another chain %q, not %q", sh.ChainID, chainID) } // Make sure the header is consistent with the commit. if sh.Commit.Height != sh.Height { return fmt.Errorf("header and commit height mismatch: %d vs %d", sh.Height, sh.Commit.Height) } if hhash, chash := sh.Header.Hash(), sh.Commit.BlockID.Hash; !bytes.Equal(hhash, chash) { return fmt.Errorf("commit signs block %X, header is block %X", chash, hhash) } return nil } // String returns a string representation of SignedHeader. func (sh SignedHeader) String() string { return sh.StringIndented("") } // StringIndented returns an indented string representation of SignedHeader. // // Header // Commit func (sh SignedHeader) StringIndented(indent string) string { return fmt.Sprintf(`SignedHeader{ %s %v %s %v %s}`, indent, sh.Header.StringIndented(indent+" "), indent, sh.Commit.StringIndented(indent+" "), indent) } // ToProto converts SignedHeader to protobuf func (sh *SignedHeader) ToProto() *tmproto.SignedHeader { if sh == nil { return nil } psh := new(tmproto.SignedHeader) if sh.Header != nil { psh.Header = sh.Header.ToProto() } if sh.Commit != nil { psh.Commit = sh.Commit.ToProto() } return psh } // FromProto sets a protobuf SignedHeader to the given pointer. // It returns an error if the header or the commit is invalid. func SignedHeaderFromProto(shp *tmproto.SignedHeader) (*SignedHeader, error) { if shp == nil { return nil, errors.New("nil SignedHeader") } sh := new(SignedHeader) if shp.Header != nil { h, err := HeaderFromProto(shp.Header) if err != nil { return nil, err } sh.Header = &h } if shp.Commit != nil { c, err := CommitFromProto(shp.Commit) if err != nil { return nil, err } sh.Commit = c } return sh, nil }