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
|
|
}
|
|
}
|