package state import ( "bytes" "fmt" "io/ioutil" "sync" "time" abci "github.com/tendermint/abci/types" cmn "github.com/tendermint/tmlibs/common" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" wire "github.com/tendermint/go-wire" "github.com/tendermint/tendermint/types" ) // database keys var ( stateKey = []byte("stateKey") ) func calcValidatorsKey(height int64) []byte { return []byte(cmn.Fmt("validatorsKey:%v", height)) } func calcConsensusParamsKey(height int64) []byte { return []byte(cmn.Fmt("consensusParamsKey:%v", height)) } func calcABCIResponsesKey(height int64) []byte { return []byte(cmn.Fmt("abciResponsesKey:%v", height)) } //----------------------------------------------------------------------------- // State is a short description of the latest committed block of the Tendermint consensus. // It keeps all information necessary to validate new blocks, // including the last validator set and the consensus params. // All fields are exposed so the struct can be easily serialized, // but the fields should only be changed by calling state.SetBlockAndValidators. // NOTE: not goroutine-safe. type State struct { // mtx for writing to db mtx sync.Mutex db dbm.DB // Immutable ChainID string // Exposed fields are updated by SetBlockAndValidators. // LastBlockHeight=0 at genesis (ie. block(H=0) does not exist) LastBlockHeight int64 LastBlockTotalTx int64 LastBlockID types.BlockID LastBlockTime time.Time // LastValidators is used to validate block.LastCommit. // Validators are persisted to the database separately every time they change, // so we can query for historical validator sets. // Note that if s.LastBlockHeight causes a valset change, // we set s.LastHeightValidatorsChanged = s.LastBlockHeight + 1 Validators *types.ValidatorSet LastValidators *types.ValidatorSet LastHeightValidatorsChanged int64 // Consensus parameters used for validating blocks. // Changes returned by EndBlock and updated after Commit. ConsensusParams types.ConsensusParams LastHeightConsensusParamsChanged int64 // Merkle root of the results from executing prev block LastResultsHash []byte // The latest AppHash we've received from calling abci.Commit() AppHash []byte logger log.Logger } // GetState loads the most recent state from the database, // or creates a new one from the given genesisFile and persists the result // to the database. func GetState(stateDB dbm.DB, genesisFile string) (*State, error) { state := LoadState(stateDB) if state == nil { var err error state, err = MakeGenesisStateFromFile(stateDB, genesisFile) if err != nil { return nil, err } state.Save() } return state, nil } // LoadState loads the State from the database. func LoadState(db dbm.DB) *State { return loadState(db, stateKey) } func loadState(db dbm.DB, key []byte) *State { buf := db.Get(key) if len(buf) == 0 { return nil } s := &State{db: db} r, n, err := bytes.NewReader(buf), new(int), new(error) wire.ReadBinaryPtr(&s, r, 0, n, err) if *err != nil { // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED cmn.Exit(cmn.Fmt(`LoadState: Data has been corrupted or its spec has changed: %v\n`, *err)) } // TODO: ensure that buf is completely read. return s } // SetLogger sets the logger on the State. func (s *State) SetLogger(l log.Logger) { s.logger = l } // Copy makes a copy of the State for mutating. func (s *State) Copy() *State { return &State{ db: s.db, ChainID: s.ChainID, LastBlockHeight: s.LastBlockHeight, LastBlockTotalTx: s.LastBlockTotalTx, LastBlockID: s.LastBlockID, LastBlockTime: s.LastBlockTime, Validators: s.Validators.Copy(), LastValidators: s.LastValidators.Copy(), LastHeightValidatorsChanged: s.LastHeightValidatorsChanged, ConsensusParams: s.ConsensusParams, LastHeightConsensusParamsChanged: s.LastHeightConsensusParamsChanged, AppHash: s.AppHash, LastResultsHash: s.LastResultsHash, logger: s.logger, } } // Save persists the State to the database. func (s *State) Save() { s.mtx.Lock() defer s.mtx.Unlock() s.saveValidatorsInfo() s.saveConsensusParamsInfo() s.db.SetSync(stateKey, s.Bytes()) } // SaveABCIResponses persists the ABCIResponses to the database. // This is useful in case we crash after app.Commit and before s.Save(). // Responses are indexed by height so they can also be loaded later to produce Merkle proofs. func (s *State) SaveABCIResponses(height int64, abciResponses *ABCIResponses) { s.db.SetSync(calcABCIResponsesKey(height), abciResponses.Bytes()) } // LoadABCIResponses loads the ABCIResponses for the given height from the database. // This is useful for recovering from crashes where we called app.Commit and before we called // s.Save(). It can also be used to produce Merkle proofs of the result of txs. func (s *State) LoadABCIResponses(height int64) (*ABCIResponses, error) { buf := s.db.Get(calcABCIResponsesKey(height)) if len(buf) == 0 { return nil, ErrNoABCIResponsesForHeight{height} } abciResponses := new(ABCIResponses) r, n, err := bytes.NewReader(buf), new(int), new(error) wire.ReadBinaryPtr(abciResponses, r, 0, n, err) if *err != nil { // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED cmn.Exit(cmn.Fmt(`LoadABCIResponses: Data has been corrupted or its spec has changed: %v\n`, *err)) } // TODO: ensure that buf is completely read. return abciResponses, nil } // LoadValidators loads the ValidatorSet for a given height. func (s *State) LoadValidators(height int64) (*types.ValidatorSet, error) { valInfo := s.loadValidatorsInfo(height) if valInfo == nil { return nil, ErrNoValSetForHeight{height} } if valInfo.ValidatorSet == nil { valInfo = s.loadValidatorsInfo(valInfo.LastHeightChanged) if valInfo == nil { cmn.PanicSanity(fmt.Sprintf(`Couldn't find validators at height %d as last changed from height %d`, valInfo.LastHeightChanged, height)) } } return valInfo.ValidatorSet, nil } func (s *State) loadValidatorsInfo(height int64) *ValidatorsInfo { buf := s.db.Get(calcValidatorsKey(height)) if len(buf) == 0 { return nil } v := new(ValidatorsInfo) r, n, err := bytes.NewReader(buf), new(int), new(error) wire.ReadBinaryPtr(v, r, 0, n, err) if *err != nil { // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED cmn.Exit(cmn.Fmt(`LoadValidators: Data has been corrupted or its spec has changed: %v\n`, *err)) } // TODO: ensure that buf is completely read. return v } // saveValidatorsInfo persists the validator set for the next block to disk. // It should be called from s.Save(), right before the state itself is persisted. // If the validator set did not change after processing the latest block, // only the last height for which the validators changed is persisted. func (s *State) saveValidatorsInfo() { changeHeight := s.LastHeightValidatorsChanged nextHeight := s.LastBlockHeight + 1 valInfo := &ValidatorsInfo{ LastHeightChanged: changeHeight, } if changeHeight == nextHeight { valInfo.ValidatorSet = s.Validators } s.db.SetSync(calcValidatorsKey(nextHeight), valInfo.Bytes()) } // LoadConsensusParams loads the ConsensusParams for a given height. func (s *State) LoadConsensusParams(height int64) (types.ConsensusParams, error) { empty := types.ConsensusParams{} paramsInfo := s.loadConsensusParamsInfo(height) if paramsInfo == nil { return empty, ErrNoConsensusParamsForHeight{height} } if paramsInfo.ConsensusParams == empty { paramsInfo = s.loadConsensusParamsInfo(paramsInfo.LastHeightChanged) if paramsInfo == nil { cmn.PanicSanity(fmt.Sprintf(`Couldn't find consensus params at height %d as last changed from height %d`, paramsInfo.LastHeightChanged, height)) } } return paramsInfo.ConsensusParams, nil } func (s *State) loadConsensusParamsInfo(height int64) *ConsensusParamsInfo { buf := s.db.Get(calcConsensusParamsKey(height)) if len(buf) == 0 { return nil } paramsInfo := new(ConsensusParamsInfo) r, n, err := bytes.NewReader(buf), new(int), new(error) wire.ReadBinaryPtr(paramsInfo, r, 0, n, err) if *err != nil { // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED cmn.Exit(cmn.Fmt(`LoadConsensusParams: Data has been corrupted or its spec has changed: %v\n`, *err)) } // TODO: ensure that buf is completely read. return paramsInfo } // saveConsensusParamsInfo persists the consensus params for the next block to disk. // It should be called from s.Save(), right before the state itself is persisted. // If the consensus params did not change after processing the latest block, // only the last height for which they changed is persisted. func (s *State) saveConsensusParamsInfo() { changeHeight := s.LastHeightConsensusParamsChanged nextHeight := s.LastBlockHeight + 1 paramsInfo := &ConsensusParamsInfo{ LastHeightChanged: changeHeight, } if changeHeight == nextHeight { paramsInfo.ConsensusParams = s.ConsensusParams } s.db.SetSync(calcConsensusParamsKey(nextHeight), paramsInfo.Bytes()) } // Equals returns true if the States are identical. func (s *State) Equals(s2 *State) bool { return bytes.Equal(s.Bytes(), s2.Bytes()) } // Bytes serializes the State using go-wire. func (s *State) Bytes() []byte { return wire.BinaryBytes(s) } // SetBlockAndValidators mutates State variables // to update block and validators after running EndBlock. func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader types.PartSetHeader, abciResponses *ABCIResponses) error { // copy the valset so we can apply changes from EndBlock // and update s.LastValidators and s.Validators prevValSet := s.Validators.Copy() nextValSet := prevValSet.Copy() // update the validator set with the latest abciResponses if len(abciResponses.EndBlock.ValidatorUpdates) > 0 { err := updateValidators(nextValSet, abciResponses.EndBlock.ValidatorUpdates) if err != nil { return fmt.Errorf("Error changing validator set: %v", err) } // change results from this height but only applies to the next height s.LastHeightValidatorsChanged = header.Height + 1 } // Update validator accums and set state variables nextValSet.IncrementAccum(1) // update the params with the latest abciResponses nextParams := s.ConsensusParams if abciResponses.EndBlock.ConsensusParamUpdates != nil { // NOTE: must not mutate s.ConsensusParams nextParams = s.ConsensusParams.Update(abciResponses.EndBlock.ConsensusParamUpdates) err := nextParams.Validate() if err != nil { return fmt.Errorf("Error updating consensus params: %v", err) } // change results from this height but only applies to the next height s.LastHeightConsensusParamsChanged = header.Height + 1 } s.setBlockAndValidators(header.Height, header.NumTxs, types.BlockID{header.Hash(), blockPartsHeader}, header.Time, nextValSet, nextParams, abciResponses.ResultsHash()) return nil } func (s *State) setBlockAndValidators(height int64, newTxs int64, blockID types.BlockID, blockTime time.Time, valSet *types.ValidatorSet, params types.ConsensusParams, resultsHash []byte) { s.LastBlockHeight = height s.LastBlockTotalTx += newTxs s.LastBlockID = blockID s.LastBlockTime = blockTime s.LastValidators = s.Validators.Copy() s.Validators = valSet s.ConsensusParams = params s.LastResultsHash = resultsHash } // GetValidators returns the last and current validator sets. func (s *State) GetValidators() (last *types.ValidatorSet, current *types.ValidatorSet) { return s.LastValidators, s.Validators } //------------------------------------------------------------------------ // ABCIResponses retains the responses // of the various ABCI calls during block processing. // It is persisted to disk for each height before calling Commit. type ABCIResponses struct { DeliverTx []*abci.ResponseDeliverTx EndBlock *abci.ResponseEndBlock } // NewABCIResponses returns a new ABCIResponses func NewABCIResponses(block *types.Block) *ABCIResponses { return &ABCIResponses{ DeliverTx: make([]*abci.ResponseDeliverTx, block.NumTxs), } } // Bytes serializes the ABCIResponse using go-wire func (a *ABCIResponses) Bytes() []byte { return wire.BinaryBytes(*a) } func (a *ABCIResponses) ResultsHash() []byte { results := types.NewResults(a.DeliverTx) return results.Hash() } //----------------------------------------------------------------------------- // ValidatorsInfo represents the latest validator set, or the last height it changed type ValidatorsInfo struct { ValidatorSet *types.ValidatorSet LastHeightChanged int64 } // Bytes serializes the ValidatorsInfo using go-wire func (valInfo *ValidatorsInfo) Bytes() []byte { return wire.BinaryBytes(*valInfo) } //----------------------------------------------------------------------------- // ConsensusParamsInfo represents the latest consensus params, or the last height it changed type ConsensusParamsInfo struct { ConsensusParams types.ConsensusParams LastHeightChanged int64 } // Bytes serializes the ConsensusParamsInfo using go-wire func (params ConsensusParamsInfo) Bytes() []byte { return wire.BinaryBytes(params) } //------------------------------------------------------------------------ // Genesis // MakeGenesisStateFromFile reads and unmarshals state from the given // file. // // Used during replay and in tests. func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) (*State, error) { genDoc, err := MakeGenesisDocFromFile(genDocFile) if err != nil { return nil, err } return MakeGenesisState(db, genDoc) } // MakeGenesisDocFromFile reads and unmarshals genesis doc from the given file. func MakeGenesisDocFromFile(genDocFile string) (*types.GenesisDoc, error) { genDocJSON, err := ioutil.ReadFile(genDocFile) if err != nil { return nil, fmt.Errorf("Couldn't read GenesisDoc file: %v", err) } genDoc, err := types.GenesisDocFromJSON(genDocJSON) if err != nil { return nil, fmt.Errorf("Error reading GenesisDoc: %v", err) } return genDoc, nil } // MakeGenesisState creates state from types.GenesisDoc. func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) (*State, error) { err := genDoc.ValidateAndComplete() if err != nil { return nil, fmt.Errorf("Error in genesis file: %v", err) } // Make validators slice validators := make([]*types.Validator, len(genDoc.Validators)) for i, val := range genDoc.Validators { pubKey := val.PubKey address := pubKey.Address() // Make validator validators[i] = &types.Validator{ Address: address, PubKey: pubKey, VotingPower: val.Power, } } return &State{ db: db, ChainID: genDoc.ChainID, LastBlockHeight: 0, LastBlockID: types.BlockID{}, LastBlockTime: genDoc.GenesisTime, Validators: types.NewValidatorSet(validators), LastValidators: types.NewValidatorSet(nil), LastHeightValidatorsChanged: 1, ConsensusParams: *genDoc.ConsensusParams, LastHeightConsensusParamsChanged: 1, AppHash: genDoc.AppHash, }, nil }