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 }