mirror of
https://github.com/tendermint/tendermint.git
synced 2026-02-10 22:10:11 +00:00
294 lines
8.7 KiB
Go
294 lines
8.7 KiB
Go
package metadata
|
|
|
|
import (
|
|
"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()
|
|
}
|
|
|
|
//-------------------------------------
|