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 }