- 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 = MedianTime(commit, state.LastValidators)
- }
-
- // 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
- }
-
- // MedianTime computes a median time for a given Commit (based on Timestamp field of votes messages) and the
- // corresponding validator set. The computed time is always between timestamps of
- // the votes sent by honest processes, i.e., a faulty processes can not arbitrarily increase or decrease the
- // computed value.
- func MedianTime(commit *types.Commit, validators *types.ValidatorSet) time.Time {
- weightedTimes := make([]*weightedTime, len(commit.Signatures))
- totalVotingPower := int64(0)
-
- for i, commitSig := range commit.Signatures {
- if commitSig.Absent() {
- continue
- }
- _, validator := validators.GetByAddress(commitSig.ValidatorAddress)
- // If there's no condition, TestValidateBlockCommit panics; not needed normally.
- if validator != nil {
- totalVotingPower += validator.VotingPower
- weightedTimes[i] = newWeightedTime(commitSig.Timestamp, validator.VotingPower)
- }
- }
-
- return weightedMedian(weightedTimes, totalVotingPower)
- }
-
- //------------------------------------------------------------------------
- // 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
- }
|