package types import ( "bytes" "errors" "fmt" "io" "strings" "time" wire "github.com/tendermint/go-wire" "github.com/tendermint/go-wire/data" cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/merkle" ) // Block defines the atomic unit of a Tendermint blockchain. type Block struct { *Header `json:"header"` *Data `json:"data"` LastCommit *Commit `json:"last_commit"` } // MakeBlock returns a new block and corresponding partset from the given information. // TODO: Add version information to the Block struct. func MakeBlock(height int64, chainID string, txs []Tx, totalTxs int64, commit *Commit, prevBlockID BlockID, valHash, appHash, consensusHash []byte, partSize int) (*Block, *PartSet) { newTxs := int64(len(txs)) block := &Block{ Header: &Header{ ChainID: chainID, Height: height, Time: time.Now(), NumTxs: newTxs, TotalTxs: totalTxs + newTxs, LastBlockID: prevBlockID, ValidatorsHash: valHash, AppHash: appHash, // state merkle root of txs from the previous block. ConsensusHash: consensusHash, }, LastCommit: commit, Data: &Data{ Txs: txs, }, } block.FillHeader() return block, block.MakePartSet(partSize) } // ValidateBasic performs basic validation that doesn't involve state data. func (b *Block) ValidateBasic(chainID string, lastBlockHeight int64, lastBlockTotalTx int64, lastBlockID BlockID, lastBlockTime time.Time, appHash, consensusHash []byte) error { if b.ChainID != chainID { return errors.New(cmn.Fmt("Wrong Block.Header.ChainID. Expected %v, got %v", chainID, b.ChainID)) } if b.Height != lastBlockHeight+1 { return errors.New(cmn.Fmt("Wrong Block.Header.Height. Expected %v, got %v", lastBlockHeight+1, b.Height)) } /* TODO: Determine bounds for Time See blockchain/reactor "stopSyncingDurationMinutes" if !b.Time.After(lastBlockTime) { return errors.New("Invalid Block.Header.Time") } */ newTxs := int64(len(b.Data.Txs)) if b.NumTxs != newTxs { return errors.New(cmn.Fmt("Wrong Block.Header.NumTxs. Expected %v, got %v", newTxs, b.NumTxs)) } if b.TotalTxs != lastBlockTotalTx+newTxs { return errors.New(cmn.Fmt("Wrong Block.Header.TotalTxs. Expected %v, got %v", lastBlockTotalTx+newTxs, b.TotalTxs)) } if !b.LastBlockID.Equals(lastBlockID) { return errors.New(cmn.Fmt("Wrong Block.Header.LastBlockID. Expected %v, got %v", lastBlockID, b.LastBlockID)) } if !bytes.Equal(b.LastCommitHash, b.LastCommit.Hash()) { return errors.New(cmn.Fmt("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 errors.New(cmn.Fmt("Wrong Block.Header.DataHash. Expected %v, got %v", b.DataHash, b.Data.Hash())) } if !bytes.Equal(b.AppHash, appHash) { return errors.New(cmn.Fmt("Wrong Block.Header.AppHash. Expected %X, got %v", appHash, b.AppHash)) } if !bytes.Equal(b.ConsensusHash, consensusHash) { return errors.New(cmn.Fmt("Wrong Block.Header.ConsensusHash. Expected %X, got %v", consensusHash, b.ConsensusHash)) } // NOTE: the AppHash and ValidatorsHash are validated later. 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() } } // Hash computes and returns the block hash. // If the block is incomplete, block hash is nil for safety. func (b *Block) Hash() data.Bytes { // fmt.Println(">>", b.Data) if b == nil || b.Header == nil || b.Data == 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. func (b *Block) MakePartSet(partSize int) *PartSet { return NewPartSetFromData(wire.BinaryBytes(b), partSize) } // HashesTo is a convenience function that checks if a block hashes to the given argument. // A nil block never hashes to anything, and nothing hashes to a nil hash. func (b *Block) HashesTo(hash []byte) bool { if len(hash) == 0 { return false } if b == nil { return false } return bytes.Equal(b.Hash(), hash) } // 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`, indent, b.Header.StringIndented(indent+" "), indent, b.Data.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" } else { return fmt.Sprintf("Block#%v", b.Hash()) } } //----------------------------------------------------------------------------- // Header defines the structure of a Tendermint block header type Header struct { ChainID string `json:"chain_id"` Height int64 `json:"height"` Time time.Time `json:"time"` NumTxs int64 `json:"num_txs"` // XXX: Can we get rid of this? TotalTxs int64 `json:"total_txs"` LastBlockID BlockID `json:"last_block_id"` LastCommitHash data.Bytes `json:"last_commit_hash"` // commit from validators from the last block DataHash data.Bytes `json:"data_hash"` // transactions ValidatorsHash data.Bytes `json:"validators_hash"` // validators for the current block AppHash data.Bytes `json:"app_hash"` // state after txs from the previous block ConsensusHash data.Bytes `json:"consensus_hash"` // consensus params for current block } // Hash returns the hash of the header. // Returns nil if ValidatorHash is missing. func (h *Header) Hash() data.Bytes { if len(h.ValidatorsHash) == 0 { return nil } return merkle.SimpleHashFromMap(map[string]interface{}{ "ChainID": h.ChainID, "Height": h.Height, "Time": h.Time, "NumTxs": h.NumTxs, "TotalTxs": h.TotalTxs, "LastBlockID": h.LastBlockID, "LastCommit": h.LastCommitHash, "Data": h.DataHash, "Validators": h.ValidatorsHash, "App": h.AppHash, "Consensus": h.ConsensusHash, }) } // 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 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 App: %v %s Conensus: %v %s}#%v`, 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.AppHash, indent, h.ConsensusHash, 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:"blockID"` Precommits []*Vote `json:"precommits"` // Volatile firstPrecommit *Vote hash data.Bytes bitArray *cmn.BitArray } // FirstPrecommit returns the first non-nil precommit in the commit 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 nil } // 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 VoteTypePrecommit } // 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. 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 != VoteTypePrecommit { 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() data.Bytes { if commit.hash == nil { bs := make([]interface{}, len(commit.Precommits)) for i, precommit := range commit.Precommits { bs[i] = precommit } commit.hash = merkle.SimpleHashFromBinaries(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: %v %s}#%v`, indent, commit.BlockID, indent, strings.Join(precommitStrings, "\n"+indent+" "), indent, commit.hash) } //----------------------------------------------------------------------------- // SignedHeader is a header along with the commits that prove it type SignedHeader struct { Header *Header `json:"header"` Commit *Commit `json:"commit"` } //----------------------------------------------------------------------------- // 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 data.Bytes } // Hash returns the hash of the data func (data *Data) Hash() data.Bytes { 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("Tx:%v", tx) } return fmt.Sprintf(`Data{ %s %v %s}#%v`, indent, strings.Join(txStrings, "\n"+indent+" "), indent, data.hash) } //-------------------------------------------------------------------------------- // BlockID defines the unique ID of a block as its Hash and its PartSetHeader type BlockID struct { Hash data.Bytes `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 { return string(blockID.Hash) + string(wire.BinaryBytes(blockID.PartsHeader)) } // WriteSignBytes writes the canonical bytes of the BlockID to the given writer for digital signing func (blockID BlockID) WriteSignBytes(w io.Writer, n *int, err *error) { if blockID.IsZero() { wire.WriteTo([]byte("null"), w, n, err) } else { wire.WriteJSON(CanonicalBlockID(blockID), w, n, err) } } // String returns a human readable string representation of the BlockID func (blockID BlockID) String() string { return fmt.Sprintf(`%v:%v`, blockID.Hash, blockID.PartsHeader) }