- package state
-
- import (
- "bytes"
- "io/ioutil"
- "sync"
- "time"
-
- abci "github.com/tendermint/abci/types"
- . "github.com/tendermint/go-common"
- cfg "github.com/tendermint/go-config"
- dbm "github.com/tendermint/go-db"
- "github.com/tendermint/go-wire"
- "github.com/tendermint/tendermint/state/txindex"
- "github.com/tendermint/tendermint/state/txindex/null"
- "github.com/tendermint/tendermint/types"
- )
-
- var (
- stateKey = []byte("stateKey")
- abciResponsesKey = []byte("abciResponsesKey")
- )
-
- //-----------------------------------------------------------------------------
-
- // NOTE: not goroutine-safe.
- type State struct {
- // mtx for writing to db
- mtx sync.Mutex
- db dbm.DB
-
- // should not change
- GenesisDoc *types.GenesisDoc
- ChainID string
-
- // updated at end of SetBlockAndValidators
- LastBlockHeight int // Genesis state has this set to 0. So, Block(H=0) does not exist.
- LastBlockID types.BlockID
- LastBlockTime time.Time
- Validators *types.ValidatorSet
- LastValidators *types.ValidatorSet // block.LastCommit validated against this
-
- // AppHash is updated after Commit
- AppHash []byte
-
- TxIndexer txindex.TxIndexer `json:"-"` // Transaction indexer.
-
- // Intermediate results from processing
- // Persisted separately from the state
- abciResponses *ABCIResponses
- }
-
- func LoadState(db dbm.DB) *State {
- return loadState(db, stateKey)
- }
-
- func loadState(db dbm.DB, key []byte) *State {
- s := &State{db: db, TxIndexer: &null.TxIndex{}}
- buf := db.Get(key)
- if len(buf) == 0 {
- return nil
- } else {
- 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
- Exit(Fmt("Data has been corrupted or its spec has changed: %v\n", *err))
- }
- // TODO: ensure that buf is completely read.
- }
-
- s.LoadABCIResponses()
- return s
- }
-
- func (s *State) Copy() *State {
- return &State{
- db: s.db,
- GenesisDoc: s.GenesisDoc,
- ChainID: s.ChainID,
- LastBlockHeight: s.LastBlockHeight,
- LastBlockID: s.LastBlockID,
- LastBlockTime: s.LastBlockTime,
- Validators: s.Validators.Copy(),
- LastValidators: s.LastValidators.Copy(),
- AppHash: s.AppHash,
- abciResponses: s.abciResponses, // pointer here, not value
- TxIndexer: s.TxIndexer, // pointer here, not value
- }
- }
-
- func (s *State) Save() {
- s.mtx.Lock()
- defer s.mtx.Unlock()
- s.db.SetSync(stateKey, s.Bytes())
- }
-
- // Sets the ABCIResponses in the state and writes them to disk
- func (s *State) SaveABCIResponses(abciResponses *ABCIResponses) {
- s.abciResponses = abciResponses
-
- // save the validators to the db
- s.db.SetSync(abciResponsesKey, s.abciResponses.Bytes())
-
- // save the tx results using the TxIndexer
- batch := txindexer.NewBatch()
- for i, r := range s.abciResponses.TxResults {
- tx := s.abciResponses.Txs[i]
- batch.Index(tx.Hash(), *r)
- }
- s.TxIndexer.Batch(batch)
- }
-
- func (s *State) LoadABCIResponses() {
- s.abciResponses = new(ABCIResponses)
-
- buf := s.db.Get(abciResponsesKey)
- if len(buf) != 0 {
- r, n, err := bytes.NewReader(buf), new(int), new(error)
- wire.ReadBinaryPtr(&s.abciResponses.Validators, r, 0, n, err)
- if *err != nil {
- // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED
- Exit(Fmt("Data has been corrupted or its spec has changed: %v\n", *err))
- }
- // TODO: ensure that buf is completely read.
- }
- }
-
- func (s *State) Equals(s2 *State) bool {
- return bytes.Equal(s.Bytes(), s2.Bytes())
- }
-
- func (s *State) Bytes() []byte {
- buf, n, err := new(bytes.Buffer), new(int), new(error)
- wire.WriteBinary(s, buf, n, err)
- if *err != nil {
- PanicCrisis(*err)
- }
- return buf.Bytes()
- }
-
- // Mutate state variables to match block and validators
- // after running EndBlock
- func (s *State) SetBlockAndValidators(header *types.Header, blockPartsHeader types.PartSetHeader) {
-
- // copy the valset
- prevValSet := s.Validators.Copy()
- nextValSet := prevValSet.Copy()
-
- // update the validator set
- err := updateValidators(nextValSet, s.abciResponses.Validators)
- if err != nil {
- log.Warn("Error changing validator set", "error", err)
- // TODO: err or carry on?
- }
- // Update validator accums and set state variables
- nextValSet.IncrementAccum(1)
-
- s.setBlockAndValidators(header.Height,
- types.BlockID{header.Hash(), blockPartsHeader}, header.Time,
- prevValSet, nextValSet)
- }
-
- func (s *State) setBlockAndValidators(
- height int, blockID types.BlockID, blockTime time.Time,
- prevValSet, nextValSet *types.ValidatorSet) {
-
- s.LastBlockHeight = height
- s.LastBlockID = blockID
- s.LastBlockTime = blockTime
- s.Validators = nextValSet
- s.LastValidators = prevValSet
- }
-
- func (s *State) GetValidators() (*types.ValidatorSet, *types.ValidatorSet) {
- return s.LastValidators, s.Validators
- }
-
- // Load the most recent state from "state" db,
- // or create a new one (and save) from genesis.
- func GetState(config cfg.Config, stateDB dbm.DB) *State {
- state := LoadState(stateDB)
- if state == nil {
- state = MakeGenesisStateFromFile(stateDB, config.GetString("genesis_file"))
- state.Save()
- }
-
- return state
- }
-
- //--------------------------------------------------
- // ABCIResponses holds intermediate state during block processing
-
- type ABCIResponses struct {
- Validators []*abci.Validator // changes to the validator set
-
- Txs types.Txs // for reference later
- TxResults []*types.TxResult // results of the txs, populated in the proxyCb
- }
-
- func NewABCIResponses(block *types.Block) *ABCIResponses {
- return &ABCIResponses{
- Txs: block.Data.Txs,
- TxResults: make([]*types.TxResult, block.NumTxs),
- }
- }
-
- // Serialize the list of validators
- func (a *ABCIResponses) Bytes() []byte {
- buf, n, err := new(bytes.Buffer), new(int), new(error)
- wire.WriteBinary(a.Validators, buf, n, err)
- if *err != nil {
- PanicCrisis(*err)
- }
- return buf.Bytes()
- }
-
- //-----------------------------------------------------------------------------
- // Genesis
-
- // MakeGenesisStateFromFile reads and unmarshals state from the given file.
- //
- // Used during replay and in tests.
- func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) *State {
- genDocJSON, err := ioutil.ReadFile(genDocFile)
- if err != nil {
- Exit(Fmt("Couldn't read GenesisDoc file: %v", err))
- }
- genDoc, err := types.GenesisDocFromJSON(genDocJSON)
- if err != nil {
- Exit(Fmt("Error reading GenesisDoc: %v", err))
- }
- return MakeGenesisState(db, genDoc)
- }
-
- // MakeGenesisState creates state from types.GenesisDoc.
- //
- // Used in tests.
- func MakeGenesisState(db dbm.DB, genDoc *types.GenesisDoc) *State {
- if len(genDoc.Validators) == 0 {
- Exit(Fmt("The genesis file has no validators"))
- }
-
- if genDoc.GenesisTime.IsZero() {
- genDoc.GenesisTime = time.Now()
- }
-
- // 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.Amount,
- }
- }
-
- return &State{
- db: db,
- GenesisDoc: genDoc,
- ChainID: genDoc.ChainID,
- LastBlockHeight: 0,
- LastBlockID: types.BlockID{},
- LastBlockTime: genDoc.GenesisTime,
- Validators: types.NewValidatorSet(validators),
- LastValidators: types.NewValidatorSet(nil),
- AppHash: genDoc.AppHash,
- TxIndexer: &null.TxIndex{}, // we do not need indexer during replay and in tests
- }
- }
|