Files
tendermint/internal/state/state.go
William Banfield 0aa3b0b6fc Proposer-Based Timestamps Merge (#7605)
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
2022-01-26 16:00:23 +00:00

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
}