tendermint/state/state.go
Emmanuel Odeke 7939d62ef0
all, state: unexpose GenesisDoc, ChainID fields make them accessor methods
Fixes #671

Unexpose GenesisDoc and ChainID fields to avoid them being
serialized to the DB on every block write/state.Save()

A GenesisDoc can now be alternatively written to the state's
database, by serializing its JSON as a value of key "genesis-doc".

There are now accessors and a setter for these attributes:
- state.GenesisDoc() (*types.GenesisDoc, error)
- state.ChainID() (string, error)
- state.SetGenesisDoc(*types.GenesisDoc)

This is a breaking change since it changes how the state's
serialization and requires that if loading the GenesisDoc entirely
from the database, you'll need to set its value in the database
as the GenesisDoc's JSON marshaled bytes.
2017-10-16 10:34:01 +04:00

431 lines
12 KiB
Go

package state
import (
"bytes"
"errors"
"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/state/txindex"
"github.com/tendermint/tendermint/state/txindex/null"
"github.com/tendermint/tendermint/types"
)
var (
stateKey = []byte("stateKey")
abciResponsesKey = []byte("abciResponsesKey")
)
func calcValidatorsKey(height int) []byte {
return []byte(cmn.Fmt("validatorsKey:%v", height))
}
//-----------------------------------------------------------------------------
// State represents the latest committed state of the Tendermint consensus,
// including the last committed block and validator set.
// Newly committed blocks are validated and executed against the State.
// NOTE: not goroutine-safe.
type State struct {
// mtx for writing to db
mtx sync.Mutex
db dbm.DB
// genesisDoc is the memoized genesisDoc to cut down
// the number of unnecessary DB lookups since we no longer
// directly serialize the GenesisDoc in state.
// See https://github.com/tendermint/tendermint/issues/671.
genesisDoc *types.GenesisDoc
// These fields are updated by SetBlockAndValidators.
// LastBlockHeight=0 at genesis (ie. block(H=0) does not exist)
// LastValidators is used to validate block.LastCommit.
LastBlockHeight int
LastBlockID types.BlockID
LastBlockTime time.Time
Validators *types.ValidatorSet
LastValidators *types.ValidatorSet
// AppHash is updated after Commit
AppHash []byte
TxIndexer txindex.TxIndexer `json:"-"` // Transaction indexer
// When a block returns a validator set change via EndBlock,
// the change only applies to the next block.
// So, if s.LastBlockHeight causes a valset change,
// we set s.LastHeightValidatorsChanged = s.LastBlockHeight + 1
LastHeightValidatorsChanged int
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) {
var err error
state := LoadState(stateDB)
if state == nil {
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 {
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
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,
LastBlockHeight: s.LastBlockHeight,
LastBlockID: s.LastBlockID,
LastBlockTime: s.LastBlockTime,
Validators: s.Validators.Copy(),
LastValidators: s.LastValidators.Copy(),
AppHash: s.AppHash,
TxIndexer: s.TxIndexer, // pointer here, not value
LastHeightValidatorsChanged: s.LastHeightValidatorsChanged,
logger: s.logger,
}
}
var (
errNilGenesisDoc = errors.New("no genesisDoc was found")
genesisDBKey = []byte("genesis-doc")
)
// GenesisDoc is the accessor to retrieve the genesisDoc associated
// with a state. If the state has no set GenesisDoc, it fetches from
// its database the JSON marshaled bytes keyed by "genesis-doc", and
// parses the GenesisDoc from that memoizing it for later use.
// If you'd like to change the value of the GenesisDoc, invoke SetGenesisDoc.
func (s *State) GenesisDoc() (*types.GenesisDoc, error) {
s.mtx.Lock()
defer s.mtx.Unlock()
if s.genesisDoc == nil {
retrGenesisDocBytes := s.db.Get(genesisDBKey)
if len(retrGenesisDocBytes) == 0 {
return nil, errNilGenesisDoc
}
genDoc, err := types.GenesisDocFromJSON(retrGenesisDocBytes)
if err != nil {
return nil, err
}
s.genesisDoc = genDoc
}
return s.genesisDoc, nil
}
func (s *State) ChainID() (string, error) {
genDoc, err := s.GenesisDoc()
if err != nil {
return "", err
}
return genDoc.ChainID, nil
}
// Save persists the State to the database.
func (s *State) Save() {
s.mtx.Lock()
s.saveValidatorsInfo()
s.db.SetSync(stateKey, s.Bytes())
s.mtx.Unlock()
}
// SetGenesisDoc sets the internal genesisDoc, but doesn't
// serialize it to the database, until Save is invoked.
func (s *State) SetGenesisDoc(genDoc *types.GenesisDoc) {
s.mtx.Lock()
s.genesisDoc = genDoc
s.mtx.Unlock()
}
// SaveABCIResponses persists the ABCIResponses to the database.
// This is useful in case we crash after app.Commit and before s.Save().
func (s *State) SaveABCIResponses(abciResponses *ABCIResponses) {
s.db.SetSync(abciResponsesKey, abciResponses.Bytes())
}
// LoadABCIResponses loads the ABCIResponses from the database.
func (s *State) LoadABCIResponses() *ABCIResponses {
abciResponses := new(ABCIResponses)
buf := s.db.Get(abciResponsesKey)
if len(buf) != 0 {
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
}
// LoadValidators loads the ValidatorSet for a given height.
func (s *State) LoadValidators(height int) (*types.ValidatorSet, error) {
v := s.loadValidators(height)
if v == nil {
return nil, ErrNoValSetForHeight{height}
}
if v.ValidatorSet == nil {
v = s.loadValidators(v.LastHeightChanged)
if v == nil {
cmn.PanicSanity(fmt.Sprintf(`Couldn't find validators at
height %d as last changed from height %d`, v.LastHeightChanged, height))
}
}
return v.ValidatorSet, nil
}
func (s *State) loadValidators(height int) *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
vi := &ValidatorsInfo{
LastHeightChanged: changeHeight,
}
if changeHeight == nextHeight {
vi.ValidatorSet = s.Validators
}
s.db.SetSync(calcValidatorsKey(nextHeight), vi.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) {
// 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.Diffs) > 0 {
err := updateValidators(nextValSet, abciResponses.EndBlock.Diffs)
if err != nil {
s.logger.Error("Error changing validator set", "err", err)
// TODO: err or carry on?
}
// 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)
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
}
// GetValidators returns the last and current validator sets.
func (s *State) GetValidators() (*types.ValidatorSet, *types.ValidatorSet) {
return s.LastValidators, s.Validators
}
var blankConsensusParams = types.ConsensusParams{}
// Params returns the consensus parameters used for
// validating blocks
func (s *State) Params() types.ConsensusParams {
// TODO: this should move into the State proper
// when we allow the app to change it
genDoc, err := s.GenesisDoc()
if err != nil || genDoc == nil {
return blankConsensusParams
}
return *genDoc.ConsensusParams
}
//------------------------------------------------------------------------
// ABCIResponses retains the responses of the various ABCI calls during block processing.
// It is persisted to disk before calling Commit.
type ABCIResponses struct {
Height int
DeliverTx []*abci.ResponseDeliverTx
EndBlock abci.ResponseEndBlock
txs types.Txs // reference for indexing results by hash
}
// NewABCIResponses returns a new ABCIResponses
func NewABCIResponses(block *types.Block) *ABCIResponses {
return &ABCIResponses{
Height: block.Height,
DeliverTx: make([]*abci.ResponseDeliverTx, block.NumTxs),
txs: block.Data.Txs,
}
}
// Bytes serializes the ABCIResponse using go-wire
func (a *ABCIResponses) Bytes() []byte {
return wire.BinaryBytes(*a)
}
//-----------------------------------------------------------------------------
// ValidatorsInfo represents the latest validator set, or the last time it changed
type ValidatorsInfo struct {
ValidatorSet *types.ValidatorSet
LastHeightChanged int
}
// Bytes serializes the ValidatorsInfo using go-wire
func (vi *ValidatorsInfo) Bytes() []byte {
return wire.BinaryBytes(*vi)
}
//------------------------------------------------------------------------
// 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.
//
// Used in tests.
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,
genesisDoc: genDoc,
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
LastHeightValidatorsChanged: 1,
}, nil
}