package types import ( "bytes" "errors" "fmt" "strings" "sync" "time" "github.com/tendermint/tendermint/crypto/merkle" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/version" ) const ( // MaxHeaderBytes is a maximum header size (including amino overhead). MaxHeaderBytes int64 = 533 // MaxAminoOverheadForBlock - maximum amino overhead to encode a block (up to // MaxBlockSizeBytes in size) not including it's parts except Data. // // Uvarint length of MaxBlockSizeBytes: 4 bytes // 2 fields (2 embedded): 2 bytes // Uvarint length of Data.Txs: 4 bytes // Data.Txs field: 1 byte MaxAminoOverheadForBlock int64 = 11 ) // Block defines the atomic unit of a Tendermint blockchain. type Block struct { mtx sync.Mutex Header `json:"header"` Data `json:"data"` Evidence EvidenceData `json:"evidence"` LastCommit *Commit `json:"last_commit"` } // MakeBlock returns a new block with an empty header, except what can be // computed from itself. // It populates the same set of fields validated by ValidateBasic. func MakeBlock(height int64, txs []Tx, lastCommit *Commit, evidence []Evidence) *Block { block := &Block{ Header: Header{ Height: height, NumTxs: int64(len(txs)), }, Data: Data{ Txs: txs, }, Evidence: EvidenceData{Evidence: evidence}, LastCommit: lastCommit, } block.fillHeader() return block } // ValidateBasic performs basic validation that doesn't involve state data. // It checks the internal consistency of the block. func (b *Block) ValidateBasic() error { if b == nil { return errors.New("Nil blocks are invalid") } b.mtx.Lock() defer b.mtx.Unlock() if b.Height < 0 { return fmt.Errorf( "Negative Block.Header.Height: %v", b.Height, ) } newTxs := int64(len(b.Data.Txs)) if b.NumTxs != newTxs { return fmt.Errorf( "Wrong Block.Header.NumTxs. Expected %v, got %v", newTxs, b.NumTxs, ) } if !bytes.Equal(b.LastCommitHash, b.LastCommit.Hash()) { return fmt.Errorf( "Wrong Block.Header.LastCommitHash. Expected %v, got %v", b.LastCommitHash, b.LastCommit.Hash(), ) } if b.Header.Height != 1 { if err := b.LastCommit.ValidateBasic(); err != nil { return err } } if !bytes.Equal(b.DataHash, b.Data.Hash()) { return fmt.Errorf( "Wrong Block.Header.DataHash. Expected %v, got %v", b.DataHash, b.Data.Hash(), ) } if !bytes.Equal(b.EvidenceHash, b.Evidence.Hash()) { return fmt.Errorf( "Wrong Block.Header.EvidenceHash. Expected %v, got %v", b.EvidenceHash, b.Evidence.Hash(), ) } return nil } // fillHeader fills in any remaining header fields that are a function of the block data func (b *Block) fillHeader() { if b.LastCommitHash == nil { b.LastCommitHash = b.LastCommit.Hash() } if b.DataHash == nil { b.DataHash = b.Data.Hash() } if b.EvidenceHash == nil { b.EvidenceHash = b.Evidence.Hash() } } // Hash computes and returns the block hash. // If the block is incomplete, block hash is nil for safety. func (b *Block) Hash() cmn.HexBytes { if b == nil { return nil } b.mtx.Lock() defer b.mtx.Unlock() if b == nil || b.LastCommit == nil { return nil } b.fillHeader() return b.Header.Hash() } // MakePartSet returns a PartSet containing parts of a serialized block. // This is the form in which the block is gossipped to peers. // CONTRACT: partSize is greater than zero. func (b *Block) MakePartSet(partSize int) *PartSet { if b == nil { return nil } b.mtx.Lock() defer b.mtx.Unlock() // We prefix the byte length, so that unmarshaling // can easily happen via a reader. bz, err := cdc.MarshalBinaryLengthPrefixed(b) if err != nil { panic(err) } return NewPartSetFromData(bz, partSize) } // HashesTo is a convenience function that checks if a block hashes to the given argument. // Returns false if the block is nil or the hash is empty. func (b *Block) HashesTo(hash []byte) bool { if len(hash) == 0 { return false } if b == nil { return false } return bytes.Equal(b.Hash(), hash) } // Size returns size of the block in bytes. func (b *Block) Size() int { bz, err := cdc.MarshalBinaryBare(b) if err != nil { return 0 } return len(bz) } // String returns a string representation of the block func (b *Block) String() string { return b.StringIndented("") } // StringIndented returns a string representation of the block func (b *Block) StringIndented(indent string) string { if b == nil { return "nil-Block" } return fmt.Sprintf(`Block{ %s %v %s %v %s %v %s %v %s}#%v`, indent, b.Header.StringIndented(indent+" "), indent, b.Data.StringIndented(indent+" "), indent, b.Evidence.StringIndented(indent+" "), indent, b.LastCommit.StringIndented(indent+" "), indent, b.Hash()) } // StringShort returns a shortened string representation of the block func (b *Block) StringShort() string { if b == nil { return "nil-Block" } return fmt.Sprintf("Block#%v", b.Hash()) } //----------------------------------------------------------------------------- // MaxDataBytes returns the maximum size of block's data. // // XXX: Panics on negative result. func MaxDataBytes(maxBytes int64, valsCount, evidenceCount int) int64 { maxDataBytes := maxBytes - MaxAminoOverheadForBlock - MaxHeaderBytes - int64(valsCount)*MaxVoteBytes - int64(evidenceCount)*MaxEvidenceBytes if maxDataBytes < 0 { panic(fmt.Sprintf( "Negative MaxDataBytes. BlockSize.MaxBytes=%d is too small to accommodate header&lastCommit&evidence=%d", maxBytes, -(maxDataBytes - maxBytes), )) } return maxDataBytes } // MaxDataBytesUnknownEvidence returns the maximum size of block's data when // evidence count is unknown. MaxEvidenceBytesPerBlock will be used as the size // of evidence. // // XXX: Panics on negative result. func MaxDataBytesUnknownEvidence(maxBytes int64, valsCount int) int64 { maxDataBytes := maxBytes - MaxAminoOverheadForBlock - MaxHeaderBytes - int64(valsCount)*MaxVoteBytes - MaxEvidenceBytesPerBlock(maxBytes) if maxDataBytes < 0 { panic(fmt.Sprintf( "Negative MaxDataBytesUnknownEvidence. BlockSize.MaxBytes=%d is too small to accommodate header&lastCommit&evidence=%d", maxBytes, -(maxDataBytes - maxBytes), )) } return maxDataBytes } //----------------------------------------------------------------------------- // Header defines the structure of a Tendermint block header. // NOTE: changes to the Header should be duplicated in: // - header.Hash() // - abci.Header // - /docs/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"` NumTxs int64 `json:"num_txs"` TotalTxs int64 `json:"total_txs"` // prev block info LastBlockID BlockID `json:"last_block_id"` // hashes of block data LastCommitHash cmn.HexBytes `json:"last_commit_hash"` // commit from validators from the last block DataHash cmn.HexBytes `json:"data_hash"` // transactions // hashes from the app output from the prev block ValidatorsHash cmn.HexBytes `json:"validators_hash"` // validators for the current block NextValidatorsHash cmn.HexBytes `json:"next_validators_hash"` // validators for the next block ConsensusHash cmn.HexBytes `json:"consensus_hash"` // consensus params for current block AppHash cmn.HexBytes `json:"app_hash"` // state after txs from the previous block LastResultsHash cmn.HexBytes `json:"last_results_hash"` // root hash of all results from the txs from the previous block // consensus info EvidenceHash cmn.HexBytes `json:"evidence_hash"` // evidence included in the block ProposerAddress 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, totalTxs int64, valHash, nextValHash []byte, consensusHash, appHash, lastResultsHash []byte, proposerAddress Address, ) { h.Version = version h.ChainID = chainID h.Time = timestamp h.LastBlockID = lastBlockID h.TotalTxs = totalTxs h.ValidatorsHash = valHash h.NextValidatorsHash = nextValHash h.ConsensusHash = consensusHash h.AppHash = appHash h.LastResultsHash = lastResultsHash h.ProposerAddress = proposerAddress } // 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() cmn.HexBytes { if h == nil || len(h.ValidatorsHash) == 0 { return nil } return merkle.SimpleHashFromByteSlices([][]byte{ cdcEncode(h.Version), cdcEncode(h.ChainID), cdcEncode(h.Height), cdcEncode(h.Time), cdcEncode(h.NumTxs), cdcEncode(h.TotalTxs), cdcEncode(h.LastBlockID), 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 a 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 NumTxs: %v %s TotalTxs: %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.NumTxs, indent, h.TotalTxs, 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()) } //------------------------------------- // Commit contains the evidence that a block was committed by a set of validators. // NOTE: Commit is empty for height 1, but never nil. type Commit struct { // NOTE: The Precommits are in order of address to preserve the bonded ValidatorSet order. // Any peer with a block can gossip precommits by index with a peer without recalculating the // active ValidatorSet. BlockID BlockID `json:"block_id"` Precommits []*Vote `json:"precommits"` // Volatile firstPrecommit *Vote hash cmn.HexBytes bitArray *cmn.BitArray } // FirstPrecommit returns the first non-nil precommit in the commit. // If all precommits are nil, it returns an empty precommit with height 0. func (commit *Commit) FirstPrecommit() *Vote { if len(commit.Precommits) == 0 { return nil } if commit.firstPrecommit != nil { return commit.firstPrecommit } for _, precommit := range commit.Precommits { if precommit != nil { commit.firstPrecommit = precommit return precommit } } return &Vote{ Type: PrecommitType, } } // Height returns the height of the commit func (commit *Commit) Height() int64 { if len(commit.Precommits) == 0 { return 0 } return commit.FirstPrecommit().Height } // Round returns the round of the commit func (commit *Commit) Round() int { if len(commit.Precommits) == 0 { return 0 } return commit.FirstPrecommit().Round } // Type returns the vote type of the commit, which is always VoteTypePrecommit func (commit *Commit) Type() byte { return byte(PrecommitType) } // Size returns the number of votes in the commit func (commit *Commit) Size() int { if commit == nil { return 0 } return len(commit.Precommits) } // BitArray returns a BitArray of which validators voted in this commit func (commit *Commit) BitArray() *cmn.BitArray { if commit.bitArray == nil { commit.bitArray = cmn.NewBitArray(len(commit.Precommits)) for i, precommit := range commit.Precommits { // TODO: need to check the BlockID otherwise we could be counting conflicts, // not just the one with +2/3 ! commit.bitArray.SetIndex(i, precommit != nil) } } return commit.bitArray } // GetByIndex returns the vote corresponding to a given validator index func (commit *Commit) GetByIndex(index int) *Vote { return commit.Precommits[index] } // IsCommit returns true if there is at least one vote func (commit *Commit) IsCommit() bool { return len(commit.Precommits) != 0 } // ValidateBasic performs basic validation that doesn't involve state data. // Does not actually check the cryptographic signatures. func (commit *Commit) ValidateBasic() error { if commit.BlockID.IsZero() { return errors.New("Commit cannot be for nil block") } if len(commit.Precommits) == 0 { return errors.New("No precommits in commit") } height, round := commit.Height(), commit.Round() // Validate the precommits. for _, precommit := range commit.Precommits { // It's OK for precommits to be missing. if precommit == nil { continue } // Ensure that all votes are precommits. if precommit.Type != PrecommitType { return fmt.Errorf("Invalid commit vote. Expected precommit, got %v", precommit.Type) } // Ensure that all heights are the same. if precommit.Height != height { return fmt.Errorf("Invalid commit precommit height. Expected %v, got %v", height, precommit.Height) } // Ensure that all rounds are the same. if precommit.Round != round { return fmt.Errorf("Invalid commit precommit round. Expected %v, got %v", round, precommit.Round) } } return nil } // Hash returns the hash of the commit func (commit *Commit) Hash() cmn.HexBytes { if commit == nil { return nil } if commit.hash == nil { bs := make([][]byte, len(commit.Precommits)) for i, precommit := range commit.Precommits { bs[i] = cdcEncode(precommit) } commit.hash = merkle.SimpleHashFromByteSlices(bs) } return commit.hash } // StringIndented returns a string representation of the commit func (commit *Commit) StringIndented(indent string) string { if commit == nil { return "nil-Commit" } precommitStrings := make([]string, len(commit.Precommits)) for i, precommit := range commit.Precommits { precommitStrings[i] = precommit.String() } return fmt.Sprintf(`Commit{ %s BlockID: %v %s Precommits: %s %v %s}#%v`, indent, commit.BlockID, indent, indent, strings.Join(precommitStrings, "\n"+indent+" "), indent, commit.hash) } //----------------------------------------------------------------------------- // 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 { // Make sure the header is consistent with the commit. if sh.Header == nil { return errors.New("SignedHeader missing header.") } if sh.Commit == nil { return errors.New("SignedHeader missing commit (precommit votes).") } // Check ChainID. if sh.ChainID != chainID { return fmt.Errorf("Header belongs to another chain '%s' not '%s'", sh.ChainID, chainID) } // Check Height. if sh.Commit.Height() != sh.Height { return fmt.Errorf("SignedHeader header and commit height mismatch: %v vs %v", sh.Height, sh.Commit.Height()) } // Check Hash. hhash := sh.Hash() chash := sh.Commit.BlockID.Hash if !bytes.Equal(hhash, chash) { return fmt.Errorf("SignedHeader commit signs block %X, header is block %X", chash, hhash) } // ValidateBasic on the Commit. err := sh.Commit.ValidateBasic() if err != nil { return cmn.ErrorWrap(err, "commit.ValidateBasic failed during SignedHeader.ValidateBasic") } return nil } func (sh SignedHeader) String() string { return sh.StringIndented("") } // StringIndented returns a string representation of the SignedHeader. 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) } //----------------------------------------------------------------------------- // Data contains the set of transactions included in the block type Data struct { // Txs that will be applied by state @ block.Height+1. // NOTE: not all txs here are valid. We're just agreeing on the order first. // This means that block.AppHash does not include these txs. Txs Txs `json:"txs"` // Volatile hash cmn.HexBytes } // Hash returns the hash of the data func (data *Data) Hash() cmn.HexBytes { if data == nil { return (Txs{}).Hash() } if data.hash == nil { data.hash = data.Txs.Hash() // NOTE: leaves of merkle tree are TxIDs } return data.hash } // StringIndented returns a string representation of the transactions func (data *Data) StringIndented(indent string) string { if data == nil { return "nil-Data" } txStrings := make([]string, cmn.MinInt(len(data.Txs), 21)) for i, tx := range data.Txs { if i == 20 { txStrings[i] = fmt.Sprintf("... (%v total)", len(data.Txs)) break } txStrings[i] = fmt.Sprintf("%X (%d bytes)", tx.Hash(), len(tx)) } return fmt.Sprintf(`Data{ %s %v %s}#%v`, indent, strings.Join(txStrings, "\n"+indent+" "), indent, data.hash) } //----------------------------------------------------------------------------- // EvidenceData contains any evidence of malicious wrong-doing by validators type EvidenceData struct { Evidence EvidenceList `json:"evidence"` // Volatile hash cmn.HexBytes } // Hash returns the hash of the data. func (data *EvidenceData) Hash() cmn.HexBytes { if data.hash == nil { data.hash = data.Evidence.Hash() } return data.hash } // StringIndented returns a string representation of the evidence. func (data *EvidenceData) StringIndented(indent string) string { if data == nil { return "nil-Evidence" } evStrings := make([]string, cmn.MinInt(len(data.Evidence), 21)) for i, ev := range data.Evidence { if i == 20 { evStrings[i] = fmt.Sprintf("... (%v total)", len(data.Evidence)) break } evStrings[i] = fmt.Sprintf("Evidence:%v", ev) } return fmt.Sprintf(`EvidenceData{ %s %v %s}#%v`, indent, strings.Join(evStrings, "\n"+indent+" "), indent, data.hash) } //-------------------------------------------------------------------------------- // BlockID defines the unique ID of a block as its Hash and its PartSetHeader type BlockID struct { Hash cmn.HexBytes `json:"hash"` PartsHeader PartSetHeader `json:"parts"` } // IsZero returns true if this is the BlockID for a nil-block func (blockID BlockID) IsZero() bool { return len(blockID.Hash) == 0 && blockID.PartsHeader.IsZero() } // Equals returns true if the BlockID matches the given BlockID func (blockID BlockID) Equals(other BlockID) bool { return bytes.Equal(blockID.Hash, other.Hash) && blockID.PartsHeader.Equals(other.PartsHeader) } // Key returns a machine-readable string representation of the BlockID func (blockID BlockID) Key() string { bz, err := cdc.MarshalBinaryBare(blockID.PartsHeader) if err != nil { panic(err) } return string(blockID.Hash) + string(bz) } // String returns a human readable string representation of the BlockID func (blockID BlockID) String() string { return fmt.Sprintf(`%v:%v`, blockID.Hash, blockID.PartsHeader) }