github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/bft/state/state.go (about)

     1  package state
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"os"
     7  	"time"
     8  
     9  	"github.com/gnolang/gno/tm2/pkg/amino"
    10  	abci "github.com/gnolang/gno/tm2/pkg/bft/abci/types"
    11  	"github.com/gnolang/gno/tm2/pkg/bft/types"
    12  	tmtime "github.com/gnolang/gno/tm2/pkg/bft/types/time"
    13  	typesver "github.com/gnolang/gno/tm2/pkg/bft/types/version"
    14  	tmver "github.com/gnolang/gno/tm2/pkg/bft/version"
    15  	"github.com/gnolang/gno/tm2/pkg/crypto"
    16  )
    17  
    18  // database keys
    19  var (
    20  	stateKey = []byte("stateKey")
    21  )
    22  
    23  // -----------------------------------------------------------------------------
    24  
    25  // State is a short description of the latest committed block of the Tendermint consensus.
    26  // It keeps all information necessary to validate new blocks,
    27  // including the last validator set and the consensus params.
    28  // All fields are exposed so the struct can be easily serialized,
    29  // but none of them should be mutated directly.
    30  // Instead, use state.Copy() or state.NextState(...).
    31  // NOTE: not goroutine-safe.
    32  type State struct {
    33  	SoftwareVersion string
    34  	BlockVersion    string
    35  	AppVersion      string
    36  
    37  	// immutable
    38  	ChainID string
    39  
    40  	// LastBlockHeight=0 at genesis (ie. block(H=0) does not exist)
    41  	LastBlockHeight  int64
    42  	LastBlockTotalTx int64
    43  	LastBlockID      types.BlockID
    44  	LastBlockTime    time.Time
    45  
    46  	// LastValidators is used to validate block.LastCommit.
    47  	// Validators are persisted to the database separately every time they change,
    48  	// so we can query for historical validator sets.
    49  	// Note that if s.LastBlockHeight causes a valset change,
    50  	// we set s.LastHeightValidatorsChanged = s.LastBlockHeight + 1 + 1
    51  	// Extra +1 due to nextValSet delay.
    52  	NextValidators              *types.ValidatorSet
    53  	Validators                  *types.ValidatorSet
    54  	LastValidators              *types.ValidatorSet
    55  	LastHeightValidatorsChanged int64
    56  
    57  	// Consensus parameters used for validating blocks.
    58  	// Changes returned by EndBlock and updated after Commit.
    59  	ConsensusParams                  abci.ConsensusParams
    60  	LastHeightConsensusParamsChanged int64
    61  
    62  	// Merkle root of the results from executing prev block
    63  	LastResultsHash []byte
    64  
    65  	// the latest AppHash we've received from calling abci.Commit()
    66  	AppHash []byte
    67  }
    68  
    69  // Copy makes a copy of the State for mutating.
    70  func (state State) Copy() State {
    71  	// Make a faithful copy.
    72  	return State{
    73  		SoftwareVersion: state.SoftwareVersion,
    74  		BlockVersion:    state.BlockVersion,
    75  		AppVersion:      state.AppVersion,
    76  
    77  		ChainID: state.ChainID,
    78  
    79  		LastBlockHeight:  state.LastBlockHeight,
    80  		LastBlockTotalTx: state.LastBlockTotalTx,
    81  		LastBlockID:      state.LastBlockID,
    82  		LastBlockTime:    state.LastBlockTime,
    83  
    84  		NextValidators:              state.NextValidators.Copy(),
    85  		Validators:                  state.Validators.Copy(),
    86  		LastValidators:              state.LastValidators.Copy(),
    87  		LastHeightValidatorsChanged: state.LastHeightValidatorsChanged,
    88  
    89  		ConsensusParams:                  state.ConsensusParams,
    90  		LastHeightConsensusParamsChanged: state.LastHeightConsensusParamsChanged,
    91  
    92  		AppHash: state.AppHash,
    93  
    94  		LastResultsHash: state.LastResultsHash,
    95  	}
    96  }
    97  
    98  // Equals returns true if the States are identical.
    99  func (state State) Equals(state2 State) bool {
   100  	sbz, s2bz := state.Bytes(), state2.Bytes()
   101  	return bytes.Equal(sbz, s2bz)
   102  }
   103  
   104  // Bytes serializes the State using go-amino.
   105  func (state State) Bytes() []byte {
   106  	return amino.MustMarshal(state)
   107  }
   108  
   109  // IsEmpty returns true if the State is equal to the empty State.
   110  func (state State) IsEmpty() bool {
   111  	return state.Validators == nil // XXX can't compare to Empty
   112  }
   113  
   114  // ------------------------------------------------------------------------
   115  // Create a block from the latest state
   116  
   117  // MakeBlock builds a block from the current state with the given txs and commit.
   118  // Note it also takes a proposerAddress because the state does not
   119  // track rounds, and hence does not know the correct proposer. TODO: fix this!
   120  func (state State) MakeBlock(
   121  	height int64,
   122  	txs []types.Tx,
   123  	commit *types.Commit,
   124  	proposerAddress crypto.Address,
   125  ) (*types.Block, *types.PartSet) {
   126  	// Build base block with block data.
   127  	block := types.MakeBlock(height, txs, commit)
   128  
   129  	// Set time.
   130  	var timestamp time.Time
   131  	if height == 1 {
   132  		timestamp = state.LastBlockTime // genesis time
   133  	} else {
   134  		timestamp = MedianTime(commit, state.LastValidators)
   135  	}
   136  
   137  	// Fill rest of header with state data.
   138  	block.Header.Populate(
   139  		state.ChainID,
   140  		timestamp, state.LastBlockID, state.LastBlockTotalTx+block.NumTxs,
   141  		state.AppVersion,
   142  		state.Validators.Hash(), state.NextValidators.Hash(),
   143  		state.ConsensusParams.Hash(), state.AppHash, state.LastResultsHash,
   144  		proposerAddress,
   145  	)
   146  
   147  	return block, block.MakePartSet(types.BlockPartSizeBytes)
   148  }
   149  
   150  // MedianTime computes a median time for a given Commit (based on Timestamp field of votes messages) and the
   151  // corresponding validator set. The computed time is always between timestamps of
   152  // the votes sent by honest processes, i.e., a faulty processes can not arbitrarily increase or decrease the
   153  // computed value.
   154  func MedianTime(commit *types.Commit, validators *types.ValidatorSet) time.Time {
   155  	weightedTimes := make([]*tmtime.WeightedTime, len(commit.Precommits))
   156  	totalVotingPower := int64(0)
   157  
   158  	for i, vote := range commit.Precommits {
   159  		if vote != nil {
   160  			_, validator := validators.GetByIndex(vote.ValidatorIndex)
   161  			totalVotingPower += validator.VotingPower
   162  			weightedTimes[i] = tmtime.NewWeightedTime(vote.Timestamp, validator.VotingPower)
   163  		}
   164  	}
   165  
   166  	return tmtime.WeightedMedian(weightedTimes, totalVotingPower)
   167  }
   168  
   169  // ------------------------------------------------------------------------
   170  // Genesis
   171  
   172  // MakeGenesisStateFromFile reads and unmarshals state from the given
   173  // file.
   174  //
   175  // Used during replay and in tests.
   176  func MakeGenesisStateFromFile(genDocFile string) (State, error) {
   177  	genDoc, err := MakeGenesisDocFromFile(genDocFile)
   178  	if err != nil {
   179  		return State{}, err
   180  	}
   181  	return MakeGenesisState(genDoc)
   182  }
   183  
   184  // MakeGenesisDocFromFile reads and unmarshals genesis doc from the given file.
   185  // XXX duplicated in bft/types/genesis.go, remove this.
   186  func MakeGenesisDocFromFile(genDocFile string) (*types.GenesisDoc, error) {
   187  	genDocJSON, err := os.ReadFile(genDocFile)
   188  	if err != nil {
   189  		return nil, fmt.Errorf("couldn't read GenesisDoc file: %w", err)
   190  	}
   191  	genDoc, err := types.GenesisDocFromJSON(genDocJSON)
   192  	if err != nil {
   193  		return nil, fmt.Errorf("error reading GenesisDoc: %w", err)
   194  	}
   195  	return genDoc, nil
   196  }
   197  
   198  // MakeGenesisState creates state from types.GenesisDoc.
   199  func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) {
   200  	err := genDoc.ValidateAndComplete()
   201  	if err != nil {
   202  		return State{}, fmt.Errorf("error in genesis file: %w", err)
   203  	}
   204  
   205  	var validatorSet, nextValidatorSet *types.ValidatorSet
   206  	if genDoc.Validators == nil {
   207  		validatorSet = types.NewValidatorSet(nil)
   208  		nextValidatorSet = types.NewValidatorSet(nil)
   209  	} else {
   210  		validators := make([]*types.Validator, len(genDoc.Validators))
   211  		for i, val := range genDoc.Validators {
   212  			validators[i] = types.NewValidator(val.PubKey, val.Power)
   213  		}
   214  		validatorSet = types.NewValidatorSet(validators)
   215  		nextValidatorSet = types.NewValidatorSet(validators).CopyIncrementProposerPriority(1)
   216  	}
   217  
   218  	return State{
   219  		SoftwareVersion: tmver.Version,
   220  		BlockVersion:    typesver.BlockVersion,
   221  		AppVersion:      "", // gets set by Handshaker after RequestInfo..
   222  		ChainID:         genDoc.ChainID,
   223  
   224  		LastBlockHeight: 0,
   225  		LastBlockID:     types.BlockID{},
   226  		LastBlockTime:   genDoc.GenesisTime,
   227  
   228  		NextValidators:              nextValidatorSet,
   229  		Validators:                  validatorSet,
   230  		LastValidators:              types.NewValidatorSet(nil),
   231  		LastHeightValidatorsChanged: 1,
   232  
   233  		ConsensusParams:                  genDoc.ConsensusParams,
   234  		LastHeightConsensusParamsChanged: 1,
   235  
   236  		AppHash: genDoc.AppHash,
   237  	}, nil
   238  }