github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/state/state.go (about)

     1  package state
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/gogo/protobuf/proto"
    11  
    12  	cmtstate "github.com/badrootd/celestia-core/proto/tendermint/state"
    13  	cmtversion "github.com/badrootd/celestia-core/proto/tendermint/version"
    14  	"github.com/badrootd/celestia-core/types"
    15  	cmttime "github.com/badrootd/celestia-core/types/time"
    16  	"github.com/badrootd/celestia-core/version"
    17  )
    18  
    19  // database key
    20  var (
    21  	stateKey = []byte("stateKey")
    22  )
    23  
    24  //-----------------------------------------------------------------------------
    25  
    26  // InitStateVersion sets the Consensus.Block, Consensus.App and Software versions
    27  func InitStateVersion(appVersion uint64) cmtstate.Version {
    28  	return cmtstate.Version{
    29  		Consensus: cmtversion.Consensus{
    30  			Block: version.BlockProtocol,
    31  			App:   appVersion,
    32  		},
    33  		Software: version.TMCoreSemVer,
    34  	}
    35  }
    36  
    37  //-----------------------------------------------------------------------------
    38  
    39  // State is a short description of the latest committed block of the consensus protocol.
    40  // It keeps all information necessary to validate new blocks,
    41  // including the last validator set and the consensus params.
    42  // All fields are exposed so the struct can be easily serialized,
    43  // but none of them should be mutated directly.
    44  // Instead, use state.Copy() or state.NextState(...).
    45  // NOTE: not goroutine-safe.
    46  type State struct {
    47  	Version cmtstate.Version
    48  
    49  	// immutable
    50  	ChainID       string
    51  	InitialHeight int64 // should be 1, not 0, when starting from height 1
    52  
    53  	// LastBlockHeight=0 at genesis (ie. block(H=0) does not exist)
    54  	LastBlockHeight int64
    55  	LastBlockID     types.BlockID
    56  	LastBlockTime   time.Time
    57  
    58  	// LastValidators is used to validate block.LastCommit.
    59  	// Validators are persisted to the database separately every time they change,
    60  	// so we can query for historical validator sets.
    61  	// Note that if s.LastBlockHeight causes a valset change,
    62  	// we set s.LastHeightValidatorsChanged = s.LastBlockHeight + 1 + 1
    63  	// Extra +1 due to nextValSet delay.
    64  	NextValidators              *types.ValidatorSet
    65  	Validators                  *types.ValidatorSet
    66  	LastValidators              *types.ValidatorSet
    67  	LastHeightValidatorsChanged int64
    68  
    69  	// Consensus parameters used for validating blocks.
    70  	// Changes returned by EndBlock and updated after Commit.
    71  	ConsensusParams                  types.ConsensusParams
    72  	LastHeightConsensusParamsChanged int64
    73  
    74  	// Merkle root of the results from executing prev block
    75  	LastResultsHash []byte
    76  
    77  	// the latest AppHash we've received from calling abci.Commit()
    78  	AppHash []byte
    79  }
    80  
    81  // Copy makes a copy of the State for mutating.
    82  func (state State) Copy() State {
    83  	return State{
    84  		Version:       state.Version,
    85  		ChainID:       state.ChainID,
    86  		InitialHeight: state.InitialHeight,
    87  
    88  		LastBlockHeight: state.LastBlockHeight,
    89  		LastBlockID:     state.LastBlockID,
    90  		LastBlockTime:   state.LastBlockTime,
    91  
    92  		NextValidators:              state.NextValidators.Copy(),
    93  		Validators:                  state.Validators.Copy(),
    94  		LastValidators:              state.LastValidators.Copy(),
    95  		LastHeightValidatorsChanged: state.LastHeightValidatorsChanged,
    96  
    97  		ConsensusParams:                  state.ConsensusParams,
    98  		LastHeightConsensusParamsChanged: state.LastHeightConsensusParamsChanged,
    99  
   100  		AppHash: state.AppHash,
   101  
   102  		LastResultsHash: state.LastResultsHash,
   103  	}
   104  }
   105  
   106  // Equals returns true if the States are identical.
   107  func (state State) Equals(state2 State) bool {
   108  	sbz, s2bz := state.Bytes(), state2.Bytes()
   109  	return bytes.Equal(sbz, s2bz)
   110  }
   111  
   112  // Bytes serializes the State using protobuf.
   113  // It panics if either casting to protobuf or serialization fails.
   114  func (state State) Bytes() []byte {
   115  	sm, err := state.ToProto()
   116  	if err != nil {
   117  		panic(err)
   118  	}
   119  	bz, err := proto.Marshal(sm)
   120  	if err != nil {
   121  		panic(err)
   122  	}
   123  	return bz
   124  }
   125  
   126  // IsEmpty returns true if the State is equal to the empty State.
   127  func (state State) IsEmpty() bool {
   128  	return state.Validators == nil // XXX can't compare to Empty
   129  }
   130  
   131  // ToProto takes the local state type and returns the equivalent proto type
   132  func (state *State) ToProto() (*cmtstate.State, error) {
   133  	if state == nil {
   134  		return nil, errors.New("state is nil")
   135  	}
   136  
   137  	sm := new(cmtstate.State)
   138  
   139  	sm.Version = state.Version
   140  	sm.ChainID = state.ChainID
   141  	sm.InitialHeight = state.InitialHeight
   142  	sm.LastBlockHeight = state.LastBlockHeight
   143  
   144  	sm.LastBlockID = state.LastBlockID.ToProto()
   145  	sm.LastBlockTime = state.LastBlockTime
   146  	vals, err := state.Validators.ToProto()
   147  	if err != nil {
   148  		return nil, err
   149  	}
   150  	sm.Validators = vals
   151  
   152  	nVals, err := state.NextValidators.ToProto()
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  	sm.NextValidators = nVals
   157  
   158  	if state.LastBlockHeight >= 1 { // At Block 1 LastValidators is nil
   159  		lVals, err := state.LastValidators.ToProto()
   160  		if err != nil {
   161  			return nil, err
   162  		}
   163  		sm.LastValidators = lVals
   164  	}
   165  
   166  	sm.LastHeightValidatorsChanged = state.LastHeightValidatorsChanged
   167  	sm.ConsensusParams = state.ConsensusParams.ToProto()
   168  	sm.LastHeightConsensusParamsChanged = state.LastHeightConsensusParamsChanged
   169  	sm.LastResultsHash = state.LastResultsHash
   170  	sm.AppHash = state.AppHash
   171  
   172  	return sm, nil
   173  }
   174  
   175  // FromProto takes a state proto message & returns the local state type
   176  func FromProto(pb *cmtstate.State) (*State, error) { //nolint:golint
   177  	if pb == nil {
   178  		return nil, errors.New("nil State")
   179  	}
   180  
   181  	state := new(State)
   182  
   183  	state.Version = pb.Version
   184  	state.ChainID = pb.ChainID
   185  	state.InitialHeight = pb.InitialHeight
   186  
   187  	bi, err := types.BlockIDFromProto(&pb.LastBlockID)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	state.LastBlockID = *bi
   192  	state.LastBlockHeight = pb.LastBlockHeight
   193  	state.LastBlockTime = pb.LastBlockTime
   194  
   195  	vals, err := types.ValidatorSetFromProto(pb.Validators)
   196  	if err != nil {
   197  		return nil, err
   198  	}
   199  	state.Validators = vals
   200  
   201  	nVals, err := types.ValidatorSetFromProto(pb.NextValidators)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  	state.NextValidators = nVals
   206  
   207  	if state.LastBlockHeight >= 1 { // At Block 1 LastValidators is nil
   208  		lVals, err := types.ValidatorSetFromProto(pb.LastValidators)
   209  		if err != nil {
   210  			return nil, err
   211  		}
   212  		state.LastValidators = lVals
   213  	} else {
   214  		state.LastValidators = types.NewValidatorSet(nil)
   215  	}
   216  
   217  	state.LastHeightValidatorsChanged = pb.LastHeightValidatorsChanged
   218  	state.ConsensusParams = types.ConsensusParamsFromProto(pb.ConsensusParams)
   219  	state.LastHeightConsensusParamsChanged = pb.LastHeightConsensusParamsChanged
   220  	state.LastResultsHash = pb.LastResultsHash
   221  	state.AppHash = pb.AppHash
   222  
   223  	return state, nil
   224  }
   225  
   226  //------------------------------------------------------------------------
   227  // Create a block from the latest state
   228  
   229  // MakeBlock builds a block from the current state with the given txs, commit,
   230  // and evidence. Note it also takes a proposerAddress because the state does not
   231  // track rounds, and hence does not know the correct proposer. TODO: fix this!
   232  func (state State) MakeBlock(
   233  	height int64,
   234  	txs types.Txs,
   235  	commit *types.Commit,
   236  	evidence []types.Evidence,
   237  	proposerAddress []byte,
   238  ) (*types.Block, *types.PartSet) {
   239  	// Build base block with block data.
   240  	block := types.MakeBlock(height, txs, commit, evidence)
   241  
   242  	// Set time.
   243  	var timestamp time.Time
   244  	if height == state.InitialHeight {
   245  		timestamp = state.LastBlockTime // genesis time
   246  	} else {
   247  		timestamp = MedianTime(commit, state.LastValidators)
   248  	}
   249  
   250  	// Fill rest of header with state data.
   251  	block.Header.Populate(
   252  		state.Version.Consensus, state.ChainID,
   253  		timestamp, state.LastBlockID,
   254  		state.Validators.Hash(), state.NextValidators.Hash(),
   255  		state.ConsensusParams.Hash(), state.AppHash, state.LastResultsHash,
   256  		proposerAddress,
   257  	)
   258  
   259  	return block, block.MakePartSet(types.BlockPartSizeBytes)
   260  }
   261  
   262  // MedianTime computes a median time for a given Commit (based on Timestamp field of votes messages) and the
   263  // corresponding validator set. The computed time is always between timestamps of
   264  // the votes sent by honest processes, i.e., a faulty processes can not arbitrarily increase or decrease the
   265  // computed value.
   266  func MedianTime(commit *types.Commit, validators *types.ValidatorSet) time.Time {
   267  	weightedTimes := make([]*cmttime.WeightedTime, len(commit.Signatures))
   268  	totalVotingPower := int64(0)
   269  
   270  	for i, commitSig := range commit.Signatures {
   271  		if commitSig.Absent() {
   272  			continue
   273  		}
   274  		_, validator := validators.GetByAddress(commitSig.ValidatorAddress)
   275  		// If there's no condition, TestValidateBlockCommit panics; not needed normally.
   276  		if validator != nil {
   277  			totalVotingPower += validator.VotingPower
   278  			weightedTimes[i] = cmttime.NewWeightedTime(commitSig.Timestamp, validator.VotingPower)
   279  		}
   280  	}
   281  
   282  	return cmttime.WeightedMedian(weightedTimes, totalVotingPower)
   283  }
   284  
   285  //------------------------------------------------------------------------
   286  // Genesis
   287  
   288  // MakeGenesisStateFromFile reads and unmarshals state from the given
   289  // file.
   290  //
   291  // Used during replay and in tests.
   292  func MakeGenesisStateFromFile(genDocFile string) (State, error) {
   293  	genDoc, err := MakeGenesisDocFromFile(genDocFile)
   294  	if err != nil {
   295  		return State{}, err
   296  	}
   297  	return MakeGenesisState(genDoc)
   298  }
   299  
   300  // MakeGenesisDocFromFile reads and unmarshals genesis doc from the given file.
   301  func MakeGenesisDocFromFile(genDocFile string) (*types.GenesisDoc, error) {
   302  	genDocJSON, err := os.ReadFile(genDocFile)
   303  	if err != nil {
   304  		return nil, fmt.Errorf("couldn't read GenesisDoc file: %v", err)
   305  	}
   306  	genDoc, err := types.GenesisDocFromJSON(genDocJSON)
   307  	if err != nil {
   308  		return nil, fmt.Errorf("error reading GenesisDoc: %v", err)
   309  	}
   310  	return genDoc, nil
   311  }
   312  
   313  // MakeGenesisState creates state from types.GenesisDoc.
   314  func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) {
   315  	err := genDoc.ValidateAndComplete()
   316  	if err != nil {
   317  		return State{}, fmt.Errorf("error in genesis file: %v", err)
   318  	}
   319  
   320  	var validatorSet, nextValidatorSet *types.ValidatorSet
   321  	if genDoc.Validators == nil {
   322  		validatorSet = types.NewValidatorSet(nil)
   323  		nextValidatorSet = types.NewValidatorSet(nil)
   324  	} else {
   325  		validators := make([]*types.Validator, len(genDoc.Validators))
   326  		for i, val := range genDoc.Validators {
   327  			validators[i] = types.NewValidator(val.PubKey, val.Power)
   328  		}
   329  		validatorSet = types.NewValidatorSet(validators)
   330  		nextValidatorSet = types.NewValidatorSet(validators).CopyIncrementProposerPriority(1)
   331  	}
   332  
   333  	appVersion := uint64(0)
   334  	if genDoc.ConsensusParams != nil {
   335  		appVersion = genDoc.ConsensusParams.Version.App
   336  	}
   337  
   338  	return State{
   339  		Version:       InitStateVersion(appVersion),
   340  		ChainID:       genDoc.ChainID,
   341  		InitialHeight: genDoc.InitialHeight,
   342  
   343  		LastBlockHeight: 0,
   344  		LastBlockID:     types.BlockID{},
   345  		LastBlockTime:   genDoc.GenesisTime,
   346  
   347  		NextValidators:              nextValidatorSet,
   348  		Validators:                  validatorSet,
   349  		LastValidators:              types.NewValidatorSet(nil),
   350  		LastHeightValidatorsChanged: genDoc.InitialHeight,
   351  
   352  		ConsensusParams:                  *genDoc.ConsensusParams,
   353  		LastHeightConsensusParamsChanged: genDoc.InitialHeight,
   354  
   355  		AppHash: genDoc.AppHash,
   356  	}, nil
   357  }