state: re-order funcs. fix tests
This commit is contained in:
parent
9e6d088757
commit
f82b7e2a13
45
state/db.go
45
state/db.go
|
@ -11,37 +11,50 @@ import (
|
|||
dbm "github.com/tendermint/tmlibs/db"
|
||||
)
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
// 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) {
|
||||
func GetState(stateDB dbm.DB, genesisFile string) (State, error) {
|
||||
state := LoadState(stateDB)
|
||||
if state == nil {
|
||||
if state.IsEmpty() {
|
||||
var err error
|
||||
state, err = MakeGenesisStateFromFile(genesisFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return state, err
|
||||
}
|
||||
state.Save(stateDB, state.AppHash)
|
||||
SaveState(stateDB, state, state.AppHash)
|
||||
}
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// LoadState loads the State from the database.
|
||||
func LoadState(db dbm.DB) *State {
|
||||
func LoadState(db dbm.DB) State {
|
||||
return loadState(db, stateKey)
|
||||
}
|
||||
|
||||
func loadState(db dbm.DB, key []byte) *State {
|
||||
func loadState(db dbm.DB, key []byte) (state State) {
|
||||
buf := db.Get(key)
|
||||
if len(buf) == 0 {
|
||||
return nil
|
||||
return state
|
||||
}
|
||||
|
||||
s := new(State)
|
||||
r, n, err := bytes.NewReader(buf), new(int), new(error)
|
||||
wire.ReadBinaryPtr(&s, r, 0, n, err)
|
||||
wire.ReadBinaryPtr(&state, 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:
|
||||
|
@ -49,7 +62,17 @@ func loadState(db dbm.DB, key []byte) *State {
|
|||
}
|
||||
// TODO: ensure that buf is completely read.
|
||||
|
||||
return s
|
||||
return state
|
||||
}
|
||||
|
||||
// SaveState persists the State, the ValidatorsInfo, and the ConsensusParamsInfo to the database.
|
||||
// It sets the given appHash on the state before persisting.
|
||||
func SaveState(db dbm.DB, s State, appHash []byte) {
|
||||
s.AppHash = appHash
|
||||
nextHeight := s.LastBlockHeight + 1
|
||||
saveValidatorsInfo(db, nextHeight, s.LastHeightValidatorsChanged, s.Validators)
|
||||
saveConsensusParamsInfo(db, nextHeight, s.LastHeightConsensusParamsChanged, s.ConsensusParams)
|
||||
db.SetSync(stateKey, s.Bytes())
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
|
@ -104,7 +127,7 @@ func LoadABCIResponses(db dbm.DB, height int64) (*ABCIResponses, error) {
|
|||
// 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 SaveABCIResponses(db dbm.DB, height int64, abciResponses *ABCIResponses) {
|
||||
func saveABCIResponses(db dbm.DB, height int64, abciResponses *ABCIResponses) {
|
||||
db.SetSync(calcABCIResponsesKey(height), abciResponses.Bytes())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
|
@ -14,27 +13,119 @@ import (
|
|||
"github.com/tendermint/tmlibs/log"
|
||||
)
|
||||
|
||||
//--------------------------------------------------
|
||||
// Execute the block
|
||||
//-----------------------------------------------------------------------------
|
||||
// BlockExecutor handles block execution and state updates.
|
||||
// It exposes ApplyBlock(), which validates & executes the block, updates state w/ ABCI responses,
|
||||
// then commits and updates the mempool atomically, then saves state.
|
||||
|
||||
// ValExecBlock executes the block and returns the responses. It does NOT mutate State.
|
||||
// + validates the block
|
||||
// + executes block.Txs on the proxyAppConn
|
||||
func (blockExec *BlockExecutor) ValExecBlock(s State, block *types.Block) (*ABCIResponses, error) {
|
||||
if err := s.validateBlock(block); err != nil {
|
||||
return nil, ErrInvalidBlock(err)
|
||||
// BlockExecutor provides the context and accessories for properly executing a block.
|
||||
type BlockExecutor struct {
|
||||
db dbm.DB
|
||||
logger log.Logger
|
||||
|
||||
txEventPublisher types.TxEventPublisher
|
||||
proxyApp proxy.AppConnConsensus
|
||||
|
||||
mempool types.Mempool
|
||||
evpool types.EvidencePool
|
||||
}
|
||||
|
||||
// NewBlockExecutor returns a new BlockExecutor.
|
||||
func NewBlockExecutor(db dbm.DB, logger log.Logger,
|
||||
txEventer types.TxEventPublisher, proxyApp proxy.AppConnConsensus,
|
||||
mempool types.Mempool, evpool types.EvidencePool) *BlockExecutor {
|
||||
return &BlockExecutor{
|
||||
db,
|
||||
logger,
|
||||
txEventer,
|
||||
proxyApp,
|
||||
mempool,
|
||||
evpool,
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyBlock validates the block against the state, executes it against the app,
|
||||
// commits it, and saves the block and state. It's the only function that needs to be called
|
||||
// from outside this package to process and commit an entire block.
|
||||
// It takes a blockID to avoid recomputing the parts hash.
|
||||
func (blockExec *BlockExecutor) ApplyBlock(s State, blockID types.BlockID, block *types.Block) (State, error) {
|
||||
|
||||
if err := validateBlock(s, block); err != nil {
|
||||
return s, ErrInvalidBlock(err)
|
||||
}
|
||||
|
||||
abciResponses, err := execBlockOnProxyApp(blockExec.logger, blockExec.proxyApp, block)
|
||||
if err != nil {
|
||||
// There was some error in proxyApp
|
||||
// TODO Report error and wait for proxyApp to be available.
|
||||
return nil, ErrProxyAppConn(err)
|
||||
return s, ErrProxyAppConn(err)
|
||||
}
|
||||
|
||||
return abciResponses, nil
|
||||
fireEvents(blockExec.txEventPublisher, block, abciResponses)
|
||||
|
||||
fail.Fail() // XXX
|
||||
|
||||
// save the results before we commit
|
||||
saveABCIResponses(blockExec.db, block.Height, abciResponses)
|
||||
|
||||
fail.Fail() // XXX
|
||||
|
||||
// update the state with the block and responses
|
||||
s, err = updateState(s, blockID, block.Header, abciResponses)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("Commit failed for application: %v", err)
|
||||
}
|
||||
|
||||
// lock mempool, commit state, update mempoool
|
||||
appHash, err := blockExec.Commit(block)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("Commit failed for application: %v", err)
|
||||
}
|
||||
|
||||
fail.Fail() // XXX
|
||||
|
||||
// save the state and the validators
|
||||
SaveState(blockExec.db, s, appHash)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Commit locks the mempool, runs the ABCI Commit message, and updates the mempool.
|
||||
// It returns the result of calling abci.Commit (the AppHash), and an error.
|
||||
// The Mempool must be locked during commit and update because state is typically reset on Commit and old txs must be replayed
|
||||
// against committed state before new txs are run in the mempool, lest they be invalid.
|
||||
func (blockExec *BlockExecutor) Commit(block *types.Block) ([]byte, error) {
|
||||
blockExec.mempool.Lock()
|
||||
defer blockExec.mempool.Unlock()
|
||||
|
||||
// Commit block, get hash back
|
||||
res, err := blockExec.proxyApp.CommitSync()
|
||||
if err != nil {
|
||||
blockExec.logger.Error("Client error during proxyAppConn.CommitSync", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
if res.IsErr() {
|
||||
blockExec.logger.Error("Error in proxyAppConn.CommitSync", "err", res)
|
||||
return nil, res
|
||||
}
|
||||
if res.Log != "" {
|
||||
blockExec.logger.Debug("Commit.Log: " + res.Log)
|
||||
}
|
||||
|
||||
blockExec.logger.Info("Committed state", "height", block.Height, "txs", block.NumTxs, "hash", res.Data)
|
||||
|
||||
// Update evpool
|
||||
blockExec.evpool.MarkEvidenceAsCommitted(block.Evidence.Evidence)
|
||||
|
||||
// Update mempool.
|
||||
if err := blockExec.mempool.Update(block.Height, block.Txs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Data, nil
|
||||
}
|
||||
|
||||
//---------------------------------------------------------
|
||||
// Helper functions for executing blocks and updating state
|
||||
|
||||
// Executes block's transactions on proxyAppConn.
|
||||
// Returns a list of transaction results and updates to the validator set
|
||||
func execBlockOnProxyApp(logger log.Logger, proxyAppConn proxy.AppConnConsensus, block *types.Block) (*ABCIResponses, error) {
|
||||
|
@ -196,189 +287,62 @@ func changeInVotingPowerMoreOrEqualToOneThird(currentSet *types.ValidatorSet, up
|
|||
return false, nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------
|
||||
// Validate block
|
||||
// updateState returns a new State updated according to the header and responses.
|
||||
func updateState(s State, blockID types.BlockID, header *types.Header,
|
||||
abciResponses *ABCIResponses) (State, error) {
|
||||
|
||||
// MakeBlock builds a block with the given txs and commit from the current state.
|
||||
func (s State) MakeBlock(height int64, txs []types.Tx, commit *types.Commit) (*types.Block, *types.PartSet) {
|
||||
// build base block
|
||||
block := types.MakeBlock(height, txs, commit)
|
||||
// copy the valset so we can apply changes from EndBlock
|
||||
// and update s.LastValidators and s.Validators
|
||||
prevValSet := s.Validators.Copy()
|
||||
nextValSet := prevValSet.Copy()
|
||||
|
||||
// fill header with state data
|
||||
block.ChainID = s.ChainID
|
||||
block.TotalTxs = s.LastBlockTotalTx + block.NumTxs
|
||||
block.LastBlockID = s.LastBlockID
|
||||
block.ValidatorsHash = s.Validators.Hash()
|
||||
block.AppHash = s.AppHash
|
||||
block.ConsensusHash = s.ConsensusParams.Hash()
|
||||
block.LastResultsHash = s.LastResultsHash
|
||||
|
||||
return block, block.MakePartSet(s.ConsensusParams.BlockGossip.BlockPartSizeBytes)
|
||||
}
|
||||
|
||||
// ValidateBlock validates the block against the state.
|
||||
func (s State) ValidateBlock(block *types.Block) error {
|
||||
return s.validateBlock(block)
|
||||
}
|
||||
|
||||
func (s State) validateBlock(b *types.Block) error {
|
||||
// validate internal consistency
|
||||
if err := b.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate basic info
|
||||
if b.ChainID != s.ChainID {
|
||||
return fmt.Errorf("Wrong Block.Header.ChainID. Expected %v, got %v", s.ChainID, b.ChainID)
|
||||
}
|
||||
if b.Height != s.LastBlockHeight+1 {
|
||||
return fmt.Errorf("Wrong Block.Header.Height. Expected %v, got %v", s.LastBlockHeight+1, b.Height)
|
||||
}
|
||||
/* TODO: Determine bounds for Time
|
||||
See blockchain/reactor "stopSyncingDurationMinutes"
|
||||
|
||||
if !b.Time.After(lastBlockTime) {
|
||||
return errors.New("Invalid Block.Header.Time")
|
||||
}
|
||||
*/
|
||||
|
||||
// validate prev block info
|
||||
if !b.LastBlockID.Equals(s.LastBlockID) {
|
||||
return fmt.Errorf("Wrong Block.Header.LastBlockID. Expected %v, got %v", s.LastBlockID, b.LastBlockID)
|
||||
}
|
||||
newTxs := int64(len(b.Data.Txs))
|
||||
if b.TotalTxs != s.LastBlockTotalTx+newTxs {
|
||||
return fmt.Errorf("Wrong Block.Header.TotalTxs. Expected %v, got %v", s.LastBlockTotalTx+newTxs, b.TotalTxs)
|
||||
}
|
||||
|
||||
// validate app info
|
||||
if !bytes.Equal(b.AppHash, s.AppHash) {
|
||||
return fmt.Errorf("Wrong Block.Header.AppHash. Expected %X, got %v", s.AppHash, b.AppHash)
|
||||
}
|
||||
if !bytes.Equal(b.ConsensusHash, s.ConsensusParams.Hash()) {
|
||||
return fmt.Errorf("Wrong Block.Header.ConsensusHash. Expected %X, got %v", s.ConsensusParams.Hash(), b.ConsensusHash)
|
||||
}
|
||||
if !bytes.Equal(b.LastResultsHash, s.LastResultsHash) {
|
||||
return fmt.Errorf("Wrong Block.Header.LastResultsHash. Expected %X, got %v", s.LastResultsHash, b.LastResultsHash)
|
||||
}
|
||||
if !bytes.Equal(b.ValidatorsHash, s.Validators.Hash()) {
|
||||
return fmt.Errorf("Wrong Block.Header.ValidatorsHash. Expected %X, got %v", s.Validators.Hash(), b.ValidatorsHash)
|
||||
}
|
||||
|
||||
// Validate block LastCommit.
|
||||
if b.Height == 1 {
|
||||
if len(b.LastCommit.Precommits) != 0 {
|
||||
return errors.New("Block at height 1 (first block) should have no LastCommit precommits")
|
||||
}
|
||||
} else {
|
||||
if len(b.LastCommit.Precommits) != s.LastValidators.Size() {
|
||||
return fmt.Errorf("Invalid block commit size. Expected %v, got %v",
|
||||
s.LastValidators.Size(), len(b.LastCommit.Precommits))
|
||||
}
|
||||
err := s.LastValidators.VerifyCommit(
|
||||
s.ChainID, s.LastBlockID, b.Height-1, b.LastCommit)
|
||||
// update the validator set with the latest abciResponses
|
||||
lastHeightValsChanged := s.LastHeightValidatorsChanged
|
||||
if len(abciResponses.EndBlock.ValidatorUpdates) > 0 {
|
||||
err := updateValidators(nextValSet, abciResponses.EndBlock.ValidatorUpdates)
|
||||
if err != nil {
|
||||
return err
|
||||
return s, fmt.Errorf("Error changing validator set: %v", err)
|
||||
}
|
||||
// change results from this height but only applies to the next height
|
||||
lastHeightValsChanged = header.Height + 1
|
||||
}
|
||||
|
||||
for _, ev := range b.Evidence.Evidence {
|
||||
if err := VerifyEvidence(s, ev); err != nil {
|
||||
return types.NewEvidenceInvalidErr(ev, err)
|
||||
}
|
||||
/* // Needs a db ...
|
||||
valset, err := LoadValidators(s.db, ev.Height())
|
||||
// Update validator accums and set state variables
|
||||
nextValSet.IncrementAccum(1)
|
||||
|
||||
// update the params with the latest abciResponses
|
||||
nextParams := s.ConsensusParams
|
||||
lastHeightParamsChanged := s.LastHeightConsensusParamsChanged
|
||||
if abciResponses.EndBlock.ConsensusParamUpdates != nil {
|
||||
// NOTE: must not mutate s.ConsensusParams
|
||||
nextParams = s.ConsensusParams.Update(abciResponses.EndBlock.ConsensusParamUpdates)
|
||||
err := nextParams.Validate()
|
||||
if err != nil {
|
||||
// XXX/TODO: what do we do if we can't load the valset?
|
||||
// eg. if we have pruned the state or height is too high?
|
||||
return err
|
||||
return s, fmt.Errorf("Error updating consensus params: %v", err)
|
||||
}
|
||||
if err := VerifyEvidenceValidator(valSet, ev); err != nil {
|
||||
return types.NewEvidenceInvalidErr(ev, err)
|
||||
}
|
||||
*/
|
||||
// change results from this height but only applies to the next height
|
||||
lastHeightParamsChanged = header.Height + 1
|
||||
}
|
||||
|
||||
return nil
|
||||
// NOTE: the AppHash has not been populated.
|
||||
// It will be filled on state.Save.
|
||||
return State{
|
||||
ChainID: s.ChainID,
|
||||
LastBlockHeight: header.Height,
|
||||
LastBlockTotalTx: s.LastBlockTotalTx + header.NumTxs,
|
||||
LastBlockID: blockID,
|
||||
LastBlockTime: header.Time,
|
||||
Validators: nextValSet,
|
||||
LastValidators: s.Validators.Copy(),
|
||||
LastHeightValidatorsChanged: lastHeightValsChanged,
|
||||
ConsensusParams: nextParams,
|
||||
LastHeightConsensusParamsChanged: lastHeightParamsChanged,
|
||||
LastResultsHash: abciResponses.ResultsHash(),
|
||||
AppHash: nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// XXX: What's cheaper (ie. what should be checked first):
|
||||
// evidence internal validity (ie. sig checks) or validator existed (fetch historical val set from db)
|
||||
|
||||
// VerifyEvidence verifies the evidence fully by checking it is internally
|
||||
// consistent and sufficiently recent.
|
||||
func VerifyEvidence(s State, evidence types.Evidence) error {
|
||||
height := s.LastBlockHeight
|
||||
|
||||
evidenceAge := height - evidence.Height()
|
||||
maxAge := s.ConsensusParams.EvidenceParams.MaxAge
|
||||
if evidenceAge > maxAge {
|
||||
return fmt.Errorf("Evidence from height %d is too old. Min height is %d",
|
||||
evidence.Height(), height-maxAge)
|
||||
}
|
||||
|
||||
if err := evidence.Verify(s.ChainID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyEvidenceValidator returns the voting power of the validator at the height of the evidence.
|
||||
// It returns an error if the validator did not exist or does not match that loaded from the historical validator set.
|
||||
func VerifyEvidenceValidator(valset *types.ValidatorSet, evidence types.Evidence) (priority int64, err error) {
|
||||
// The address must have been an active validator at the height
|
||||
ev := evidence
|
||||
height, addr, idx := ev.Height(), ev.Address(), ev.Index()
|
||||
valIdx, val := valset.GetByAddress(addr)
|
||||
if val == nil {
|
||||
return priority, fmt.Errorf("Address %X was not a validator at height %d", addr, height)
|
||||
} else if idx != valIdx {
|
||||
return priority, fmt.Errorf("Address %X was validator %d at height %d, not %d", addr, valIdx, height, idx)
|
||||
}
|
||||
|
||||
priority = val.VotingPower
|
||||
return priority, nil
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// ApplyBlock validates & executes the block, updates state w/ ABCI responses,
|
||||
// then commits and updates the mempool atomically, then saves state.
|
||||
|
||||
// BlockExecutor provides the context and accessories for properly executing a block.
|
||||
type BlockExecutor struct {
|
||||
db dbm.DB
|
||||
logger log.Logger
|
||||
|
||||
txEventPublisher types.TxEventPublisher
|
||||
proxyApp proxy.AppConnConsensus
|
||||
|
||||
mempool types.Mempool
|
||||
evpool types.EvidencePool
|
||||
}
|
||||
|
||||
func NewBlockExecutor(db dbm.DB, logger log.Logger, txEventer types.TxEventPublisher, proxyApp proxy.AppConnConsensus,
|
||||
mempool types.Mempool, evpool types.EvidencePool) *BlockExecutor {
|
||||
return &BlockExecutor{
|
||||
db,
|
||||
logger,
|
||||
txEventer,
|
||||
proxyApp,
|
||||
mempool,
|
||||
evpool,
|
||||
}
|
||||
}
|
||||
|
||||
// ApplyBlock validates the block against the state, executes it against the app,
|
||||
// commits it, and saves the block and state. It's the only function that needs to be called
|
||||
// from outside this package to process and commit an entire block.
|
||||
// It takes a blockID to avoid recomputing the parts hash.
|
||||
func (blockExec *BlockExecutor) ApplyBlock(s State, blockID types.BlockID, block *types.Block) (State, error) {
|
||||
|
||||
abciResponses, err := blockExec.ValExecBlock(s, block)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("Exec failed for application: %v", err)
|
||||
}
|
||||
|
||||
func fireEvents(txEventPublisher types.TxEventPublisher, block *types.Block, abciResponses *ABCIResponses) {
|
||||
// TODO: Fire events
|
||||
/*
|
||||
tx := types.Tx(req.GetDeliverTx().Tx)
|
||||
|
@ -389,68 +353,10 @@ func (blockExec *BlockExecutor) ApplyBlock(s State, blockID types.BlockID, block
|
|||
Result: *txRes,
|
||||
}})
|
||||
*/
|
||||
|
||||
fail.Fail() // XXX
|
||||
|
||||
// save the results before we commit
|
||||
SaveABCIResponses(blockExec.db, block.Height, abciResponses)
|
||||
|
||||
fail.Fail() // XXX
|
||||
|
||||
// update the state with the block and responses
|
||||
s, err = s.NextState(blockID, block.Header, abciResponses)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("Commit failed for application: %v", err)
|
||||
}
|
||||
|
||||
// lock mempool, commit state, update mempoool
|
||||
appHash, err := blockExec.Commit(block)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("Commit failed for application: %v", err)
|
||||
}
|
||||
|
||||
fail.Fail() // XXX
|
||||
|
||||
// save the state and the validators
|
||||
s.Save(blockExec.db, appHash)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Commit locks the mempool, runs the ABCI Commit message, and updates the mempool.
|
||||
// It returns the result of calling abci.Commit (the AppHash), and an error.
|
||||
// The Mempool must be locked during commit and update because state is typically reset on Commit and old txs must be replayed
|
||||
// against committed state before new txs are run in the mempool, lest they be invalid.
|
||||
func (blockExec *BlockExecutor) Commit(block *types.Block) ([]byte, error) {
|
||||
blockExec.mempool.Lock()
|
||||
defer blockExec.mempool.Unlock()
|
||||
|
||||
// Commit block, get hash back
|
||||
res, err := blockExec.proxyApp.CommitSync()
|
||||
if err != nil {
|
||||
blockExec.logger.Error("Client error during proxyAppConn.CommitSync", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
if res.IsErr() {
|
||||
blockExec.logger.Error("Error in proxyAppConn.CommitSync", "err", res)
|
||||
return nil, res
|
||||
}
|
||||
if res.Log != "" {
|
||||
blockExec.logger.Debug("Commit.Log: " + res.Log)
|
||||
}
|
||||
|
||||
blockExec.logger.Info("Committed state", "height", block.Height, "txs", block.NumTxs, "hash", res.Data)
|
||||
|
||||
// Update evpool
|
||||
blockExec.evpool.MarkEvidenceAsCommitted(block.Evidence.Evidence)
|
||||
|
||||
// Update mempool.
|
||||
if err := blockExec.mempool.Update(block.Height, block.Txs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Data, nil
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
// Execute block without state. TODO: eliminate
|
||||
|
||||
// ExecCommitBlock executes and commits a block on the proxyApp without validating or mutating the state.
|
||||
// It returns the application root hash (result of abci.Commit).
|
||||
|
|
|
@ -23,64 +23,6 @@ var (
|
|||
nTxsPerBlock = 10
|
||||
)
|
||||
|
||||
func TestValidateBlock(t *testing.T) {
|
||||
state := state()
|
||||
state.SetLogger(log.TestingLogger())
|
||||
|
||||
// proper block must pass
|
||||
block := makeBlock(state, 1)
|
||||
err := state.ValidateBlock(block)
|
||||
require.NoError(t, err)
|
||||
|
||||
// wrong chain fails
|
||||
block = makeBlock(state, 1)
|
||||
block.ChainID = "not-the-real-one"
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong height fails
|
||||
block = makeBlock(state, 1)
|
||||
block.Height += 10
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong total tx fails
|
||||
block = makeBlock(state, 1)
|
||||
block.TotalTxs += 10
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong blockid fails
|
||||
block = makeBlock(state, 1)
|
||||
block.LastBlockID.PartsHeader.Total += 10
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong app hash fails
|
||||
block = makeBlock(state, 1)
|
||||
block.AppHash = []byte("wrong app hash")
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong consensus hash fails
|
||||
block = makeBlock(state, 1)
|
||||
block.ConsensusHash = []byte("wrong consensus hash")
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong results hash fails
|
||||
block = makeBlock(state, 1)
|
||||
block.LastResultsHash = []byte("wrong results hash")
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong validators hash fails
|
||||
block = makeBlock(state, 1)
|
||||
block.ValidatorsHash = []byte("wrong validators hash")
|
||||
err = state.ValidateBlock(block)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestApplyBlock(t *testing.T) {
|
||||
cc := proxy.NewLocalClientCreator(dummy.NewDummyApplication())
|
||||
proxyApp := proxy.NewAppConns(cc, nil)
|
||||
|
@ -88,15 +30,16 @@ func TestApplyBlock(t *testing.T) {
|
|||
require.Nil(t, err)
|
||||
defer proxyApp.Stop()
|
||||
|
||||
state := state()
|
||||
state.SetLogger(log.TestingLogger())
|
||||
state, stateDB := state(), dbm.NewMemDB()
|
||||
|
||||
block := makeBlock(state, 1)
|
||||
|
||||
err = state.ApplyBlock(types.NopEventBus{}, proxyApp.Consensus(),
|
||||
block, block.MakePartSet(testPartSize).Header(),
|
||||
blockExec := NewBlockExecutor(stateDB, log.TestingLogger(),
|
||||
types.NopEventBus{}, proxyApp.Consensus(),
|
||||
types.MockMempool{}, types.MockEvidencePool{})
|
||||
|
||||
block := makeBlock(state, 1)
|
||||
blockID := types.BlockID{block.Hash(), block.MakePartSet(testPartSize).Header()}
|
||||
|
||||
state, err = blockExec.ApplyBlock(state, blockID, block)
|
||||
require.Nil(t, err)
|
||||
|
||||
// TODO check state and mempool
|
||||
|
@ -112,15 +55,14 @@ func TestBeginBlockAbsentValidators(t *testing.T) {
|
|||
defer proxyApp.Stop()
|
||||
|
||||
state := state()
|
||||
state.SetLogger(log.TestingLogger())
|
||||
|
||||
// there were 2 validators
|
||||
val1PrivKey := crypto.GenPrivKeyEd25519()
|
||||
/*val1PrivKey := crypto.GenPrivKeyEd25519()
|
||||
val2PrivKey := crypto.GenPrivKeyEd25519()
|
||||
lastValidators := types.NewValidatorSet([]*types.Validator{
|
||||
types.NewValidator(val1PrivKey.PubKey(), 10),
|
||||
types.NewValidator(val2PrivKey.PubKey(), 5),
|
||||
})
|
||||
})*/
|
||||
|
||||
prevHash := state.LastBlockID.Hash
|
||||
prevParts := types.PartSetHeader{}
|
||||
|
@ -141,7 +83,7 @@ func TestBeginBlockAbsentValidators(t *testing.T) {
|
|||
lastCommit := &types.Commit{BlockID: prevBlockID, Precommits: tc.lastCommitPrecommits}
|
||||
|
||||
block, _ := state.MakeBlock(2, makeTxs(2), lastCommit)
|
||||
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger(), lastValidators)
|
||||
_, err = ExecCommitBlock(proxyApp.Consensus(), block, log.TestingLogger())
|
||||
require.Nil(t, err, tc.desc)
|
||||
|
||||
// -> app must receive an index of the absent validator
|
||||
|
@ -159,8 +101,8 @@ func makeTxs(height int64) (txs []types.Tx) {
|
|||
return txs
|
||||
}
|
||||
|
||||
func state() *State {
|
||||
s, _ := MakeGenesisState(dbm.NewMemDB(), &types.GenesisDoc{
|
||||
func state() State {
|
||||
s, _ := MakeGenesisState(&types.GenesisDoc{
|
||||
ChainID: chainID,
|
||||
Validators: []types.GenesisValidator{
|
||||
{privKey.PubKey(), 10000, "test"},
|
||||
|
@ -170,7 +112,7 @@ func state() *State {
|
|||
return s
|
||||
}
|
||||
|
||||
func makeBlock(state *State, height int64) *types.Block {
|
||||
func makeBlock(state State, height int64) *types.Block {
|
||||
block, _ := state.MakeBlock(height, makeTxs(state.LastBlockHeight), new(types.Commit))
|
||||
return block
|
||||
}
|
||||
|
|
111
state/state.go
111
state/state.go
|
@ -6,9 +6,6 @@ import (
|
|||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
|
||||
wire "github.com/tendermint/go-wire"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
|
@ -19,18 +16,6 @@ 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.
|
||||
|
@ -94,16 +79,6 @@ func (s State) Copy() State {
|
|||
}
|
||||
}
|
||||
|
||||
// Save persists the State, the ValidatorsInfo, and the ConsensusParamsInfo to the database.
|
||||
// It sets the given appHash on the state before persisting.
|
||||
func (s State) Save(db dbm.DB, appHash []byte) {
|
||||
s.AppHash = appHash
|
||||
nextHeight := s.LastBlockHeight + 1
|
||||
saveValidatorsInfo(db, nextHeight, s.LastHeightValidatorsChanged, s.Validators)
|
||||
saveConsensusParamsInfo(db, nextHeight, s.LastHeightConsensusParamsChanged, s.ConsensusParams)
|
||||
db.SetSync(stateKey, s.Bytes())
|
||||
}
|
||||
|
||||
// Equals returns true if the States are identical.
|
||||
func (s State) Equals(s2 State) bool {
|
||||
return bytes.Equal(s.Bytes(), s2.Bytes())
|
||||
|
@ -114,59 +89,9 @@ func (s State) Bytes() []byte {
|
|||
return wire.BinaryBytes(s)
|
||||
}
|
||||
|
||||
// NextState returns a new State updated according to the header and responses.
|
||||
func (s State) NextState(blockID types.BlockID, header *types.Header,
|
||||
abciResponses *ABCIResponses) (State, 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
|
||||
lastHeightValsChanged := s.LastHeightValidatorsChanged
|
||||
if len(abciResponses.EndBlock.ValidatorUpdates) > 0 {
|
||||
err := updateValidators(nextValSet, abciResponses.EndBlock.ValidatorUpdates)
|
||||
if err != nil {
|
||||
return s, fmt.Errorf("Error changing validator set: %v", err)
|
||||
}
|
||||
// change results from this height but only applies to the next height
|
||||
lastHeightValsChanged = header.Height + 1
|
||||
}
|
||||
|
||||
// Update validator accums and set state variables
|
||||
nextValSet.IncrementAccum(1)
|
||||
|
||||
// update the params with the latest abciResponses
|
||||
nextParams := s.ConsensusParams
|
||||
lastHeightParamsChanged := s.LastHeightConsensusParamsChanged
|
||||
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 s, fmt.Errorf("Error updating consensus params: %v", err)
|
||||
}
|
||||
// change results from this height but only applies to the next height
|
||||
lastHeightParamsChanged = header.Height + 1
|
||||
}
|
||||
|
||||
// NOTE: the AppHash has not been populated.
|
||||
// It will be filled on state.Save.
|
||||
return State{
|
||||
ChainID: s.ChainID,
|
||||
LastBlockHeight: header.Height,
|
||||
LastBlockTotalTx: s.LastBlockTotalTx + header.NumTxs,
|
||||
LastBlockID: blockID,
|
||||
LastBlockTime: header.Time,
|
||||
Validators: nextValSet,
|
||||
LastValidators: s.Validators.Copy(),
|
||||
LastHeightValidatorsChanged: lastHeightValsChanged,
|
||||
ConsensusParams: nextParams,
|
||||
LastHeightConsensusParamsChanged: lastHeightParamsChanged,
|
||||
LastResultsHash: abciResponses.ResultsHash(),
|
||||
AppHash: nil,
|
||||
}, nil
|
||||
// IsEmpty returns true if the State is equal to the empty State.
|
||||
func (s State) IsEmpty() bool {
|
||||
return s.LastBlockHeight == 0 // XXX can't compare to Empty
|
||||
}
|
||||
|
||||
// GetValidators returns the last and current validator sets.
|
||||
|
@ -174,6 +99,26 @@ func (s State) GetValidators() (last *types.ValidatorSet, current *types.Validat
|
|||
return s.LastValidators, s.Validators
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Create a block from the latest state
|
||||
|
||||
// MakeBlock builds a block with the given txs and commit from the current state.
|
||||
func (s State) MakeBlock(height int64, txs []types.Tx, commit *types.Commit) (*types.Block, *types.PartSet) {
|
||||
// build base block
|
||||
block := types.MakeBlock(height, txs, commit)
|
||||
|
||||
// fill header with state data
|
||||
block.ChainID = s.ChainID
|
||||
block.TotalTxs = s.LastBlockTotalTx + block.NumTxs
|
||||
block.LastBlockID = s.LastBlockID
|
||||
block.ValidatorsHash = s.Validators.Hash()
|
||||
block.AppHash = s.AppHash
|
||||
block.ConsensusHash = s.ConsensusParams.Hash()
|
||||
block.LastResultsHash = s.LastResultsHash
|
||||
|
||||
return block, block.MakePartSet(s.ConsensusParams.BlockGossip.BlockPartSizeBytes)
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------
|
||||
// Genesis
|
||||
|
||||
|
@ -181,10 +126,10 @@ func (s State) GetValidators() (last *types.ValidatorSet, current *types.Validat
|
|||
// file.
|
||||
//
|
||||
// Used during replay and in tests.
|
||||
func MakeGenesisStateFromFile(genDocFile string) (*State, error) {
|
||||
func MakeGenesisStateFromFile(genDocFile string) (State, error) {
|
||||
genDoc, err := MakeGenesisDocFromFile(genDocFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return State{}, err
|
||||
}
|
||||
return MakeGenesisState(genDoc)
|
||||
}
|
||||
|
@ -203,10 +148,10 @@ func MakeGenesisDocFromFile(genDocFile string) (*types.GenesisDoc, error) {
|
|||
}
|
||||
|
||||
// MakeGenesisState creates state from types.GenesisDoc.
|
||||
func MakeGenesisState(genDoc *types.GenesisDoc) (*State, error) {
|
||||
func MakeGenesisState(genDoc *types.GenesisDoc) (State, error) {
|
||||
err := genDoc.ValidateAndComplete()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error in genesis file: %v", err)
|
||||
return State{}, fmt.Errorf("Error in genesis file: %v", err)
|
||||
}
|
||||
|
||||
// Make validators slice
|
||||
|
@ -223,7 +168,7 @@ func MakeGenesisState(genDoc *types.GenesisDoc) (*State, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return &State{
|
||||
return State{
|
||||
|
||||
ChainID: genDoc.ChainID,
|
||||
|
||||
|
|
|
@ -14,19 +14,17 @@ import (
|
|||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
||||
cfg "github.com/tendermint/tendermint/config"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
// setupTestCase does setup common to all test cases
|
||||
func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, *State) {
|
||||
func setupTestCase(t *testing.T) (func(t *testing.T), dbm.DB, State) {
|
||||
config := cfg.ResetTestRoot("state_")
|
||||
stateDB := dbm.NewDB("state", config.DBBackend, config.DBDir())
|
||||
state, err := GetState(stateDB, config.GenesisFile())
|
||||
assert.NoError(t, err, "expected no error on GetState")
|
||||
state.SetLogger(log.TestingLogger())
|
||||
|
||||
tearDown := func(t *testing.T) {}
|
||||
|
||||
|
@ -59,7 +57,7 @@ func TestStateSaveLoad(t *testing.T) {
|
|||
assert := assert.New(t)
|
||||
|
||||
state.LastBlockHeight++
|
||||
state.Save()
|
||||
SaveState(stateDB, state, state.AppHash)
|
||||
|
||||
loadedState := LoadState(stateDB)
|
||||
assert.True(state.Equals(loadedState),
|
||||
|
@ -69,7 +67,7 @@ func TestStateSaveLoad(t *testing.T) {
|
|||
|
||||
// TestABCIResponsesSaveLoad tests saving and loading ABCIResponses.
|
||||
func TestABCIResponsesSaveLoad1(t *testing.T) {
|
||||
tearDown, _, state := setupTestCase(t)
|
||||
tearDown, stateDB, state := setupTestCase(t)
|
||||
defer tearDown(t)
|
||||
// nolint: vetshadow
|
||||
assert := assert.New(t)
|
||||
|
@ -88,8 +86,8 @@ func TestABCIResponsesSaveLoad1(t *testing.T) {
|
|||
},
|
||||
}}
|
||||
|
||||
SaveABCIResponses(state.db, block.Height, abciResponses)
|
||||
loadedAbciResponses, err := LoadABCIResponses(state.db, block.Height)
|
||||
saveABCIResponses(stateDB, block.Height, abciResponses)
|
||||
loadedAbciResponses, err := LoadABCIResponses(stateDB, block.Height)
|
||||
assert.Nil(err)
|
||||
assert.Equal(abciResponses, loadedAbciResponses,
|
||||
cmn.Fmt(`ABCIResponses don't match: Got %v, Expected %v`, loadedAbciResponses,
|
||||
|
@ -98,7 +96,7 @@ func TestABCIResponsesSaveLoad1(t *testing.T) {
|
|||
|
||||
// TestResultsSaveLoad tests saving and loading abci results.
|
||||
func TestABCIResponsesSaveLoad2(t *testing.T) {
|
||||
tearDown, _, state := setupTestCase(t)
|
||||
tearDown, stateDB, _ := setupTestCase(t)
|
||||
defer tearDown(t)
|
||||
// nolint: vetshadow
|
||||
assert := assert.New(t)
|
||||
|
@ -142,7 +140,7 @@ func TestABCIResponsesSaveLoad2(t *testing.T) {
|
|||
// query all before, should return error
|
||||
for i := range cases {
|
||||
h := int64(i + 1)
|
||||
res, err := LoadABCIResponses(state.db, h)
|
||||
res, err := LoadABCIResponses(stateDB, h)
|
||||
assert.Error(err, "%d: %#v", i, res)
|
||||
}
|
||||
|
||||
|
@ -153,13 +151,13 @@ func TestABCIResponsesSaveLoad2(t *testing.T) {
|
|||
DeliverTx: tc.added,
|
||||
EndBlock: &abci.ResponseEndBlock{},
|
||||
}
|
||||
SaveABCIResponses(state.db, h, responses)
|
||||
saveABCIResponses(stateDB, h, responses)
|
||||
}
|
||||
|
||||
// query all before, should return expected value
|
||||
for i, tc := range cases {
|
||||
h := int64(i + 1)
|
||||
res, err := LoadABCIResponses(state.db, h)
|
||||
res, err := LoadABCIResponses(stateDB, h)
|
||||
assert.NoError(err, "%d", i)
|
||||
assert.Equal(tc.expected.Hash(), res.ResultsHash(), "%d", i)
|
||||
}
|
||||
|
@ -167,56 +165,57 @@ func TestABCIResponsesSaveLoad2(t *testing.T) {
|
|||
|
||||
// TestValidatorSimpleSaveLoad tests saving and loading validators.
|
||||
func TestValidatorSimpleSaveLoad(t *testing.T) {
|
||||
tearDown, _, state := setupTestCase(t)
|
||||
tearDown, stateDB, state := setupTestCase(t)
|
||||
defer tearDown(t)
|
||||
// nolint: vetshadow
|
||||
assert := assert.New(t)
|
||||
|
||||
// can't load anything for height 0
|
||||
v, err := LoadValidators(state.db, 0)
|
||||
v, err := LoadValidators(stateDB, 0)
|
||||
assert.IsType(ErrNoValSetForHeight{}, err, "expected err at height 0")
|
||||
|
||||
// should be able to load for height 1
|
||||
v, err = LoadValidators(state.db, 1)
|
||||
v, err = LoadValidators(stateDB, 1)
|
||||
assert.Nil(err, "expected no err at height 1")
|
||||
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
|
||||
|
||||
// increment height, save; should be able to load for next height
|
||||
state.LastBlockHeight++
|
||||
nextHeight := state.LastBlockHeight + 1
|
||||
saveValidatorsInfo(state.db, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
v, err = LoadValidators(state.db, nextHeight)
|
||||
saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
v, err = LoadValidators(stateDB, nextHeight)
|
||||
assert.Nil(err, "expected no err")
|
||||
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
|
||||
|
||||
// increment height, save; should be able to load for next height
|
||||
state.LastBlockHeight += 10
|
||||
nextHeight = state.LastBlockHeight + 1
|
||||
saveValidatorsInfo(state.db, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
v, err = LoadValidators(state.db, nextHeight)
|
||||
saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
v, err = LoadValidators(stateDB, nextHeight)
|
||||
assert.Nil(err, "expected no err")
|
||||
assert.Equal(v.Hash(), state.Validators.Hash(), "expected validator hashes to match")
|
||||
|
||||
// should be able to load for next next height
|
||||
_, err = LoadValidators(state.db, state.LastBlockHeight+2)
|
||||
_, err = LoadValidators(stateDB, state.LastBlockHeight+2)
|
||||
assert.IsType(ErrNoValSetForHeight{}, err, "expected err at unknown height")
|
||||
}
|
||||
|
||||
// TestValidatorChangesSaveLoad tests saving and loading a validator set with changes.
|
||||
func TestOneValidatorChangesSaveLoad(t *testing.T) {
|
||||
tearDown, _, state := setupTestCase(t)
|
||||
tearDown, stateDB, state := setupTestCase(t)
|
||||
defer tearDown(t)
|
||||
|
||||
// change vals at these heights
|
||||
changeHeights := []int64{1, 2, 4, 5, 10, 15, 16, 17, 20}
|
||||
N := len(changeHeights)
|
||||
|
||||
// build the validator history by running SetBlockAndValidators
|
||||
// build the validator history by running updateState
|
||||
// with the right validator set for each height
|
||||
highestHeight := changeHeights[N-1] + 5
|
||||
changeIndex := 0
|
||||
_, val := state.Validators.GetByIndex(0)
|
||||
power := val.VotingPower
|
||||
var err error
|
||||
for i := int64(1); i < highestHeight; i++ {
|
||||
// when we get to a change height,
|
||||
// use the next pubkey
|
||||
|
@ -224,11 +223,11 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) {
|
|||
changeIndex++
|
||||
power += 1
|
||||
}
|
||||
header, parts, responses := makeHeaderPartsResponsesValPowerChange(state, i, power)
|
||||
err := state.SetBlockAndValidators(header, parts, responses)
|
||||
header, blockID, responses := makeHeaderPartsResponsesValPowerChange(state, i, power)
|
||||
state, err = updateState(state, blockID, header, responses)
|
||||
assert.Nil(t, err)
|
||||
nextHeight := state.LastBlockHeight + 1
|
||||
saveValidatorsInfo(state.db, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
}
|
||||
|
||||
// on each change height, increment the power by one.
|
||||
|
@ -246,7 +245,7 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) {
|
|||
}
|
||||
|
||||
for i, power := range testCases {
|
||||
v, err := LoadValidators(state.db, int64(i+1))
|
||||
v, err := LoadValidators(stateDB, int64(i+1))
|
||||
assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", i))
|
||||
assert.Equal(t, v.Size(), 1, "validator set size is greater than 1: %d", v.Size())
|
||||
_, val := v.GetByIndex(0)
|
||||
|
@ -260,21 +259,22 @@ func TestOneValidatorChangesSaveLoad(t *testing.T) {
|
|||
// changes.
|
||||
func TestManyValidatorChangesSaveLoad(t *testing.T) {
|
||||
const valSetSize = 7
|
||||
tearDown, _, state := setupTestCase(t)
|
||||
tearDown, stateDB, state := setupTestCase(t)
|
||||
state.Validators = genValSet(valSetSize)
|
||||
state.Save()
|
||||
SaveState(stateDB, state, state.AppHash)
|
||||
defer tearDown(t)
|
||||
|
||||
const height = 1
|
||||
pubkey := crypto.GenPrivKeyEd25519().PubKey()
|
||||
// swap the first validator with a new one ^^^ (validator set size stays the same)
|
||||
header, parts, responses := makeHeaderPartsResponsesValPubKeyChange(state, height, pubkey)
|
||||
err := state.SetBlockAndValidators(header, parts, responses)
|
||||
header, blockID, responses := makeHeaderPartsResponsesValPubKeyChange(state, height, pubkey)
|
||||
var err error
|
||||
state, err = updateState(state, blockID, header, responses)
|
||||
require.Nil(t, err)
|
||||
nextHeight := state.LastBlockHeight + 1
|
||||
saveValidatorsInfo(state.db, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
saveValidatorsInfo(stateDB, nextHeight, state.LastHeightValidatorsChanged, state.Validators)
|
||||
|
||||
v, err := LoadValidators(state.db, height+1)
|
||||
v, err := LoadValidators(stateDB, height+1)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, valSetSize, v.Size())
|
||||
|
||||
|
@ -296,7 +296,7 @@ func genValSet(size int) *types.ValidatorSet {
|
|||
// TestConsensusParamsChangesSaveLoad tests saving and loading consensus params
|
||||
// with changes.
|
||||
func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
||||
tearDown, _, state := setupTestCase(t)
|
||||
tearDown, stateDB, state := setupTestCase(t)
|
||||
defer tearDown(t)
|
||||
|
||||
// change vals at these heights
|
||||
|
@ -312,11 +312,12 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
|||
params[i].BlockSize.MaxBytes += i
|
||||
}
|
||||
|
||||
// build the params history by running SetBlockAndValidators
|
||||
// build the params history by running updateState
|
||||
// with the right params set for each height
|
||||
highestHeight := changeHeights[N-1] + 5
|
||||
changeIndex := 0
|
||||
cp := params[changeIndex]
|
||||
var err error
|
||||
for i := int64(1); i < highestHeight; i++ {
|
||||
// when we get to a change height,
|
||||
// use the next params
|
||||
|
@ -324,11 +325,12 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
|||
changeIndex++
|
||||
cp = params[changeIndex]
|
||||
}
|
||||
header, parts, responses := makeHeaderPartsResponsesParams(state, i, cp)
|
||||
err := state.SetBlockAndValidators(header, parts, responses)
|
||||
header, blockID, responses := makeHeaderPartsResponsesParams(state, i, cp)
|
||||
state, err = updateState(state, blockID, header, responses)
|
||||
|
||||
require.Nil(t, err)
|
||||
nextHeight := state.LastBlockHeight + 1
|
||||
saveConsensusParamsInfo(state.db, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams)
|
||||
saveConsensusParamsInfo(stateDB, nextHeight, state.LastHeightConsensusParamsChanged, state.ConsensusParams)
|
||||
}
|
||||
|
||||
// make all the test cases by using the same params until after the change
|
||||
|
@ -346,7 +348,7 @@ func TestConsensusParamsChangesSaveLoad(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
p, err := LoadConsensusParams(state.db, testCase.height)
|
||||
p, err := LoadConsensusParams(stateDB, testCase.height)
|
||||
assert.Nil(t, err, fmt.Sprintf("expected no err at height %d", testCase.height))
|
||||
assert.Equal(t, testCase.params, p, fmt.Sprintf(`unexpected consensus params at
|
||||
height %d`, testCase.height))
|
||||
|
@ -421,15 +423,15 @@ func TestLessThanOneThirdOfVotingPowerPerBlockEnforced(t *testing.T) {
|
|||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
tearDown, _, state := setupTestCase(t)
|
||||
tearDown, stateDB, state := setupTestCase(t)
|
||||
state.Validators = genValSet(tc.initialValSetSize)
|
||||
state.Save()
|
||||
SaveState(stateDB, state, state.AppHash)
|
||||
height := state.LastBlockHeight + 1
|
||||
block := makeBlock(state, height)
|
||||
abciResponses := &ABCIResponses{
|
||||
EndBlock: &abci.ResponseEndBlock{ValidatorUpdates: tc.valUpdatesFn(state.Validators)},
|
||||
}
|
||||
err := state.SetBlockAndValidators(block.Header, types.PartSetHeader{}, abciResponses)
|
||||
state, err := updateState(state, types.BlockID{block.Hash(), types.PartSetHeader{}}, block.Header, abciResponses)
|
||||
if tc.shouldErr {
|
||||
assert.Error(t, err, "#%d", i)
|
||||
} else {
|
||||
|
@ -489,8 +491,8 @@ func TestApplyUpdates(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func makeHeaderPartsResponsesValPubKeyChange(state *State, height int64,
|
||||
pubkey crypto.PubKey) (*types.Header, types.PartSetHeader, *ABCIResponses) {
|
||||
func makeHeaderPartsResponsesValPubKeyChange(state State, height int64,
|
||||
pubkey crypto.PubKey) (*types.Header, types.BlockID, *ABCIResponses) {
|
||||
|
||||
block := makeBlock(state, height)
|
||||
abciResponses := &ABCIResponses{
|
||||
|
@ -508,11 +510,11 @@ func makeHeaderPartsResponsesValPubKeyChange(state *State, height int64,
|
|||
}
|
||||
}
|
||||
|
||||
return block.Header, types.PartSetHeader{}, abciResponses
|
||||
return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses
|
||||
}
|
||||
|
||||
func makeHeaderPartsResponsesValPowerChange(state *State, height int64,
|
||||
power int64) (*types.Header, types.PartSetHeader, *ABCIResponses) {
|
||||
func makeHeaderPartsResponsesValPowerChange(state State, height int64,
|
||||
power int64) (*types.Header, types.BlockID, *ABCIResponses) {
|
||||
|
||||
block := makeBlock(state, height)
|
||||
abciResponses := &ABCIResponses{
|
||||
|
@ -529,17 +531,17 @@ func makeHeaderPartsResponsesValPowerChange(state *State, height int64,
|
|||
}
|
||||
}
|
||||
|
||||
return block.Header, types.PartSetHeader{}, abciResponses
|
||||
return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses
|
||||
}
|
||||
|
||||
func makeHeaderPartsResponsesParams(state *State, height int64,
|
||||
params types.ConsensusParams) (*types.Header, types.PartSetHeader, *ABCIResponses) {
|
||||
func makeHeaderPartsResponsesParams(state State, height int64,
|
||||
params types.ConsensusParams) (*types.Header, types.BlockID, *ABCIResponses) {
|
||||
|
||||
block := makeBlock(state, height)
|
||||
abciResponses := &ABCIResponses{
|
||||
EndBlock: &abci.ResponseEndBlock{ConsensusParamUpdates: types.TM2PB.ConsensusParams(¶ms)},
|
||||
}
|
||||
return block.Header, types.PartSetHeader{}, abciResponses
|
||||
return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses
|
||||
}
|
||||
|
||||
type paramsChangeTestCase struct {
|
||||
|
@ -547,13 +549,13 @@ type paramsChangeTestCase struct {
|
|||
params types.ConsensusParams
|
||||
}
|
||||
|
||||
func makeHeaderPartsResults(state *State, height int64,
|
||||
results []*abci.ResponseDeliverTx) (*types.Header, types.PartSetHeader, *ABCIResponses) {
|
||||
func makeHeaderPartsResults(state State, height int64,
|
||||
results []*abci.ResponseDeliverTx) (*types.Header, types.BlockID, *ABCIResponses) {
|
||||
|
||||
block := makeBlock(state, height)
|
||||
abciResponses := &ABCIResponses{
|
||||
DeliverTx: results,
|
||||
EndBlock: &abci.ResponseEndBlock{},
|
||||
}
|
||||
return block.Header, types.PartSetHeader{}, abciResponses
|
||||
return block.Header, types.BlockID{block.Hash(), types.PartSetHeader{}}, abciResponses
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
//-----------------------------------------------------
|
||||
// Validate block
|
||||
|
||||
// ValidateBlock validates the block against the state.
|
||||
func ValidateBlock(s State, block *types.Block) error {
|
||||
return validateBlock(s, block)
|
||||
}
|
||||
|
||||
func validateBlock(s State, b *types.Block) error {
|
||||
// validate internal consistency
|
||||
if err := b.ValidateBasic(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// validate basic info
|
||||
if b.ChainID != s.ChainID {
|
||||
return fmt.Errorf("Wrong Block.Header.ChainID. Expected %v, got %v", s.ChainID, b.ChainID)
|
||||
}
|
||||
if b.Height != s.LastBlockHeight+1 {
|
||||
return fmt.Errorf("Wrong Block.Header.Height. Expected %v, got %v", s.LastBlockHeight+1, b.Height)
|
||||
}
|
||||
/* TODO: Determine bounds for Time
|
||||
See blockchain/reactor "stopSyncingDurationMinutes"
|
||||
|
||||
if !b.Time.After(lastBlockTime) {
|
||||
return errors.New("Invalid Block.Header.Time")
|
||||
}
|
||||
*/
|
||||
|
||||
// validate prev block info
|
||||
if !b.LastBlockID.Equals(s.LastBlockID) {
|
||||
return fmt.Errorf("Wrong Block.Header.LastBlockID. Expected %v, got %v", s.LastBlockID, b.LastBlockID)
|
||||
}
|
||||
newTxs := int64(len(b.Data.Txs))
|
||||
if b.TotalTxs != s.LastBlockTotalTx+newTxs {
|
||||
return fmt.Errorf("Wrong Block.Header.TotalTxs. Expected %v, got %v", s.LastBlockTotalTx+newTxs, b.TotalTxs)
|
||||
}
|
||||
|
||||
// validate app info
|
||||
if !bytes.Equal(b.AppHash, s.AppHash) {
|
||||
return fmt.Errorf("Wrong Block.Header.AppHash. Expected %X, got %v", s.AppHash, b.AppHash)
|
||||
}
|
||||
if !bytes.Equal(b.ConsensusHash, s.ConsensusParams.Hash()) {
|
||||
return fmt.Errorf("Wrong Block.Header.ConsensusHash. Expected %X, got %v", s.ConsensusParams.Hash(), b.ConsensusHash)
|
||||
}
|
||||
if !bytes.Equal(b.LastResultsHash, s.LastResultsHash) {
|
||||
return fmt.Errorf("Wrong Block.Header.LastResultsHash. Expected %X, got %v", s.LastResultsHash, b.LastResultsHash)
|
||||
}
|
||||
if !bytes.Equal(b.ValidatorsHash, s.Validators.Hash()) {
|
||||
return fmt.Errorf("Wrong Block.Header.ValidatorsHash. Expected %X, got %v", s.Validators.Hash(), b.ValidatorsHash)
|
||||
}
|
||||
|
||||
// Validate block LastCommit.
|
||||
if b.Height == 1 {
|
||||
if len(b.LastCommit.Precommits) != 0 {
|
||||
return errors.New("Block at height 1 (first block) should have no LastCommit precommits")
|
||||
}
|
||||
} else {
|
||||
if len(b.LastCommit.Precommits) != s.LastValidators.Size() {
|
||||
return fmt.Errorf("Invalid block commit size. Expected %v, got %v",
|
||||
s.LastValidators.Size(), len(b.LastCommit.Precommits))
|
||||
}
|
||||
err := s.LastValidators.VerifyCommit(
|
||||
s.ChainID, s.LastBlockID, b.Height-1, b.LastCommit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, ev := range b.Evidence.Evidence {
|
||||
if err := VerifyEvidence(s, ev); err != nil {
|
||||
return types.NewEvidenceInvalidErr(ev, err)
|
||||
}
|
||||
/* // Needs a db ...
|
||||
valset, err := LoadValidators(s.db, ev.Height())
|
||||
if err != nil {
|
||||
// XXX/TODO: what do we do if we can't load the valset?
|
||||
// eg. if we have pruned the state or height is too high?
|
||||
return err
|
||||
}
|
||||
if err := VerifyEvidenceValidator(valSet, ev); err != nil {
|
||||
return types.NewEvidenceInvalidErr(ev, err)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// XXX: What's cheaper (ie. what should be checked first):
|
||||
// evidence internal validity (ie. sig checks) or validator existed (fetch historical val set from db)
|
||||
|
||||
// VerifyEvidence verifies the evidence fully by checking it is internally
|
||||
// consistent and sufficiently recent.
|
||||
func VerifyEvidence(s State, evidence types.Evidence) error {
|
||||
height := s.LastBlockHeight
|
||||
|
||||
evidenceAge := height - evidence.Height()
|
||||
maxAge := s.ConsensusParams.EvidenceParams.MaxAge
|
||||
if evidenceAge > maxAge {
|
||||
return fmt.Errorf("Evidence from height %d is too old. Min height is %d",
|
||||
evidence.Height(), height-maxAge)
|
||||
}
|
||||
|
||||
if err := evidence.Verify(s.ChainID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyEvidenceValidator returns the voting power of the validator at the height of the evidence.
|
||||
// It returns an error if the validator did not exist or does not match that loaded from the historical validator set.
|
||||
func VerifyEvidenceValidator(valset *types.ValidatorSet, evidence types.Evidence) (priority int64, err error) {
|
||||
// The address must have been an active validator at the height
|
||||
ev := evidence
|
||||
height, addr, idx := ev.Height(), ev.Address(), ev.Index()
|
||||
valIdx, val := valset.GetByAddress(addr)
|
||||
if val == nil {
|
||||
return priority, fmt.Errorf("Address %X was not a validator at height %d", addr, height)
|
||||
} else if idx != valIdx {
|
||||
return priority, fmt.Errorf("Address %X was validator %d at height %d, not %d", addr, valIdx, height, idx)
|
||||
}
|
||||
|
||||
priority = val.VotingPower
|
||||
return priority, nil
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func _TestValidateBlock(t *testing.T) {
|
||||
state := state()
|
||||
|
||||
// proper block must pass
|
||||
block := makeBlock(state, 1)
|
||||
err := ValidateBlock(state, block)
|
||||
require.NoError(t, err)
|
||||
|
||||
// wrong chain fails
|
||||
block = makeBlock(state, 1)
|
||||
block.ChainID = "not-the-real-one"
|
||||
err = ValidateBlock(state, block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong height fails
|
||||
block = makeBlock(state, 1)
|
||||
block.Height += 10
|
||||
err = ValidateBlock(state, block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong total tx fails
|
||||
block = makeBlock(state, 1)
|
||||
block.TotalTxs += 10
|
||||
err = ValidateBlock(state, block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong blockid fails
|
||||
block = makeBlock(state, 1)
|
||||
block.LastBlockID.PartsHeader.Total += 10
|
||||
err = ValidateBlock(state, block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong app hash fails
|
||||
block = makeBlock(state, 1)
|
||||
block.AppHash = []byte("wrong app hash")
|
||||
err = ValidateBlock(state, block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong consensus hash fails
|
||||
block = makeBlock(state, 1)
|
||||
block.ConsensusHash = []byte("wrong consensus hash")
|
||||
err = ValidateBlock(state, block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong results hash fails
|
||||
block = makeBlock(state, 1)
|
||||
block.LastResultsHash = []byte("wrong results hash")
|
||||
err = ValidateBlock(state, block)
|
||||
require.Error(t, err)
|
||||
|
||||
// wrong validators hash fails
|
||||
block = makeBlock(state, 1)
|
||||
block.ValidatorsHash = []byte("wrong validators hash")
|
||||
err = ValidateBlock(state, block)
|
||||
require.Error(t, err)
|
||||
}
|
Loading…
Reference in New Issue