mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-29 00:32:14 +00:00
Update state to keep track of this info. Change function args as needed. Make NumTx also an int64 for consistency.
472 lines
14 KiB
Go
472 lines
14 KiB
Go
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 []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.
|
|
},
|
|
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 []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))
|
|
}
|
|
// 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
|
|
}
|
|
|
|
// 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,
|
|
})
|
|
}
|
|
|
|
// 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}#%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.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)
|
|
}
|