github.com/evdatsion/aphelion-dpos-bft@v0.32.1/state/state.go (about)

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