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