diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b38b1127937..499d02bb79c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -184,6 +184,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### State Machine Breaking +* (baseapp) [#18627](https://github.com/cosmos/cosmos-sdk/pull/18627) Post handlers are run on non successful transaction executions too. * (x/upgrade) [#16244](https://github.com/cosmos/cosmos-sdk/pull/16244) Upgrade module no longer stores the app version but gets and sets the app version stored in the `ParamStore` of baseapp. * (x/staking) [#17655](https://github.com/cosmos/cosmos-sdk/pull/17655) `HistoricalInfo` was replaced with `HistoricalRecord`, it removes the validator set and comet header and only keep what is needed for IBC. diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index 7fea57304593..29f1839af001 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -938,24 +938,25 @@ func (app *BaseApp) runTx(mode execMode, txBytes []byte) (gInfo sdk.GasInfo, res if err == nil { result, err = app.runMsgs(runMsgCtx, msgs, msgsV2, mode) } - if err == nil { - // Run optional postHandlers. - // - // Note: If the postHandler fails, we also revert the runMsgs state. - if app.postHandler != nil { - // The runMsgCtx context currently contains events emitted by the ante handler. - // We clear this to correctly order events without duplicates. - // Note that the state is still preserved. - postCtx := runMsgCtx.WithEventManager(sdk.NewEventManager()) - - newCtx, err := app.postHandler(postCtx, tx, mode == execModeSimulate, err == nil) - if err != nil { - return gInfo, nil, anteEvents, err - } - result.Events = append(result.Events, newCtx.EventManager().ABCIEvents()...) + // Run optional postHandlers (should run regardless of the execution result). + // + // Note: If the postHandler fails, we also revert the runMsgs state. + if app.postHandler != nil { + // The runMsgCtx context currently contains events emitted by the ante handler. + // We clear this to correctly order events without duplicates. + // Note that the state is still preserved. + postCtx := runMsgCtx.WithEventManager(sdk.NewEventManager()) + + newCtx, err := app.postHandler(postCtx, tx, mode == execModeSimulate, err == nil) + if err != nil { + return gInfo, nil, anteEvents, err } + result.Events = append(result.Events, newCtx.EventManager().ABCIEvents()...) + } + + if err == nil { if mode == execModeFinalize { // When block gas exceeds, it'll panic and won't commit the cached store. consumeBlockGas() diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index c769f773c3d2..67141a5e3002 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -594,6 +594,53 @@ func TestBaseAppAnteHandler(t *testing.T) { require.NoError(t, err) } +func TestBaseAppPostHandler(t *testing.T) { + postHandlerRun := false + anteOpt := func(bapp *baseapp.BaseApp) { + bapp.SetPostHandler(func(ctx sdk.Context, tx sdk.Tx, simulate, success bool) (newCtx sdk.Context, err error) { + postHandlerRun = true + return ctx, nil + }) + } + + suite := NewBaseAppSuite(t, anteOpt) + + baseapptestutil.RegisterCounterServer(suite.baseApp.MsgServiceRouter(), CounterServerImpl{t, capKey1, []byte("foo")}) + + _, err := suite.baseApp.InitChain(&abci.RequestInitChain{ + ConsensusParams: &cmtproto.ConsensusParams{}, + }) + require.NoError(t, err) + + // execute a tx that will fail ante handler execution + // + // NOTE: State should not be mutated here. This will be implicitly checked by + // the next txs ante handler execution (anteHandlerTxTest). + tx := newTxCounter(t, suite.txConfig, 0, 0) + txBytes, err := suite.txConfig.TxEncoder()(tx) + require.NoError(t, err) + + res, err := suite.baseApp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1, Txs: [][]byte{txBytes}}) + require.NoError(t, err) + require.Empty(t, res.Events) + require.True(t, res.TxResults[0].IsOK(), fmt.Sprintf("%v", res)) + + // PostHandler runs on successful message execution + require.True(t, postHandlerRun) + + // It should also run on failed message execution + postHandlerRun = false + tx = setFailOnHandler(t, suite.txConfig, tx, true) + txBytes, err = suite.txConfig.TxEncoder()(tx) + require.NoError(t, err) + res, err = suite.baseApp.FinalizeBlock(&abci.RequestFinalizeBlock{Height: 1, Txs: [][]byte{txBytes}}) + require.NoError(t, err) + require.Empty(t, res.Events) + require.False(t, res.TxResults[0].IsOK(), fmt.Sprintf("%v", res)) + + require.True(t, postHandlerRun) +} + // Test and ensure that invalid block heights always cause errors. // See issues: // - https://github.com/cosmos/cosmos-sdk/issues/11220 diff --git a/docs/architecture/adr-007-specialization-groups.md b/docs/architecture/adr-007-specialization-groups.md index bafcc697bf2c..dd5617a49d09 100644 --- a/docs/architecture/adr-007-specialization-groups.md +++ b/docs/architecture/adr-007-specialization-groups.md @@ -10,7 +10,7 @@ This idea was first conceived of in order to fulfill the use case of the creation of a decentralized Computer Emergency Response Team (dCERT), whose members would be elected by a governing community and would fulfill the role of coordinating the community under emergency situations. This thinking -can be further abstracted into the conception of "blockchain specialization +can be further abstracted into the concept of "blockchain specialization groups". The creation of these groups are the beginning of specialization capabilities @@ -44,7 +44,7 @@ A specialization group can be broadly broken down into the following functions * Individual compensation for all constituents of a group from the greater community -Membership admittance to a specialization group could take place over a wide +Membership admission to a specialization group could take place over a wide variety of mechanisms. The most obvious example is through a general vote among the entire community, however in certain systems a community may want to allow the members already in a specialization group to internally elect new members,