mirror of
https://github.com/fluencelabs/tendermint
synced 2025-07-15 12:31:42 +00:00
Compare commits
4 Commits
master
...
aditya/rev
Author | SHA1 | Date | |
---|---|---|---|
4ac6c1defc | |||
d8aaaf63fb | |||
7c11fc4116 | |||
22e2484878 |
@ -179,6 +179,25 @@ func (blockExec *BlockExecutor) ApplyBlock(state State, blockID types.BlockID, b
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// RevertBlock takes in a reverted block and the new latest block and returns the state
|
||||
// to its previous State before reverted block got applied
|
||||
// reverted block must be the LastBlockHeight of current state or will return error
|
||||
// Will not revert changes to mempool or evpool
|
||||
func (blockExec *BlockExecutor) RevertBlock(state State, reverted, newHead *types.Block) (State, error) {
|
||||
if reverted.Header.Height != state.LastBlockHeight {
|
||||
return state, fmt.Errorf("Can only revert latest block. Expected reverted block %d, got %d", state.LastBlockHeight, reverted.Header.Height)
|
||||
}
|
||||
prevState := blockExec.revertState(state, reverted, newHead)
|
||||
// revert state to previous version
|
||||
SaveState(blockExec.db, prevState)
|
||||
// delete peripheral state info for reverted height
|
||||
oldHeight := reverted.Header.Height
|
||||
deleteABCIResponses(blockExec.db, oldHeight)
|
||||
deleteConsensusParamsInfo(blockExec.db, oldHeight+1)
|
||||
deleteValidatorsInfo(blockExec.db, oldHeight+2)
|
||||
return prevState, 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.
|
||||
@ -442,6 +461,40 @@ func updateState(
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (blockExec *BlockExecutor) revertState(
|
||||
state State,
|
||||
reverted *types.Block,
|
||||
newHead *types.Block,
|
||||
) State {
|
||||
|
||||
lastValidatorsInfo := loadValidatorsInfo(blockExec.db, state.LastBlockHeight-1)
|
||||
lastValidators, _ := LoadValidators(blockExec.db, lastValidatorsInfo.LastHeightChanged)
|
||||
lastHeightValsChanged := lastValidatorsInfo.LastHeightChanged
|
||||
|
||||
lastConsParamsInfo := loadConsensusParamsInfo(blockExec.db, state.LastBlockHeight-1)
|
||||
consParams, _ := LoadConsensusParams(blockExec.db, lastConsParamsInfo.LastHeightChanged)
|
||||
lastHeightConsChanged := lastConsParamsInfo.LastHeightChanged
|
||||
|
||||
abciResponses, _ := LoadABCIResponses(blockExec.db, state.LastBlockHeight-1)
|
||||
|
||||
return State{
|
||||
Version: state.Version,
|
||||
ChainID: state.ChainID,
|
||||
LastBlockHeight: state.LastBlockHeight - 1,
|
||||
LastBlockTotalTx: state.LastBlockTotalTx - int64(len(reverted.Data.Txs)),
|
||||
LastBlockID: reverted.Header.LastBlockID,
|
||||
LastBlockTime: newHead.Header.Time,
|
||||
NextValidators: state.Validators.Copy(),
|
||||
Validators: state.LastValidators.Copy(),
|
||||
LastValidators: lastValidators.Copy(),
|
||||
LastHeightValidatorsChanged: lastHeightValsChanged,
|
||||
ConsensusParams: consParams,
|
||||
LastHeightConsensusParamsChanged: lastHeightConsChanged,
|
||||
LastResultsHash: abciResponses.ResultsHash(),
|
||||
AppHash: reverted.Header.AppHash,
|
||||
}
|
||||
}
|
||||
|
||||
// Fire NewBlock, NewBlockHeader.
|
||||
// Fire TxEvent for every tx.
|
||||
// NOTE: if Tendermint crashes before commit, some or all of these events may be published again.
|
||||
|
@ -47,6 +47,31 @@ func TestApplyBlock(t *testing.T) {
|
||||
// TODO check state and mempool
|
||||
}
|
||||
|
||||
func TestRevertBlock(t *testing.T) {
|
||||
cc := proxy.NewLocalClientCreator(kvstore.NewKVStoreApplication())
|
||||
proxyApp := proxy.NewAppConns(cc)
|
||||
err := proxyApp.Start()
|
||||
require.Nil(t, err)
|
||||
defer proxyApp.Stop()
|
||||
|
||||
state, stateDB, privVals := makeState(3, 1)
|
||||
|
||||
blockExec := sm.NewBlockExecutor(stateDB, log.TestingLogger(), proxyApp.Consensus(),
|
||||
mock.Mempool{}, sm.MockEvidencePool{})
|
||||
|
||||
//nolint:ineffassign
|
||||
state1, block1, _, commit, err := makeAndCommitGoodBlock(state, 1, &types.Commit{}, state.Validators.GetProposer().Address, blockExec, privVals, nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
state2, block2, _, _, err := makeAndCommitGoodBlock(state1, 2, commit, state1.Validators.GetProposer().Address, blockExec, privVals, nil)
|
||||
require.Nil(t, err)
|
||||
|
||||
revertedState, _ := blockExec.RevertBlock(state2, block2, block1)
|
||||
require.True(t, state1.Equals(revertedState), "Expected:\n%v\n\nActual:\n%v", state1, revertedState)
|
||||
|
||||
// TODO: check changes in ValSet and ConsensusParams
|
||||
}
|
||||
|
||||
// TestBeginBlockValidators ensures we send absent validators list.
|
||||
func TestBeginBlockValidators(t *testing.T) {
|
||||
app := &testApp{}
|
||||
|
@ -40,33 +40,33 @@ func makeAndCommitGoodBlock(
|
||||
proposerAddr []byte,
|
||||
blockExec *sm.BlockExecutor,
|
||||
privVals map[string]types.PrivValidator,
|
||||
evidence []types.Evidence) (sm.State, types.BlockID, *types.Commit, error) {
|
||||
evidence []types.Evidence) (sm.State, *types.Block, types.BlockID, *types.Commit, error) {
|
||||
// A good block passes
|
||||
state, blockID, err := makeAndApplyGoodBlock(state, height, lastCommit, proposerAddr, blockExec, evidence)
|
||||
state, block, blockID, err := makeAndApplyGoodBlock(state, height, lastCommit, proposerAddr, blockExec, evidence)
|
||||
if err != nil {
|
||||
return state, types.BlockID{}, nil, err
|
||||
return state, nil, types.BlockID{}, nil, err
|
||||
}
|
||||
|
||||
// Simulate a lastCommit for this block from all validators for the next height
|
||||
commit, err := makeValidCommit(height, blockID, state.Validators, privVals)
|
||||
if err != nil {
|
||||
return state, types.BlockID{}, nil, err
|
||||
return state, nil, types.BlockID{}, nil, err
|
||||
}
|
||||
return state, blockID, commit, nil
|
||||
return state, block, blockID, commit, nil
|
||||
}
|
||||
|
||||
func makeAndApplyGoodBlock(state sm.State, height int64, lastCommit *types.Commit, proposerAddr []byte,
|
||||
blockExec *sm.BlockExecutor, evidence []types.Evidence) (sm.State, types.BlockID, error) {
|
||||
blockExec *sm.BlockExecutor, evidence []types.Evidence) (sm.State, *types.Block, types.BlockID, error) {
|
||||
block, _ := state.MakeBlock(height, makeTxs(height), lastCommit, evidence, proposerAddr)
|
||||
if err := blockExec.ValidateBlock(state, block); err != nil {
|
||||
return state, types.BlockID{}, err
|
||||
return state, nil, types.BlockID{}, err
|
||||
}
|
||||
blockID := types.BlockID{Hash: block.Hash(), PartsHeader: types.PartSetHeader{}}
|
||||
state, err := blockExec.ApplyBlock(state, blockID, block)
|
||||
if err != nil {
|
||||
return state, types.BlockID{}, err
|
||||
return state, nil, types.BlockID{}, err
|
||||
}
|
||||
return state, blockID, nil
|
||||
return state, block, blockID, nil
|
||||
}
|
||||
|
||||
func makeValidCommit(height int64, blockID types.BlockID, vals *types.ValidatorSet, privVals map[string]types.PrivValidator) (*types.Commit, error) {
|
||||
|
@ -170,6 +170,12 @@ func saveABCIResponses(db dbm.DB, height int64, abciResponses *ABCIResponses) {
|
||||
db.SetSync(calcABCIResponsesKey(height), abciResponses.Bytes())
|
||||
}
|
||||
|
||||
// DeleteABCIResponses should delete previously persisted ABCIResponses from database.
|
||||
// Should only be called by RevertBlock
|
||||
func deleteABCIResponses(db dbm.DB, height int64) {
|
||||
db.DeleteSync(calcABCIResponsesKey(height))
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// ValidatorsInfo represents the latest validator set, or the last height it changed
|
||||
@ -261,6 +267,12 @@ func saveValidatorsInfo(db dbm.DB, height, lastHeightChanged int64, valSet *type
|
||||
db.Set(calcValidatorsKey(height), valInfo.Bytes())
|
||||
}
|
||||
|
||||
// DeleteValidatorsInfo should delete previously persisted ValidatorsInfo from database.
|
||||
// Should only be called by RevertBlock
|
||||
func deleteValidatorsInfo(db dbm.DB, height int64) {
|
||||
db.DeleteSync(calcValidatorsKey(height))
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// ConsensusParamsInfo represents the latest consensus params, or the last height it changed
|
||||
@ -331,3 +343,9 @@ func saveConsensusParamsInfo(db dbm.DB, nextHeight, changeHeight int64, params t
|
||||
}
|
||||
db.Set(calcConsensusParamsKey(nextHeight), paramsInfo.Bytes())
|
||||
}
|
||||
|
||||
// DeleteConsensusParamsInfo should delete previously persisted ConsesnsusParamsInfo from database.
|
||||
// Should only be called by RevertBlock
|
||||
func deleteConsensusParamsInfo(db dbm.DB, nextHeight int64) {
|
||||
db.DeleteSync(calcConsensusParamsKey(nextHeight))
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ func TestValidateBlockHeader(t *testing.T) {
|
||||
A good block passes
|
||||
*/
|
||||
var err error
|
||||
state, _, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, nil)
|
||||
state, _, _, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, nil)
|
||||
require.NoError(t, err, "height %d", height)
|
||||
}
|
||||
}
|
||||
@ -123,7 +123,7 @@ func TestValidateBlockCommit(t *testing.T) {
|
||||
*/
|
||||
var err error
|
||||
var blockID types.BlockID
|
||||
state, blockID, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, nil)
|
||||
state, _, blockID, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, nil)
|
||||
require.NoError(t, err, "height %d", height)
|
||||
|
||||
/*
|
||||
@ -190,7 +190,7 @@ func TestValidateBlockEvidence(t *testing.T) {
|
||||
}
|
||||
|
||||
var err error
|
||||
state, _, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, evidence)
|
||||
state, _, _, lastCommit, err = makeAndCommitGoodBlock(state, height, lastCommit, proposerAddr, blockExec, privVals, evidence)
|
||||
require.NoError(t, err, "height %d", height)
|
||||
}
|
||||
}
|
||||
|
@ -195,6 +195,46 @@ func (bs *BlockStore) saveBlockPart(height int64, index int, part *types.Part) {
|
||||
bs.db.Set(calcBlockPartKey(height, index), partBytes)
|
||||
}
|
||||
|
||||
// RevertBlock reverts the store changes from the latest SaveBlock.
|
||||
// Only modifies blockstore, does not change TM state
|
||||
// For now, seenCommit for reverted block remains in db
|
||||
// Returns the block that got reverted and the newest lastBlock
|
||||
func (bs *BlockStore) RevertBlock() (reverted *types.Block, newHead *types.Block) {
|
||||
bs.mtx.RLock()
|
||||
latest := bs.height
|
||||
bs.mtx.RUnlock()
|
||||
|
||||
// Load block to return later
|
||||
reverted = bs.LoadBlock(latest)
|
||||
newHead = bs.LoadBlock(latest - 1)
|
||||
|
||||
// Delete block meta
|
||||
blockMeta := bs.LoadBlockMeta(latest)
|
||||
blockID := blockMeta.BlockID
|
||||
bs.db.Delete(calcBlockMetaKey(latest))
|
||||
|
||||
// Delete block parts
|
||||
for i := 0; i < blockID.PartsHeader.Total; i++ {
|
||||
bs.db.Delete(calcBlockPartKey(latest, i))
|
||||
}
|
||||
|
||||
// Delete block commit
|
||||
bs.db.Delete(calcBlockCommitKey(latest - 1))
|
||||
|
||||
// Update BlockStoreStateJSON descriptor
|
||||
BlockStoreStateJSON{Height: latest - 1}.Save(bs.db)
|
||||
|
||||
// Update height
|
||||
bs.mtx.Lock()
|
||||
bs.height = latest - 1
|
||||
bs.mtx.Unlock()
|
||||
|
||||
// Flush
|
||||
bs.db.SetSync(nil, nil)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
func calcBlockMetaKey(height int64) []byte {
|
||||
@ -222,6 +262,11 @@ type BlockStoreStateJSON struct {
|
||||
Height int64 `json:"height"`
|
||||
}
|
||||
|
||||
// Returns blockStoreKey for testing purposes
|
||||
func BlockStoreKey() []byte {
|
||||
return blockStoreKey
|
||||
}
|
||||
|
||||
// Save persists the blockStore state to the database as JSON.
|
||||
func (bsj BlockStoreStateJSON) Save(db dbm.DB) {
|
||||
bytes, err := cdc.MarshalJSON(bsj)
|
||||
|
@ -29,7 +29,7 @@ type cleanupFunc func()
|
||||
|
||||
// make a Commit with a single vote containing just the height and a timestamp
|
||||
func makeTestCommit(height int64, timestamp time.Time) *types.Commit {
|
||||
commitSigs := []*types.CommitSig{{Height: height, Timestamp: timestamp}}
|
||||
commitSigs := []*types.CommitSig{{Type: types.PrecommitType, Height: height, Timestamp: timestamp}}
|
||||
return types.NewCommit(types.BlockID{}, commitSigs)
|
||||
}
|
||||
|
||||
@ -321,6 +321,56 @@ func TestBlockStoreSaveLoadBlock(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRevertBlock(t *testing.T) {
|
||||
state, bs, cleanup := makeStateAndBlockStore(log.NewTMLogger(new(bytes.Buffer)))
|
||||
defer cleanup()
|
||||
require.Equal(t, bs.Height(), int64(0), "initially the height should be zero")
|
||||
|
||||
// Create and Save first block
|
||||
block1 := makeBlock(1, state, &types.Commit{})
|
||||
partset1 := block1.MakePartSet(2)
|
||||
seenCommit1 := makeTestCommit(1, tmtime.Now())
|
||||
|
||||
bs.SaveBlock(block1, partset1, seenCommit1)
|
||||
|
||||
// Sanity check: Loading first block returns correct value
|
||||
recovered := bs.LoadBlock(1)
|
||||
recovered.Data.Hash()
|
||||
require.True(t, block1.Equals(recovered), fmt.Sprintf("Expected:\n%v\n\nActual:\n%v", block1, recovered))
|
||||
|
||||
// Create and save second block
|
||||
header2 := types.Header{
|
||||
Height: 2,
|
||||
NumTxs: 100,
|
||||
ChainID: "block_test",
|
||||
Time: tmtime.Now(),
|
||||
LastCommitHash: seenCommit1.Hash(),
|
||||
}
|
||||
block2 := newBlock(header2, seenCommit1)
|
||||
partSet2 := block2.MakePartSet(2)
|
||||
seenCommit2 := makeTestCommit(2, tmtime.Now())
|
||||
|
||||
bs.SaveBlock(block2, partSet2, seenCommit2)
|
||||
|
||||
// Check height is updated correctly
|
||||
height := bs.Height()
|
||||
require.Equal(t, int64(2), height)
|
||||
|
||||
// Revert latest block: block2
|
||||
b2, b1 := bs.RevertBlock()
|
||||
|
||||
// Check height updated correctly
|
||||
height = bs.Height()
|
||||
require.Equal(t, int64(1), height)
|
||||
|
||||
b2.Data.Hash()
|
||||
b1.Data.Hash()
|
||||
|
||||
// Check returned blocks are correct
|
||||
require.True(t, block2.Equals(b2), "Expected:\n%v\n\nActual:%v\n", block2, b2)
|
||||
require.True(t, block1.Equals(b1), "Expected:\n%v\nActual:%v\n", block1, b1)
|
||||
}
|
||||
|
||||
func TestLoadBlockPart(t *testing.T) {
|
||||
bs, db := freshBlockStore()
|
||||
height, index := int64(10), 1
|
||||
|
@ -257,6 +257,22 @@ func (b *Block) StringShort() string {
|
||||
return fmt.Sprintf("Block#%v", b.Hash())
|
||||
}
|
||||
|
||||
// Equals returns whether two blocks are equal. Useful for testing
|
||||
func (b *Block) Equals(b2 *Block) bool {
|
||||
if b2 == nil {
|
||||
return false
|
||||
}
|
||||
bz1, err := b.Marshal()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
bz2, err := b2.Marshal()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return bytes.Equal(bz1, bz2)
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// These methods are for Protobuf Compatibility
|
||||
|
||||
|
Reference in New Issue
Block a user