mirror of
https://github.com/tendermint/tendermint.git
synced 2026-05-01 04:45:46 +00:00
This pull request merges in the changes for implementing Proposer-based timestamps into `master`. The power was primarily being done in the `wb/proposer-based-timestamps` branch, with changes being merged into that branch during development. This pull request represents an amalgamation of the changes made into that development branch. All of the changes that were placed into that branch have been cleanly rebased on top of the latest `master`. The changes compile and the tests pass insofar as our tests in general pass. ### Note To Reviewers These changes have been extensively reviewed during development. There is not much new here. In the interest of making effective use of time, I would recommend against trying to perform a complete audit of the changes presented and instead examine for mistakes that may have occurred during the process of rebasing the changes. I gave the complete change set a first pass for any issues, but additional eyes would be very appreciated. In sum, this change set does the following: closes #6942 merges in #6849
359 lines
10 KiB
Go
359 lines
10 KiB
Go
package state
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
|
|
tmstate "github.com/tendermint/tendermint/proto/tendermint/state"
|
|
tmversion "github.com/tendermint/tendermint/proto/tendermint/version"
|
|
"github.com/tendermint/tendermint/types"
|
|
"github.com/tendermint/tendermint/version"
|
|
)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
type Version struct {
|
|
Consensus version.Consensus ` json:"consensus"`
|
|
Software string ` json:"software"`
|
|
}
|
|
|
|
// InitStateVersion sets the Consensus.Block and Software versions,
|
|
// but leaves the Consensus.App version blank.
|
|
// The Consensus.App version will be set during the Handshake, once
|
|
// we hear from the app what protocol version it is running.
|
|
var InitStateVersion = Version{
|
|
Consensus: version.Consensus{
|
|
Block: version.BlockProtocol,
|
|
App: 0,
|
|
},
|
|
Software: version.TMVersion,
|
|
}
|
|
|
|
func (v *Version) ToProto() tmstate.Version {
|
|
return tmstate.Version{
|
|
Consensus: tmversion.Consensus{
|
|
Block: v.Consensus.Block,
|
|
App: v.Consensus.App,
|
|
},
|
|
Software: v.Software,
|
|
}
|
|
}
|
|
|
|
func VersionFromProto(v tmstate.Version) Version {
|
|
return Version{
|
|
Consensus: version.Consensus{
|
|
Block: v.Consensus.Block,
|
|
App: v.Consensus.App,
|
|
},
|
|
Software: v.Software,
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// State is a short description of the latest committed block of the Tendermint consensus.
|
|
// It keeps all information necessary to validate new blocks,
|
|
// including the last validator set and the consensus params.
|
|
// All fields are exposed so the struct can be easily serialized,
|
|
// but none of them should be mutated directly.
|
|
// Instead, use state.Copy() or updateState(...).
|
|
// NOTE: not goroutine-safe.
|
|
type State struct {
|
|
// FIXME: This can be removed as TMVersion is a constant, and version.Consensus should
|
|
// eventually be replaced by VersionParams in ConsensusParams
|
|
Version Version
|
|
|
|
// immutable
|
|
ChainID string
|
|
InitialHeight int64 // should be 1, not 0, when starting from height 1
|
|
|
|
// LastBlockHeight=0 at genesis (ie. block(H=0) does not exist)
|
|
LastBlockHeight int64
|
|
LastBlockID types.BlockID
|
|
LastBlockTime time.Time
|
|
|
|
// LastValidators is used to validate block.LastCommit.
|
|
// Validators are persisted to the database separately every time they change,
|
|
// so we can query for historical validator sets.
|
|
// Note that if s.LastBlockHeight causes a valset change,
|
|
// we set s.LastHeightValidatorsChanged = s.LastBlockHeight + 1 + 1
|
|
// Extra +1 due to nextValSet delay.
|
|
NextValidators *types.ValidatorSet
|
|
Validators *types.ValidatorSet
|
|
LastValidators *types.ValidatorSet
|
|
LastHeightValidatorsChanged int64
|
|
|
|
// Consensus parameters used for validating blocks.
|
|
// Changes returned by EndBlock and updated after Commit.
|
|
ConsensusParams types.ConsensusParams
|
|
LastHeightConsensusParamsChanged int64
|
|
|
|
// Merkle root of the results from executing prev block
|
|
LastResultsHash []byte
|
|
|
|
// the latest AppHash we've received from calling abci.Commit()
|
|
AppHash []byte
|
|
}
|
|
|
|
// Copy makes a copy of the State for mutating.
|
|
func (state State) Copy() State {
|
|
|
|
return State{
|
|
Version: state.Version,
|
|
ChainID: state.ChainID,
|
|
InitialHeight: state.InitialHeight,
|
|
|
|
LastBlockHeight: state.LastBlockHeight,
|
|
LastBlockID: state.LastBlockID,
|
|
LastBlockTime: state.LastBlockTime,
|
|
|
|
NextValidators: state.NextValidators.Copy(),
|
|
Validators: state.Validators.Copy(),
|
|
LastValidators: state.LastValidators.Copy(),
|
|
LastHeightValidatorsChanged: state.LastHeightValidatorsChanged,
|
|
|
|
ConsensusParams: state.ConsensusParams,
|
|
LastHeightConsensusParamsChanged: state.LastHeightConsensusParamsChanged,
|
|
|
|
AppHash: state.AppHash,
|
|
|
|
LastResultsHash: state.LastResultsHash,
|
|
}
|
|
}
|
|
|
|
// Equals returns true if the States are identical.
|
|
func (state State) Equals(state2 State) bool {
|
|
sbz, s2bz := state.Bytes(), state2.Bytes()
|
|
return bytes.Equal(sbz, s2bz)
|
|
}
|
|
|
|
// Bytes serializes the State using protobuf.
|
|
// It panics if either casting to protobuf or serialization fails.
|
|
func (state State) Bytes() []byte {
|
|
sm, err := state.ToProto()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
bz, err := proto.Marshal(sm)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return bz
|
|
}
|
|
|
|
// IsEmpty returns true if the State is equal to the empty State.
|
|
func (state State) IsEmpty() bool {
|
|
return state.Validators == nil // XXX can't compare to Empty
|
|
}
|
|
|
|
// ToProto takes the local state type and returns the equivalent proto type
|
|
func (state *State) ToProto() (*tmstate.State, error) {
|
|
if state == nil {
|
|
return nil, errors.New("state is nil")
|
|
}
|
|
|
|
sm := new(tmstate.State)
|
|
|
|
sm.Version = state.Version.ToProto()
|
|
sm.ChainID = state.ChainID
|
|
sm.InitialHeight = state.InitialHeight
|
|
sm.LastBlockHeight = state.LastBlockHeight
|
|
|
|
sm.LastBlockID = state.LastBlockID.ToProto()
|
|
sm.LastBlockTime = state.LastBlockTime
|
|
vals, err := state.Validators.ToProto()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sm.Validators = vals
|
|
|
|
nVals, err := state.NextValidators.ToProto()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sm.NextValidators = nVals
|
|
|
|
if state.LastBlockHeight >= 1 { // At Block 1 LastValidators is nil
|
|
lVals, err := state.LastValidators.ToProto()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sm.LastValidators = lVals
|
|
}
|
|
|
|
sm.LastHeightValidatorsChanged = state.LastHeightValidatorsChanged
|
|
sm.ConsensusParams = state.ConsensusParams.ToProto()
|
|
sm.LastHeightConsensusParamsChanged = state.LastHeightConsensusParamsChanged
|
|
sm.LastResultsHash = state.LastResultsHash
|
|
sm.AppHash = state.AppHash
|
|
|
|
return sm, nil
|
|
}
|
|
|
|
// FromProto takes a state proto message & returns the local state type
|
|
func FromProto(pb *tmstate.State) (*State, error) {
|
|
if pb == nil {
|
|
return nil, errors.New("nil State")
|
|
}
|
|
|
|
state := new(State)
|
|
|
|
state.Version = VersionFromProto(pb.Version)
|
|
state.ChainID = pb.ChainID
|
|
state.InitialHeight = pb.InitialHeight
|
|
|
|
bi, err := types.BlockIDFromProto(&pb.LastBlockID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
state.LastBlockID = *bi
|
|
state.LastBlockHeight = pb.LastBlockHeight
|
|
state.LastBlockTime = pb.LastBlockTime
|
|
|
|
vals, err := types.ValidatorSetFromProto(pb.Validators)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
state.Validators = vals
|
|
|
|
nVals, err := types.ValidatorSetFromProto(pb.NextValidators)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
state.NextValidators = nVals
|
|
|
|
if state.LastBlockHeight >= 1 { // At Block 1 LastValidators is nil
|
|
lVals, err := types.ValidatorSetFromProto(pb.LastValidators)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
state.LastValidators = lVals
|
|
} else {
|
|
state.LastValidators = types.NewValidatorSet(nil)
|
|
}
|
|
|
|
state.LastHeightValidatorsChanged = pb.LastHeightValidatorsChanged
|
|
state.ConsensusParams = types.ConsensusParamsFromProto(pb.ConsensusParams)
|
|
state.LastHeightConsensusParamsChanged = pb.LastHeightConsensusParamsChanged
|
|
state.LastResultsHash = pb.LastResultsHash
|
|
state.AppHash = pb.AppHash
|
|
|
|
return state, nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Create a block from the latest state
|
|
|
|
// MakeBlock builds a block from the current state with the given txs, commit,
|
|
// and evidence. Note it also takes a proposerAddress because the state does not
|
|
// track rounds, and hence does not know the correct proposer. TODO: fix this!
|
|
func (state State) MakeBlock(
|
|
height int64,
|
|
txs []types.Tx,
|
|
commit *types.Commit,
|
|
evidence []types.Evidence,
|
|
proposerAddress []byte,
|
|
) (*types.Block, *types.PartSet, error) {
|
|
|
|
// Build base block with block data.
|
|
block := types.MakeBlock(height, txs, commit, evidence)
|
|
|
|
// Set time.
|
|
var timestamp time.Time
|
|
if height == state.InitialHeight {
|
|
timestamp = state.LastBlockTime // genesis time
|
|
} else {
|
|
timestamp = time.Now()
|
|
}
|
|
|
|
// Fill rest of header with state data.
|
|
block.Header.Populate(
|
|
state.Version.Consensus, state.ChainID,
|
|
timestamp, state.LastBlockID,
|
|
state.Validators.Hash(), state.NextValidators.Hash(),
|
|
state.ConsensusParams.HashConsensusParams(), state.AppHash, state.LastResultsHash,
|
|
proposerAddress,
|
|
)
|
|
|
|
bps, err := block.MakePartSet(types.BlockPartSizeBytes)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
return block, bps, nil
|
|
}
|
|
|
|
//------------------------------------------------------------------------
|
|
// Genesis
|
|
|
|
// MakeGenesisStateFromFile reads and unmarshals state from the given
|
|
// file.
|
|
//
|
|
// Used during replay and in tests.
|
|
func MakeGenesisStateFromFile(genDocFile string) (State, error) {
|
|
genDoc, err := MakeGenesisDocFromFile(genDocFile)
|
|
if err != nil {
|
|
return State{}, err
|
|
}
|
|
return MakeGenesisState(genDoc)
|
|
}
|
|
|
|
// MakeGenesisDocFromFile reads and unmarshals genesis doc from the given file.
|
|
func MakeGenesisDocFromFile(genDocFile string) (*types.GenesisDoc, error) {
|
|
genDocJSON, err := os.ReadFile(genDocFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("couldn't read GenesisDoc file: %w", err)
|
|
}
|
|
genDoc, err := types.GenesisDocFromJSON(genDocJSON)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error reading GenesisDoc: %w", err)
|
|
}
|
|
return genDoc, nil
|
|
}
|
|
|
|
// MakeGenesisState creates state from types.GenesisDoc.
|
|
func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) {
|
|
err := genDoc.ValidateAndComplete()
|
|
if err != nil {
|
|
return State{}, fmt.Errorf("error in genesis doc: %w", err)
|
|
}
|
|
|
|
var validatorSet, nextValidatorSet *types.ValidatorSet
|
|
if genDoc.Validators == nil || len(genDoc.Validators) == 0 {
|
|
validatorSet = types.NewValidatorSet(nil)
|
|
nextValidatorSet = types.NewValidatorSet(nil)
|
|
} else {
|
|
validators := make([]*types.Validator, len(genDoc.Validators))
|
|
for i, val := range genDoc.Validators {
|
|
validators[i] = types.NewValidator(val.PubKey, val.Power)
|
|
}
|
|
validatorSet = types.NewValidatorSet(validators)
|
|
nextValidatorSet = types.NewValidatorSet(validators).CopyIncrementProposerPriority(1)
|
|
}
|
|
|
|
return State{
|
|
Version: InitStateVersion,
|
|
ChainID: genDoc.ChainID,
|
|
InitialHeight: genDoc.InitialHeight,
|
|
|
|
LastBlockHeight: 0,
|
|
LastBlockID: types.BlockID{},
|
|
LastBlockTime: genDoc.GenesisTime,
|
|
|
|
NextValidators: nextValidatorSet,
|
|
Validators: validatorSet,
|
|
LastValidators: types.NewValidatorSet(nil),
|
|
LastHeightValidatorsChanged: genDoc.InitialHeight,
|
|
|
|
ConsensusParams: *genDoc.ConsensusParams,
|
|
LastHeightConsensusParamsChanged: genDoc.InitialHeight,
|
|
|
|
AppHash: genDoc.AppHash,
|
|
}, nil
|
|
}
|