mirror of
https://github.com/tendermint/tendermint.git
synced 2026-01-08 06:15:33 +00:00
handshake replay through consensus using mockApp
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
|
||||
"github.com/ebuchman/fail-test"
|
||||
@@ -278,15 +277,20 @@ func (s *State) CommitStateUpdateMempool(proxyAppConn proxy.AppConnConsensus, bl
|
||||
type Mempool interface {
|
||||
Lock()
|
||||
Unlock()
|
||||
Update(height int, txs []types.Tx)
|
||||
|
||||
CheckTx(types.Tx, func(*abci.Response)) error
|
||||
Reap(int) types.Txs
|
||||
Update(height int, txs types.Txs)
|
||||
}
|
||||
|
||||
type MockMempool struct {
|
||||
}
|
||||
|
||||
func (m MockMempool) Lock() {}
|
||||
func (m MockMempool) Unlock() {}
|
||||
func (m MockMempool) Update(height int, txs []types.Tx) {}
|
||||
func (m MockMempool) Lock() {}
|
||||
func (m MockMempool) Unlock() {}
|
||||
func (m MockMempool) CheckTx(tx types.Tx, cb func(*abci.Response)) error { return nil }
|
||||
func (m MockMempool) Reap(n int) types.Txs { return types.Txs{} }
|
||||
func (m MockMempool) Update(height int, txs types.Txs) {}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// Handshake with app to sync to latest state of core by replaying blocks
|
||||
@@ -298,16 +302,19 @@ type BlockStore interface {
|
||||
LoadBlockMeta(height int) *types.BlockMeta
|
||||
}
|
||||
|
||||
type blockReplayFunc func(cfg.Config, *State, proxy.AppConnConsensus, BlockStore)
|
||||
|
||||
type Handshaker struct {
|
||||
config cfg.Config
|
||||
state *State
|
||||
store BlockStore
|
||||
config cfg.Config
|
||||
state *State
|
||||
store BlockStore
|
||||
replayLastBlock blockReplayFunc
|
||||
|
||||
nBlocks int // number of blocks applied to the state
|
||||
}
|
||||
|
||||
func NewHandshaker(config cfg.Config, state *State, store BlockStore) *Handshaker {
|
||||
return &Handshaker{config, state, store, 0}
|
||||
func NewHandshaker(config cfg.Config, state *State, store BlockStore, f blockReplayFunc) *Handshaker {
|
||||
return &Handshaker{config, state, store, f, 0}
|
||||
}
|
||||
|
||||
// TODO: retry the handshake/replay if it fails ?
|
||||
@@ -326,7 +333,7 @@ func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error {
|
||||
// TODO: check version
|
||||
|
||||
// replay blocks up to the latest in the blockstore
|
||||
err = h.ReplayBlocks(appHash, blockHeight, proxyApp.Consensus())
|
||||
err = h.ReplayBlocks(appHash, blockHeight, proxyApp)
|
||||
if err != nil {
|
||||
return errors.New(Fmt("Error on replay: %v", err))
|
||||
}
|
||||
@@ -340,7 +347,7 @@ func (h *Handshaker) Handshake(proxyApp proxy.AppConns) error {
|
||||
}
|
||||
|
||||
// Replay all blocks after blockHeight and ensure the result matches the current state.
|
||||
func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int, appConnConsensus proxy.AppConnConsensus) error {
|
||||
func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int, proxyApp proxy.AppConns) error {
|
||||
|
||||
storeBlockHeight := h.store.Height()
|
||||
stateBlockHeight := h.state.LastBlockHeight
|
||||
@@ -353,85 +360,71 @@ func (h *Handshaker) ReplayBlocks(appHash []byte, appBlockHeight int, appConnCon
|
||||
return ErrAppBlockHeightTooHigh{storeBlockHeight, appBlockHeight}
|
||||
|
||||
} else if storeBlockHeight == appBlockHeight {
|
||||
// We ran Commit, but if we crashed before state.Save(),
|
||||
// load the intermediate state and update the state.AppHash.
|
||||
// NOTE: If ABCI allowed rollbacks, we could just replay the
|
||||
// block even though it's been committed
|
||||
stateAppHash := h.state.AppHash
|
||||
lastBlockAppHash := h.store.LoadBlock(storeBlockHeight).AppHash
|
||||
// We already ran Commit, so run through consensus with mock app
|
||||
mockApp := newMockProxyApp(appHash)
|
||||
|
||||
if bytes.Equal(stateAppHash, appHash) {
|
||||
// we're all synced up
|
||||
log.Debug("ABCI RelpayBlocks: Already synced")
|
||||
} else if bytes.Equal(stateAppHash, lastBlockAppHash) {
|
||||
// we crashed after commit and before saving state,
|
||||
// so load the intermediate state and update the hash
|
||||
h.state.LoadIntermediate()
|
||||
h.state.AppHash = appHash
|
||||
log.Debug("ABCI RelpayBlocks: Loaded intermediate state and updated state.AppHash")
|
||||
} else {
|
||||
PanicSanity(Fmt("Unexpected state.AppHash: state.AppHash %X; app.AppHash %X, lastBlock.AppHash %X", stateAppHash, appHash, lastBlockAppHash))
|
||||
h.replayLastBlock(h.config, h.state, mockApp, h.store)
|
||||
|
||||
}
|
||||
return nil
|
||||
|
||||
} else if storeBlockHeight == appBlockHeight+1 &&
|
||||
storeBlockHeight == stateBlockHeight+1 {
|
||||
} else if storeBlockHeight == appBlockHeight+1 {
|
||||
// We crashed after saving the block
|
||||
// but before Commit (both the state and app are behind),
|
||||
// so just replay the block
|
||||
// so run through consensus with the real app
|
||||
h.replayLastBlock(h.config, h.state, proxyApp.Consensus(), h.store)
|
||||
|
||||
// check that the lastBlock.AppHash matches the state apphash
|
||||
block := h.store.LoadBlock(storeBlockHeight)
|
||||
if !bytes.Equal(block.Header.AppHash, appHash) {
|
||||
return ErrLastStateMismatch{storeBlockHeight, block.Header.AppHash, appHash}
|
||||
}
|
||||
|
||||
blockMeta := h.store.LoadBlockMeta(storeBlockHeight)
|
||||
|
||||
h.nBlocks += 1
|
||||
var eventCache types.Fireable // nil
|
||||
|
||||
// replay the latest block
|
||||
return h.state.ApplyBlock(eventCache, appConnConsensus, block, blockMeta.BlockID.PartsHeader, MockMempool{})
|
||||
} else if storeBlockHeight != stateBlockHeight {
|
||||
// unless we failed before committing or saving state (previous 2 case),
|
||||
// the store and state should be at the same height!
|
||||
PanicSanity(Fmt("Expected storeHeight (%d) and stateHeight (%d) to match.", storeBlockHeight, stateBlockHeight))
|
||||
} else {
|
||||
// store is more than one ahead,
|
||||
// so app wants to replay many blocks
|
||||
/*
|
||||
|
||||
// replay all blocks starting with appBlockHeight+1
|
||||
var eventCache types.Fireable // nil
|
||||
// replay all blocks starting with appBlockHeight+1
|
||||
var eventCache types.Fireable // nil
|
||||
|
||||
// TODO: use stateBlockHeight instead and let the consensus state
|
||||
// do the replay
|
||||
// TODO: use stateBlockHeight instead and let the consensus state
|
||||
// do the replay
|
||||
|
||||
var appHash []byte
|
||||
for i := appBlockHeight + 1; i <= storeBlockHeight; i++ {
|
||||
h.nBlocks += 1
|
||||
block := h.store.LoadBlock(i)
|
||||
_, err := execBlockOnProxyApp(eventCache, appConnConsensus, block)
|
||||
if err != nil {
|
||||
log.Warn("Error executing block on proxy app", "height", i, "err", err)
|
||||
return err
|
||||
var appHash []byte
|
||||
for i := appBlockHeight + 1; i <= storeBlockHeight; i++ {
|
||||
h.nBlocks += 1
|
||||
block := h.store.LoadBlock(i)
|
||||
_, err := execBlockOnProxyApp(eventCache, appConnConsensus, block)
|
||||
if err != nil {
|
||||
log.Warn("Error executing block on proxy app", "height", i, "err", err)
|
||||
return err
|
||||
}
|
||||
// Commit block, get hash back
|
||||
res := appConnConsensus.CommitSync()
|
||||
if res.IsErr() {
|
||||
log.Warn("Error in proxyAppConn.CommitSync", "error", res)
|
||||
return res
|
||||
}
|
||||
if res.Log != "" {
|
||||
log.Info("Commit.Log: " + res.Log)
|
||||
}
|
||||
appHash = res.Data
|
||||
}
|
||||
// Commit block, get hash back
|
||||
res := appConnConsensus.CommitSync()
|
||||
if res.IsErr() {
|
||||
log.Warn("Error in proxyAppConn.CommitSync", "error", res)
|
||||
return res
|
||||
if !bytes.Equal(h.state.AppHash, appHash) {
|
||||
return errors.New(Fmt("Tendermint state.AppHash does not match AppHash after replay. Got %X, expected %X", appHash, h.state.AppHash))
|
||||
}
|
||||
if res.Log != "" {
|
||||
log.Info("Commit.Log: " + res.Log)
|
||||
}
|
||||
appHash = res.Data
|
||||
}
|
||||
if !bytes.Equal(h.state.AppHash, appHash) {
|
||||
return errors.New(Fmt("Tendermint state.AppHash does not match AppHash after replay. Got %X, expected %X", appHash, h.state.AppHash))
|
||||
}
|
||||
*/
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------
|
||||
|
||||
func newMockProxyApp(appHash []byte) proxy.AppConnConsensus {
|
||||
clientCreator := proxy.NewLocalClientCreator(&mockProxyApp{appHash: appHash})
|
||||
cli, _ := clientCreator.NewABCIClient()
|
||||
return proxy.NewAppConnConsensus(cli)
|
||||
}
|
||||
|
||||
type mockProxyApp struct {
|
||||
abci.BaseApplication
|
||||
|
||||
appHash []byte
|
||||
}
|
||||
|
||||
func (mock *mockProxyApp) Commit() abci.Result {
|
||||
return abci.NewResultOK(mock.appHash, "")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user