Skip to content

Commit

Permalink
fix: panic recovery in PrepareProposal and ProcessProposal Handlers (c…
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderbez committed Dec 21, 2022
1 parent 1d16adc commit 1152e5b
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 4 deletions.
39 changes: 35 additions & 4 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,26 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc
//
// Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md
// Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md
func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) (resp abci.ResponsePrepareProposal) {
ctx := app.getContextForTx(runTxPrepareProposal, []byte{})
if app.prepareProposal == nil {
panic("PrepareProposal method not set")
}
return app.prepareProposal(ctx, req)

defer func() {
if err := recover(); err != nil {
app.logger.Error(
"panic recovered in PrepareProposal",
"height", req.Height,
"time", req.Time,
"panic", err,
)
resp = abci.ResponsePrepareProposal{Txs: req.Txs}
}
}()

resp = app.prepareProposal(ctx, req)
return resp
}

// ProcessProposal implements the ProcessProposal ABCI method and returns a
Expand All @@ -265,9 +279,12 @@ func (app *BaseApp) PrepareProposal(req abci.RequestPrepareProposal) abci.Respon
// to implement optimizations such as executing the entire proposed block
// immediately. It may even execute the block in parallel.
//
// If a panic is detected during execution of an application's ProcessProposal
// handler, it will be recovered and we will reject the proposal.
//
// Ref: https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-060-abci-1.0.md
// Ref: https://github.com/tendermint/tendermint/blob/main/spec/abci/abci%2B%2B_basic_concepts.md
func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.ResponseProcessProposal {
func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) (resp abci.ResponseProcessProposal) {
if app.processProposal == nil {
panic("app.ProcessProposal is not set")
}
Expand All @@ -280,7 +297,21 @@ func (app *BaseApp) ProcessProposal(req abci.RequestProcessProposal) abci.Respon
WithProposer(req.ProposerAddress).
WithConsensusParams(app.GetConsensusParams(app.processProposalState.ctx))

return app.processProposal(ctx, req)
defer func() {
if err := recover(); err != nil {
app.logger.Error(
"panic recovered in ProcessProposal",
"height", req.Height,
"time", req.Time,
"hash", fmt.Sprintf("%X", req.Hash),
"panic", err,
)
resp = abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_REJECT}
}
}()

resp = app.processProposal(ctx, req)
return resp
}

// CheckTx implements the ABCI interface and executes a tx in CheckTx mode. In
Expand Down
41 changes: 41 additions & 0 deletions baseapp/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package baseapp_test

import (
"bytes"
"errors"
"fmt"
"strings"
"testing"
Expand Down Expand Up @@ -1452,3 +1453,43 @@ func TestABCI_PrepareProposal_Failures(t *testing.T) {
res := suite.baseApp.PrepareProposal(req)
require.Equal(t, 1, len(res.Txs))
}

func TestABCI_PrepareProposal_PanicRecovery(t *testing.T) {
prepareOpt := func(app *baseapp.BaseApp) {
app.SetPrepareProposal(func(ctx sdk.Context, rpp abci.RequestPrepareProposal) abci.ResponsePrepareProposal {
panic(errors.New("test"))
})
}
suite := NewBaseAppSuite(t, prepareOpt)

suite.baseApp.InitChain(abci.RequestInitChain{
ConsensusParams: &tmproto.ConsensusParams{},
})

req := abci.RequestPrepareProposal{
MaxTxBytes: 1000,
}

require.NotPanics(t, func() {
res := suite.baseApp.PrepareProposal(req)
require.Equal(t, req.Txs, res.Txs)
})
}

func TestABCI_ProcessProposal_PanicRecovery(t *testing.T) {
processOpt := func(app *baseapp.BaseApp) {
app.SetProcessProposal(func(ctx sdk.Context, rpp abci.RequestProcessProposal) abci.ResponseProcessProposal {
panic(errors.New("test"))
})
}
suite := NewBaseAppSuite(t, processOpt)

suite.baseApp.InitChain(abci.RequestInitChain{
ConsensusParams: &tmproto.ConsensusParams{},
})

require.NotPanics(t, func() {
res := suite.baseApp.ProcessProposal(abci.RequestProcessProposal{})
require.Equal(t, res.Status, abci.ResponseProcessProposal_REJECT)
})
}

0 comments on commit 1152e5b

Please sign in to comment.