diff --git a/baseapp/abci.go b/baseapp/abci.go index 08dbe8d367..4244fcb4b7 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -1,6 +1,7 @@ package baseapp import ( + "context" "crypto/sha256" "errors" "fmt" @@ -16,6 +17,8 @@ import ( "google.golang.org/grpc/codes" grpcstatus "google.golang.org/grpc/status" + coreheader "cosmossdk.io/core/header" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/cosmos/cosmos-sdk/codec" snapshottypes "github.com/cosmos/cosmos-sdk/snapshots/types" "github.com/cosmos/cosmos-sdk/store/rootmulti" @@ -58,14 +61,14 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC } // initialize states with a correct header - app.setState(runTxModeDeliver, initHeader) + app.setState(execModeFinalize, initHeader) app.setState(runTxModeCheck, initHeader) // Store the consensus params in the BaseApp's paramstore. Note, this must be // done after the deliver state and context have been set as it's persisted // to state. if req.ConsensusParams != nil { - app.StoreConsensusParams(app.deliverState.ctx, req.ConsensusParams) + app.StoreConsensusParams(app.finalizeBlockState.ctx, req.ConsensusParams) } if app.initChainer == nil { @@ -73,9 +76,9 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC } // add block gas meter for any genesis transactions (allow infinite gas) - app.deliverState.ctx = app.deliverState.ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) + app.finalizeBlockState.ctx = app.finalizeBlockState.ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) - res = app.initChainer(app.deliverState.ctx, req) + res = app.initChainer(app.finalizeBlockState.ctx, req) // sanity check if len(req.Validators) > 0 { @@ -150,94 +153,6 @@ func (app *BaseApp) FilterPeerByID(info string) abci.ResponseQuery { return abci.ResponseQuery{} } -// BeginBlock implements the ABCI application interface. -func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) { - if req.Header.ChainID != app.chainID { - panic(fmt.Sprintf("invalid chain-id on BeginBlock; expected: %s, got: %s", app.chainID, req.Header.ChainID)) - } - - if app.cms.TracingEnabled() { - app.cms.SetTracingContext(sdk.TraceContext( - map[string]interface{}{"blockHeight": req.Header.Height}, - )) - } - - if err := app.validateHeight(req); err != nil { - panic(err) - } - - // Initialize the DeliverTx state. If this is the first block, it should - // already be initialized in InitChain. Otherwise app.deliverState will be - // nil, since it is reset on Commit. - if app.deliverState == nil { - app.setState(runTxModeDeliver, req.Header) - } else { - // In the first block, app.deliverState.ctx will already be initialized - // by InitChain. Context is now updated with Header information. - app.deliverState.ctx = app.deliverState.ctx. - WithBlockHeader(req.Header). - WithBlockHeight(req.Header.Height) - } - - gasMeter := app.getBlockGasMeter(app.deliverState.ctx) - - app.deliverState.ctx = app.deliverState.ctx. - WithBlockGasMeter(gasMeter). - WithHeaderHash(req.Hash). - WithConsensusParams(app.GetConsensusParams(app.deliverState.ctx)) - - if app.checkState != nil { - app.checkState.ctx = app.checkState.ctx. - WithBlockGasMeter(gasMeter). - WithHeaderHash(req.Hash) - } - - if app.beginBlocker != nil { - res = app.beginBlocker(app.deliverState.ctx, req) - res.Events = sdk.MarkEventsToIndex(res.Events, app.indexEvents) - } - // set the signed validators for addition to context in deliverTx - app.voteInfos = req.LastCommitInfo.GetVotes() - - // call the hooks with the BeginBlock messages - for _, streamingListener := range app.abciListeners { - if err := streamingListener.ListenBeginBlock(app.deliverState.ctx, req, res); err != nil { - panic(fmt.Errorf("BeginBlock listening hook failed, height: %d, err: %w", req.Header.Height, err)) - } - } - - // Reset the gas meter so that the AnteHandlers aren't required to - gasMeter = app.getBlockGasMeter(app.deliverState.ctx) - app.deliverState.ctx = app.deliverState.ctx.WithBlockGasMeter(gasMeter) - - return res -} - -// EndBlock implements the ABCI interface. -func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) { - if app.deliverState.ms.TracingEnabled() { - app.deliverState.ms = app.deliverState.ms.SetTracingContext(nil).(sdk.CacheMultiStore) - } - - if app.endBlocker != nil { - res = app.endBlocker(app.deliverState.ctx, req) - res.Events = sdk.MarkEventsToIndex(res.Events, app.indexEvents) - } - - if cp := app.GetConsensusParams(app.deliverState.ctx); cp != nil { - res.ConsensusParamUpdates = cp - } - - // call the streaming service hooks with the EndBlock messages - for _, streamingListener := range app.abciListeners { - if err := streamingListener.ListenEndBlock(app.deliverState.ctx, req, res); err != nil { - panic(fmt.Errorf("EndBlock listening hook failed, height: %d, err: %w", req.Height, err)) - } - } - - return res -} - // PrepareProposal implements the PrepareProposal ABCI method and returns a // ResponsePrepareProposal object to the client. The PrepareProposal method is // responsible for allowing the block proposer to perform application-dependent @@ -371,7 +286,7 @@ func (app *BaseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { panic(fmt.Sprintf("unknown RequestCheckTx type: %s", req.Type)) } - gInfo, result, anteEvents, priority, err := app.runTx(mode, req.Tx) + gInfo, result, anteEvents, err := app.runTx(mode, req.Tx) if err != nil { return sdkerrors.ResponseCheckTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, anteEvents, app.trace) } @@ -382,7 +297,6 @@ func (app *BaseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { Log: result.Log, Data: result.Data, Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents), - Priority: priority, } } @@ -397,7 +311,7 @@ func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) (res abci.ResponseDeliv defer func() { for _, streamingListener := range app.abciListeners { - if err := streamingListener.ListenDeliverTx(app.deliverState.ctx, req, res); err != nil { + if err := streamingListener.ListenDeliverTx(app.finalizeBlockState.ctx, req, res); err != nil { panic(fmt.Errorf("DeliverTx listening hook failed: %w", err)) } } @@ -410,7 +324,7 @@ func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) (res abci.ResponseDeliv telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted") }() - gInfo, result, anteEvents, _, err := app.runTx(runTxModeDeliver, req.Tx) + gInfo, result, anteEvents, err := app.runTx(execModeFinalize, req.Tx) if err != nil { resultStr = "failed" return sdkerrors.ResponseDeliverTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, sdk.MarkEventsToIndex(anteEvents, app.indexEvents), app.trace) @@ -433,7 +347,7 @@ func (app *BaseApp) DeliverTx(req abci.RequestDeliverTx) (res abci.ResponseDeliv // against that height and gracefully halt if it matches the latest committed // height. func (app *BaseApp) Commit() abci.ResponseCommit { - header := app.deliverState.ctx.BlockHeader() + header := app.finalizeBlockState.Context().BlockHeader() retainHeight := app.GetBlockRetentionHeight(header.Height) rms, ok := app.cms.(*rootmulti.Store) @@ -441,56 +355,24 @@ func (app *BaseApp) Commit() abci.ResponseCommit { rms.SetCommitHeader(header) } - // Write the DeliverTx state into branched storage and commit the MultiStore. - // The write to the DeliverTx state writes all state transitions to the root - // MultiStore (app.cms) so when Commit() is called is persists those values. - app.deliverState.ms.Write() - commitID := app.cms.Commit() + app.cms.Commit() - res := abci.ResponseCommit{ - Data: commitID.Hash, + resp := abci.ResponseCommit{ RetainHeight: retainHeight, } - // call the hooks with the Commit message - for _, streamingListener := range app.abciListeners { - if err := streamingListener.ListenCommit(app.deliverState.ctx, res); err != nil { - panic(fmt.Errorf("Commit listening hook failed, height: %d, err: %w", header.Height, err)) - } - } - - app.logger.Info("commit synced", "commit", fmt.Sprintf("%X", commitID)) - - // Reset the Check state to the latest committed. + // Reset the CheckTx state to the latest committed. // - // NOTE: This is safe because Tendermint holds a lock on the mempool for + // NOTE: This is safe because CometBFT holds a lock on the mempool for // Commit. Use the header from this latest block. app.setState(runTxModeCheck, header) - // empty/reset the deliver state - app.deliverState = nil - - var halt bool - - switch { - case app.haltHeight > 0 && uint64(header.Height) >= app.haltHeight: - halt = true - - case app.haltTime > 0 && header.Time.Unix() >= int64(app.haltTime): - halt = true - } + app.finalizeBlockState = nil - if halt { - // Halt the binary and allow Tendermint to receive the ResponseCommit - // response with the commit ID hash. This will allow the node to successfully - // restart and process blocks assuming the halt configuration has been - // reset or moved to a more distant value. - app.halt() - } - - go app.snapshotManager.SnapshotIfApplicable(header.Height) + // The SnapshotIfApplicable method will create the snapshot by starting the goroutine + app.snapshotManager.SnapshotIfApplicable(header.Height) - return res + return resp } // halt attempts to gracefully shutdown the node via SIGINT and SIGTERM falling @@ -852,7 +734,7 @@ func (app *BaseApp) GetBlockRetentionHeight(commitHeight int64) int64 { // evidence parameters instead of computing an estimated nubmer of blocks based // on the unbonding period and block commitment time as the two should be // equivalent. - cp := app.GetConsensusParams(app.deliverState.ctx) + cp := app.GetConsensusParams(app.finalizeBlockState.ctx) if cp != nil && cp.Evidence != nil && cp.Evidence.MaxAgeNumBlocks > 0 { retentionHeight = commitHeight - cp.Evidence.MaxAgeNumBlocks } @@ -992,7 +874,7 @@ func SplitABCIQueryPath(requestPath string) (path []string) { // any state changes made in InitChain. func (app *BaseApp) getContextForProposal(ctx sdk.Context, height int64) sdk.Context { if height == app.initialHeight { - ctx, _ = app.deliverState.ctx.CacheContext() + ctx, _ = app.finalizeBlockState.ctx.CacheContext() // clear all context data set during InitChain to avoid inconsistent behavior ctx = ctx.WithBlockHeader(tmproto.Header{}) @@ -1001,3 +883,267 @@ func (app *BaseApp) getContextForProposal(ctx sdk.Context, height int64) sdk.Con return ctx } + +// workingHash gets the apphash that will be finalized in commit. +// These writes will be persisted to the root multi-store (app.cms) and flushed to +// disk in the Commit phase. This means when the ABCI client requests Commit(), the application +// state transitions will be flushed to disk and as a result, but we already have +// an application Merkle root. +func (app *BaseApp) workingHash() []byte { + // Write the FinalizeBlock state into branched storage and commit the MultiStore. + // The write to the FinalizeBlock state writes all state transitions to the root + // MultiStore (app.cms) so when Commit() is called it persists those values. + app.finalizeBlockState.ms.Write() + + // Get the hash of all writes in order to return the apphash to the comet in finalizeBlock. + commitHash := app.cms.WorkingHash() + app.logger.Debug("hash of all writes", "workingHash", fmt.Sprintf("%X", commitHash)) + + return commitHash +} + +// checkHalt checkes if height or time exceeds halt-height or halt-time respectively. +func (app *BaseApp) checkHalt(height int64, time time.Time) error { + var halt bool + switch { + case app.haltHeight > 0 && uint64(height) > app.haltHeight: + halt = true + + case app.haltTime > 0 && time.Unix() > int64(app.haltTime): + halt = true + } + + if halt { + return fmt.Errorf("halt per configuration height %d time %d", app.haltHeight, app.haltTime) + } + + return nil +} + +func (app *BaseApp) validateFinalizeBlockHeight(req *abci.RequestFinalizeBlock) error { + if req.Height < 1 { + return fmt.Errorf("invalid height: %d", req.Height) + } + + lastBlockHeight := app.LastBlockHeight() + + // expectedHeight holds the expected height to validate + var expectedHeight int64 + if lastBlockHeight == 0 && app.initialHeight > 1 { + // In this case, we're validating the first block of the chain, i.e no + // previous commit. The height we're expecting is the initial height. + expectedHeight = app.initialHeight + } else { + // This case can mean two things: + // + // - Either there was already a previous commit in the store, in which + // case we increment the version from there. + // - Or there was no previous commit, in which case we start at version 1. + expectedHeight = lastBlockHeight + 1 + } + + if req.Height != expectedHeight { + return fmt.Errorf("invalid height: %d; expected: %d", req.Height, expectedHeight) + } + + return nil +} + +// internalFinalizeBlock executes the block, called by the Optimistic +// Execution flow or by the FinalizeBlock ABCI method. The context received is +// only used to handle early cancellation, for anything related to state app.finalizeBlockState.Context() +// must be used. +func (app *BaseApp) internalFinalizeBlock(ctx context.Context, req *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { + var events []abci.Event + + if err := app.checkHalt(req.Height, req.Time); err != nil { + return nil, err + } + + if err := app.validateFinalizeBlockHeight(req); err != nil { + return nil, err + } + + header := cmtproto.Header{ + ChainID: app.chainID, + Height: req.Height, + Time: req.Time, + ProposerAddress: req.ProposerAddress, + NextValidatorsHash: req.NextValidatorsHash, + AppHash: app.LastCommitID().Hash, + } + + // finalizeBlockState should be set on InitChain or ProcessProposal. If it is + // nil, it means we are replaying this block and we need to set the state here + // given that during block replay ProcessProposal is not executed by CometBFT. + if app.finalizeBlockState == nil { + app.setState(execModeFinalize, header) + } + + // Context is now updated with Header information. + app.finalizeBlockState.SetContext(app.finalizeBlockState.Context(). + WithBlockHeader(header). + WithHeaderHash(req.Hash). + WithHeaderInfo(coreheader.Info{ + ChainID: app.chainID, + Height: req.Height, + Time: req.Time, + Hash: req.Hash, + AppHash: app.LastCommitID().Hash, + }). + WithConsensusParams(app.GetConsensusParams(app.finalizeBlockState.Context())). + WithVoteInfos(req.DecidedLastCommit.Votes). + WithExecMode(sdk.ExecModeFinalize). + WithCometInfo(cometInfo{ + Misbehavior: req.Misbehavior, + ValidatorsHash: req.NextValidatorsHash, + ProposerAddress: req.ProposerAddress, + LastCommit: req.DecidedLastCommit, + })) + + // GasMeter must be set after we get a context with updated consensus params. + gasMeter := app.getBlockGasMeter(app.finalizeBlockState.Context()) + app.finalizeBlockState.SetContext(app.finalizeBlockState.Context().WithBlockGasMeter(gasMeter)) + + if app.checkState != nil { + app.checkState.SetContext(app.checkState.Context(). + WithBlockGasMeter(gasMeter). + WithHeaderHash(req.Hash)) + } + + preblockEvents, err := app.preBlock(req) + if err != nil { + return nil, err + } + + events = append(events, preblockEvents...) + + beginBlock, err := app.beginBlock(req) + if err != nil { + return nil, err + } + + // First check for an abort signal after beginBlock, as it's the first place + // we spend any significant amount of time. + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + // continue + } + + events = append(events, beginBlock.Events...) + + // Reset the gas meter so that the AnteHandlers aren't required to + gasMeter = app.getBlockGasMeter(app.finalizeBlockState.Context()) + app.finalizeBlockState.SetContext(app.finalizeBlockState.Context().WithBlockGasMeter(gasMeter)) + + // Iterate over all raw transactions in the proposal and attempt to execute + // them, gathering the execution results. + // + // NOTE: Not all raw transactions may adhere to the sdk.Tx interface, e.g. + // vote extensions, so skip those. + txResults := make([]*abci.ExecTxResult, 0, len(req.Txs)) + app.logger.Info("executing transactions", "numTxs", len(req.Txs)) + for _, rawTx := range req.Txs { + var response *abci.ExecTxResult + + if _, err := app.txDecoder(rawTx); err == nil { + response = app.deliverTx(rawTx) + } else { + // In the case where a transaction included in a block proposal is malformed, + // we still want to return a default response to comet. This is because comet + // expects a response for each transaction included in a block proposal. + response = sdkerrors.ResponseExecTxResultWithEvents( + sdkerrors.ErrTxDecode, + 0, + 0, + nil, + false, + ) + } + + // check after every tx if we should abort + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + // continue + } + + txResults = append(txResults, response) + } + app.logger.Info("executed transactions", "numTxs", len(req.Txs)) + + endBlock, err := app.endBlock(app.finalizeBlockState.Context()) + if err != nil { + return nil, err + } + + // check after endBlock if we should abort, to avoid propagating the result + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + // continue + } + + events = append(events, endBlock.Events...) + cp := app.GetConsensusParams(app.finalizeBlockState.Context()) + + return &abci.ResponseFinalizeBlock{ + Events: events, + TxResults: txResults, + ValidatorUpdates: endBlock.ValidatorUpdates, + ConsensusParamUpdates: cp, + }, nil +} + +// FinalizeBlock will execute the block proposal provided by RequestFinalizeBlock. +// Specifically, it will execute an application's BeginBlock (if defined), followed +// by the transactions in the proposal, finally followed by the application's +// EndBlock (if defined). +// +// For each raw transaction, i.e. a byte slice, BaseApp will only execute it if +// it adheres to the sdk.Tx interface. Otherwise, the raw transaction will be +// skipped. This is to support compatibility with proposers injecting vote +// extensions into the proposal, which should not themselves be executed in cases +// where they adhere to the sdk.Tx interface. +func (app *BaseApp) FinalizeBlock(req *abci.RequestFinalizeBlock) (res *abci.ResponseFinalizeBlock, err error) { + defer func() { + // call the streaming service hooks with the FinalizeBlock messages + for _, streamingListener := range app.abciListeners { + if err := streamingListener.ListenFinalizeBlock(app.finalizeBlockState.Context(), *req, *res); err != nil { + app.logger.Error("ListenFinalizeBlock listening hook failed", "height", req.Height, "err", err) + } + } + }() + + if app.optimisticExec.Initialized() { + // check if the hash we got is the same as the one we are executing + aborted := app.optimisticExec.AbortIfNeeded(req.Hash) + // Wait for the OE to finish, regardless of whether it was aborted or not + res, err = app.optimisticExec.WaitResult() + + // only return if we are not aborting + if !aborted { + if res != nil { + res.AppHash = app.workingHash() + } + + return res, err + } + + // if it was aborted, we need to reset the state + app.finalizeBlockState = nil + app.optimisticExec.Reset() + } + + // if no OE is running, just run the block (this is either a block replay or a OE that got aborted) + res, err = app.internalFinalizeBlock(context.Background(), req) + if res != nil { + res.AppHash = app.workingHash() + } + + return res, err +} diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index bcae791730..ec19cbced6 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -14,10 +14,13 @@ import ( "github.com/cosmos/gogoproto/proto" "golang.org/x/exp/maps" + errorsmod "cosmossdk.io/errors" + "github.com/cosmos/cosmos-sdk/baseapp/oe" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/snapshots" "github.com/cosmos/cosmos-sdk/store" storetypes "github.com/cosmos/cosmos-sdk/store/types" + "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/mempool" @@ -35,12 +38,12 @@ type ( ) const ( - runTxModeCheck runTxMode = iota // Check a transaction - runTxModeReCheck // Recheck a (pending) transaction after a commit - runTxModeSimulate // Simulate a transaction - runTxModeDeliver // Deliver a transaction - runTxPrepareProposal // Prepare a TM block proposal - runTxProcessProposal // Process a TM block proposal + runTxModeCheck runTxMode = iota // Check a transaction execModeCheck + runTxModeReCheck // Recheck a (pending) transaction after a commit execModeReCheck + runTxModeSimulate // Simulate a transaction execModeSimulate + runTxPrepareProposal // Prepare a TM block proposal execModePrepareProposal + runTxProcessProposal // Process a TM block proposal execModeProcessProposal + execModeFinalize // Finalize a block proposal ) var _ abci.Application = (*BaseApp)(nil) @@ -80,7 +83,7 @@ type BaseApp struct { //nolint: maligned // checkState is set on InitChain and reset on Commit // deliverState is set on InitChain and BeginBlock and set to nil on Commit checkState *state // for CheckTx - deliverState *state // for DeliverTx + finalizeBlockState *state // for DeliverTx processProposalState *state // for ProcessProposal prepareProposalState *state // for PrepareProposal @@ -144,6 +147,12 @@ type BaseApp struct { //nolint: maligned abciListeners []ABCIListener chainID string + + // optimisticExec contains the context required for Optimistic Execution, + // including the goroutine handling.This is experimental and must be enabled + // by developers. + optimisticExec *oe.OptimisticExecution + preBlocker sdk.PreBlocker // logic to run before BeginBlocker } // NewBaseApp returns a reference to an initialized BaseApp. It accepts a @@ -431,9 +440,9 @@ func (app *BaseApp) setState(mode runTxMode, header tmproto.Header) { // Minimum gas prices are also set. It is set on InitChain and reset on Commit. baseState.ctx = baseState.ctx.WithIsCheckTx(true).WithMinGasPrices(app.minGasPrices) app.checkState = baseState - case runTxModeDeliver: + case execModeFinalize: // It is set on InitChain and BeginBlock and set to nil on Commit. - app.deliverState = baseState + app.finalizeBlockState = baseState case runTxPrepareProposal: // It is set on InitChain and Commit. app.prepareProposalState = baseState @@ -555,8 +564,8 @@ func validateBasicTxMsgs(msgs []sdk.Msg) error { // if app is in runTxProcessProposal, and checkState otherwise. func (app *BaseApp) getState(mode runTxMode) *state { switch mode { - case runTxModeDeliver: - return app.deliverState + case execModeFinalize: + return app.finalizeBlockState case runTxPrepareProposal: return app.prepareProposalState @@ -626,7 +635,7 @@ func (app *BaseApp) cacheTxContext(ctx sdk.Context, txBytes []byte) (sdk.Context // Note, gas execution info is always returned. A reference to a Result is // returned if the tx does not run out of gas and if all the messages are valid // and execute successfully. An error is returned otherwise. -func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, priority int64, err error) { +func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, result *sdk.Result, anteEvents []abci.Event, err error) { // NOTE: GasWanted should be returned by the AnteHandler. GasUsed is // determined by the GasMeter. We need access to the context to get the gas // meter, so we initialize upfront. @@ -636,8 +645,8 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re ms := ctx.MultiStore() // only run the tx if there is block gas remaining - if mode == runTxModeDeliver && ctx.BlockGasMeter().IsOutOfGas() { - return gInfo, nil, nil, 0, sdkerrors.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx") + if mode == execModeFinalize && ctx.BlockGasMeter().IsOutOfGas() { + return gInfo, nil, nil, errorsmod.Wrap(sdkerrors.ErrOutOfGas, "no block gas left to run tx") } defer func() { @@ -670,18 +679,18 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re // NOTE: consumeBlockGas must exist in a separate defer function from the // general deferred recovery function to recover from consumeBlockGas as it'll // be executed first (deferred statements are executed as stack). - if mode == runTxModeDeliver { + if mode == execModeFinalize { defer consumeBlockGas() } tx, err := app.txDecoder(txBytes) if err != nil { - return sdk.GasInfo{}, nil, nil, 0, err + return sdk.GasInfo{}, nil, nil, err } msgs := tx.GetMsgs() if err := validateBasicTxMsgs(msgs); err != nil { - return sdk.GasInfo{}, nil, nil, 0, err + return sdk.GasInfo{}, nil, nil, err } if app.anteHandler != nil { @@ -717,10 +726,9 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re gasWanted = ctx.GasMeter().Limit() if err != nil { - return gInfo, nil, nil, 0, err + return gInfo, nil, nil, err } - priority = ctx.Priority() msCache.Write() anteEvents = events.ToABCIEvents() } @@ -728,12 +736,12 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re if mode == runTxModeCheck { err = app.mempool.Insert(ctx, tx) if err != nil { - return gInfo, nil, anteEvents, priority, err + return gInfo, nil, anteEvents, err } - } else if mode == runTxModeDeliver { + } else if mode == execModeFinalize { err = app.mempool.Remove(tx) if err != nil && !errors.Is(err, mempool.ErrTxNotFound) { - return gInfo, nil, anteEvents, priority, + return gInfo, nil, anteEvents, fmt.Errorf("failed to remove tx from mempool: %w", err) } } @@ -759,26 +767,26 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re newCtx, err := app.postHandler(postCtx, tx, mode == runTxModeSimulate, err == nil) if err != nil { - return gInfo, nil, anteEvents, priority, err + return gInfo, nil, anteEvents, err } result.Events = append(result.Events, newCtx.EventManager().ABCIEvents()...) } - if mode == runTxModeDeliver { + if mode == execModeFinalize { // When block gas exceeds, it'll panic and won't commit the cached store. consumeBlockGas() msCache.Write() } - if len(anteEvents) > 0 && (mode == runTxModeDeliver || mode == runTxModeSimulate) { + if len(anteEvents) > 0 && (mode == execModeFinalize || mode == runTxModeSimulate) { // append the events in the order of occurrence result.Events = append(anteEvents, result.Events...) } } - return gInfo, result, anteEvents, priority, err + return gInfo, result, anteEvents, err } // runMsgs iterates through a list of messages and executes them with the provided @@ -793,7 +801,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s // NOTE: GasWanted is determined by the AnteHandler and GasUsed by the GasMeter. for i, msg := range msgs { - if mode != runTxModeDeliver && mode != runTxModeSimulate { + if mode != execModeFinalize && mode != runTxModeSimulate { break } @@ -884,7 +892,7 @@ func (app *BaseApp) PrepareProposalVerifyTx(tx sdk.Tx) ([]byte, error) { return nil, err } - _, _, _, _, err = app.runTx(runTxPrepareProposal, bz) //nolint:dogsled + _, _, _, err = app.runTx(runTxPrepareProposal, bz) //nolint:dogsled if err != nil { return nil, err } @@ -903,7 +911,7 @@ func (app *BaseApp) ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) { return nil, err } - _, _, _, _, err = app.runTx(runTxProcessProposal, txBz) //nolint:dogsled + _, _, _, err = app.runTx(runTxProcessProposal, txBz) //nolint:dogsled if err != nil { return nil, err } @@ -915,3 +923,114 @@ func (app *BaseApp) ProcessProposalVerifyTx(txBz []byte) (sdk.Tx, error) { func (app *BaseApp) Close() error { return nil } + +func (app *BaseApp) preBlock(req *abci.RequestFinalizeBlock) ([]abci.Event, error) { + var events []abci.Event + if app.preBlocker != nil { + ctx := app.finalizeBlockState.Context() + rsp, err := app.preBlocker(ctx, req) + if err != nil { + return nil, err + } + // rsp.ConsensusParamsChanged is true from preBlocker means ConsensusParams in store get changed + // write the consensus parameters in store to context + if rsp.ConsensusParamsChanged { + ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx)) + // GasMeter must be set after we get a context with updated consensus params. + gasMeter := app.getBlockGasMeter(ctx) + ctx = ctx.WithBlockGasMeter(gasMeter) + app.finalizeBlockState.SetContext(ctx) + } + events = ctx.EventManager().ABCIEvents() + } + return events, nil +} + +func (app *BaseApp) deliverTx(tx []byte) *abci.ExecTxResult { + gInfo := sdk.GasInfo{} + resultStr := "successful" + + var resp *abci.ExecTxResult + + defer func() { + telemetry.IncrCounter(1, "tx", "count") + telemetry.IncrCounter(1, "tx", resultStr) + telemetry.SetGauge(float32(gInfo.GasUsed), "tx", "gas", "used") + telemetry.SetGauge(float32(gInfo.GasWanted), "tx", "gas", "wanted") + }() + + gInfo, result, anteEvents, err := app.runTx(execModeFinalize, tx) + if err != nil { + resultStr = "failed" + resp = sdkerrors.ResponseExecTxResultWithEvents( + err, + gInfo.GasWanted, + gInfo.GasUsed, + sdk.MarkEventsToIndex(anteEvents, app.indexEvents), + app.trace, + ) + return resp + } + + resp = &abci.ExecTxResult{ + GasWanted: int64(gInfo.GasWanted), + GasUsed: int64(gInfo.GasUsed), + Log: result.Log, + Data: result.Data, + Events: sdk.MarkEventsToIndex(result.Events, app.indexEvents), + } + + return resp +} + +func (app *BaseApp) beginBlock(_ *abci.RequestFinalizeBlock) (sdk.BeginBlock, error) { + var ( + resp sdk.BeginBlock + err error + ) + + if app.beginBlocker != nil { + resp, err = app.beginBlocker(app.finalizeBlockState.Context()) + if err != nil { + return resp, err + } + + // append BeginBlock attributes to all events in the EndBlock response + for i, event := range resp.Events { + resp.Events[i].Attributes = append( + event.Attributes, + abci.EventAttribute{Key: "mode", Value: "BeginBlock"}, + ) + } + + resp.Events = sdk.MarkEventsToIndex(resp.Events, app.indexEvents) + } + + return resp, nil +} + +// endBlock is an application-defined function that is called after transactions +// have been processed in FinalizeBlock. +func (app *BaseApp) endBlock(_ sdk.Context) (sdk.EndBlock, error) { + var endblock sdk.EndBlock + + if app.endBlocker != nil { + eb, err := app.endBlocker(app.finalizeBlockState.Context()) + if err != nil { + return endblock, err + } + + // append EndBlock attributes to all events in the EndBlock response + for i, event := range eb.Events { + eb.Events[i].Attributes = append( + event.Attributes, + abci.EventAttribute{Key: "mode", Value: "EndBlock"}, + ) + } + + eb.Events = sdk.MarkEventsToIndex(eb.Events, app.indexEvents) + endblock = eb + } + + return endblock, nil +} diff --git a/baseapp/info.go b/baseapp/info.go new file mode 100644 index 0000000000..b42d6b3262 --- /dev/null +++ b/baseapp/info.go @@ -0,0 +1,215 @@ +package baseapp + +import ( + "time" + + abci "github.com/cometbft/cometbft/abci/types" + + "cosmossdk.io/core/comet" +) + +// NewBlockInfo returns a new BlockInfo instance +// This function should be only used in tests +func NewBlockInfo( + misbehavior []abci.Misbehavior, + validatorsHash []byte, + proposerAddress []byte, + lastCommit abci.CommitInfo, +) comet.BlockInfo { + return &cometInfo{ + Misbehavior: misbehavior, + ValidatorsHash: validatorsHash, + ProposerAddress: proposerAddress, + LastCommit: lastCommit, + } +} + +// CometInfo defines the properties provided by comet to the application +type cometInfo struct { + Misbehavior []abci.Misbehavior + ValidatorsHash []byte + ProposerAddress []byte + LastCommit abci.CommitInfo +} + +func (r cometInfo) GetEvidence() comet.EvidenceList { + return evidenceWrapper{evidence: r.Misbehavior} +} + +func (r cometInfo) GetValidatorsHash() []byte { + return r.ValidatorsHash +} + +func (r cometInfo) GetProposerAddress() []byte { + return r.ProposerAddress +} + +func (r cometInfo) GetLastCommit() comet.CommitInfo { + return commitInfoWrapper{r.LastCommit} +} + +type evidenceWrapper struct { + evidence []abci.Misbehavior +} + +func (e evidenceWrapper) Len() int { + return len(e.evidence) +} + +func (e evidenceWrapper) Get(i int) comet.Evidence { + return misbehaviorWrapper{e.evidence[i]} +} + +// commitInfoWrapper is a wrapper around abci.CommitInfo that implements CommitInfo interface +type commitInfoWrapper struct { + abci.CommitInfo +} + +var _ comet.CommitInfo = (*commitInfoWrapper)(nil) + +func (c commitInfoWrapper) Round() int32 { + return c.CommitInfo.Round +} + +func (c commitInfoWrapper) Votes() comet.VoteInfos { + return abciVoteInfoWrapper{c.CommitInfo.Votes} +} + +// abciVoteInfoWrapper is a wrapper around abci.VoteInfo that implements VoteInfos interface +type abciVoteInfoWrapper struct { + votes []abci.VoteInfo +} + +var _ comet.VoteInfos = (*abciVoteInfoWrapper)(nil) + +func (e abciVoteInfoWrapper) Len() int { + return len(e.votes) +} + +func (e abciVoteInfoWrapper) Get(i int) comet.VoteInfo { + return voteInfoWrapper{e.votes[i]} +} + +// voteInfoWrapper is a wrapper around abci.VoteInfo that implements VoteInfo interface +type voteInfoWrapper struct { + abci.VoteInfo +} + +var _ comet.VoteInfo = (*voteInfoWrapper)(nil) + +func (v voteInfoWrapper) GetBlockIDFlag() comet.BlockIDFlag { + var flag comet.BlockIDFlag + return flag + //return comet.BlockIDFlag(v.VoteInfo.BlockIdFlag) +} + +func (v voteInfoWrapper) Validator() comet.Validator { + return validatorWrapper{v.VoteInfo.Validator} +} + +// validatorWrapper is a wrapper around abci.Validator that implements Validator interface +type validatorWrapper struct { + abci.Validator +} + +var _ comet.Validator = (*validatorWrapper)(nil) + +func (v validatorWrapper) Address() []byte { + return v.Validator.Address +} + +func (v validatorWrapper) Power() int64 { + return v.Validator.Power +} + +type misbehaviorWrapper struct { + abci.Misbehavior +} + +func (m misbehaviorWrapper) Type() comet.MisbehaviorType { + return comet.MisbehaviorType(m.Misbehavior.Type) +} + +func (m misbehaviorWrapper) Height() int64 { + return m.Misbehavior.Height +} + +func (m misbehaviorWrapper) Validator() comet.Validator { + return validatorWrapper{m.Misbehavior.Validator} +} + +func (m misbehaviorWrapper) Time() time.Time { + return m.Misbehavior.Time +} + +func (m misbehaviorWrapper) TotalVotingPower() int64 { + return m.Misbehavior.TotalVotingPower +} + +type prepareProposalInfo struct { + *abci.RequestPrepareProposal +} + +var _ comet.BlockInfo = (*prepareProposalInfo)(nil) + +func (r prepareProposalInfo) GetEvidence() comet.EvidenceList { + return evidenceWrapper{r.Misbehavior} +} + +func (r prepareProposalInfo) GetValidatorsHash() []byte { + return r.NextValidatorsHash +} + +func (r prepareProposalInfo) GetProposerAddress() []byte { + return r.RequestPrepareProposal.ProposerAddress +} + +func (r prepareProposalInfo) GetLastCommit() comet.CommitInfo { + return extendedCommitInfoWrapper{r.RequestPrepareProposal.LocalLastCommit} +} + +var _ comet.BlockInfo = (*prepareProposalInfo)(nil) + +type extendedCommitInfoWrapper struct { + abci.ExtendedCommitInfo +} + +var _ comet.CommitInfo = (*extendedCommitInfoWrapper)(nil) + +func (e extendedCommitInfoWrapper) Round() int32 { + return e.ExtendedCommitInfo.Round +} + +func (e extendedCommitInfoWrapper) Votes() comet.VoteInfos { + return extendedVoteInfoWrapperList{e.ExtendedCommitInfo.Votes} +} + +type extendedVoteInfoWrapperList struct { + votes []abci.ExtendedVoteInfo +} + +var _ comet.VoteInfos = (*extendedVoteInfoWrapperList)(nil) + +func (e extendedVoteInfoWrapperList) Len() int { + return len(e.votes) +} + +func (e extendedVoteInfoWrapperList) Get(i int) comet.VoteInfo { + return extendedVoteInfoWrapper{e.votes[i]} +} + +type extendedVoteInfoWrapper struct { + abci.ExtendedVoteInfo +} + +var _ comet.VoteInfo = (*extendedVoteInfoWrapper)(nil) + +func (e extendedVoteInfoWrapper) GetBlockIDFlag() comet.BlockIDFlag { + var flag comet.BlockIDFlag + return flag + // return comet.BlockIDFlag(e.ExtendedVoteInfo.BlockIdFlag) +} + +func (e extendedVoteInfoWrapper) Validator() comet.Validator { + return validatorWrapper{e.ExtendedVoteInfo.Validator} +} diff --git a/baseapp/oe/optimistic_execution.go b/baseapp/oe/optimistic_execution.go new file mode 100644 index 0000000000..c0c8e2575a --- /dev/null +++ b/baseapp/oe/optimistic_execution.go @@ -0,0 +1,160 @@ +package oe + +import ( + "bytes" + "context" + "encoding/hex" + "math/rand" + "sync" + "time" + + abci "github.com/cometbft/cometbft/abci/types" + + "cosmossdk.io/log" +) + +// FinalizeBlockFunc is the function that is called by the OE to finalize the +// block. It is the same as the one in the ABCI app. +type FinalizeBlockFunc func(context.Context, *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) + +// OptimisticExecution is a struct that contains the OE context. It is used to +// run the FinalizeBlock function in a goroutine, and to abort it if needed. +type OptimisticExecution struct { + finalizeBlockFunc FinalizeBlockFunc // ABCI FinalizeBlock function with a context + logger log.Logger + + mtx sync.Mutex + stopCh chan struct{} + request *abci.RequestFinalizeBlock + response *abci.ResponseFinalizeBlock + err error + cancelFunc func() // cancel function for the context + initialized bool // A boolean value indicating whether the struct has been initialized + + // debugging/testing options + abortRate int // number from 0 to 100 that determines the percentage of OE that should be aborted +} + +// NewOptimisticExecution initializes the Optimistic Execution context but does not start it. +func NewOptimisticExecution(logger log.Logger, fn FinalizeBlockFunc, opts ...func(*OptimisticExecution)) *OptimisticExecution { + logger = logger.With(log.ModuleKey, "oe") + oe := &OptimisticExecution{logger: logger, finalizeBlockFunc: fn} + for _, opt := range opts { + opt(oe) + } + return oe +} + +// WithAbortRate sets the abort rate for the OE. The abort rate is a number from +// 0 to 100 that determines the percentage of OE that should be aborted. +// This is for testing purposes only and must not be used in production. +func WithAbortRate(rate int) func(*OptimisticExecution) { + return func(oe *OptimisticExecution) { + oe.abortRate = rate + } +} + +// Reset resets the OE context. Must be called whenever we want to invalidate +// the current OE. +func (oe *OptimisticExecution) Reset() { + oe.mtx.Lock() + defer oe.mtx.Unlock() + oe.request = nil + oe.response = nil + oe.err = nil + oe.initialized = false +} + +func (oe *OptimisticExecution) Enabled() bool { + return oe != nil +} + +// Initialized returns true if the OE was initialized, meaning that it contains +// a request and it was run or it is running. +func (oe *OptimisticExecution) Initialized() bool { + if oe == nil { + return false + } + oe.mtx.Lock() + defer oe.mtx.Unlock() + + return oe.initialized +} + +// Execute initializes the OE and starts it in a goroutine. +func (oe *OptimisticExecution) Execute(req *abci.RequestProcessProposal) { + oe.mtx.Lock() + defer oe.mtx.Unlock() + + oe.stopCh = make(chan struct{}) + oe.request = &abci.RequestFinalizeBlock{ + Txs: req.Txs, + DecidedLastCommit: req.ProposedLastCommit, + Misbehavior: req.Misbehavior, + Hash: req.Hash, + Height: req.Height, + Time: req.Time, + NextValidatorsHash: req.NextValidatorsHash, + ProposerAddress: req.ProposerAddress, + } + + oe.logger.Debug("OE started", "height", req.Height, "hash", hex.EncodeToString(req.Hash), "time", req.Time.String()) + ctx, cancel := context.WithCancel(context.Background()) + oe.cancelFunc = cancel + oe.initialized = true + + go func() { + start := time.Now() + resp, err := oe.finalizeBlockFunc(ctx, oe.request) + + oe.mtx.Lock() + + executionTime := time.Since(start) + oe.logger.Debug("OE finished", "duration", executionTime.String(), "height", oe.request.Height, "hash", hex.EncodeToString(oe.request.Hash)) + oe.response, oe.err = resp, err + + close(oe.stopCh) + oe.mtx.Unlock() + }() +} + +// AbortIfNeeded aborts the OE if the request hash is not the same as the one in +// the running OE. Returns true if the OE was aborted. +func (oe *OptimisticExecution) AbortIfNeeded(reqHash []byte) bool { + if oe == nil { + return false + } + + oe.mtx.Lock() + defer oe.mtx.Unlock() + + if !bytes.Equal(oe.request.Hash, reqHash) { + oe.logger.Error("OE aborted due to hash mismatch", "oe_hash", hex.EncodeToString(oe.request.Hash), "req_hash", hex.EncodeToString(reqHash), "oe_height", oe.request.Height, "req_height", oe.request.Height) + oe.cancelFunc() + return true + } else if oe.abortRate > 0 && rand.Intn(100) < oe.abortRate { + // this is for test purposes only, we can emulate a certain percentage of + // OE needed to be aborted. + oe.cancelFunc() + oe.logger.Error("OE aborted due to test abort rate") + return true + } + + return false +} + +// Abort aborts the OE unconditionally and waits for it to finish. +func (oe *OptimisticExecution) Abort() { + if oe == nil || oe.cancelFunc == nil { + return + } + + oe.cancelFunc() + <-oe.stopCh +} + +// WaitResult waits for the OE to finish and returns the result. +func (oe *OptimisticExecution) WaitResult() (*abci.ResponseFinalizeBlock, error) { + <-oe.stopCh + return oe.response, oe.err +} diff --git a/baseapp/oe/optimistic_execution_test.go b/baseapp/oe/optimistic_execution_test.go new file mode 100644 index 0000000000..0b92244783 --- /dev/null +++ b/baseapp/oe/optimistic_execution_test.go @@ -0,0 +1,34 @@ +package oe + +import ( + "context" + "errors" + "testing" + + abci "github.com/cometbft/cometbft/abci/types" + "github.com/stretchr/testify/assert" + + "cosmossdk.io/log" +) + +func testFinalizeBlock(_ context.Context, _ *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) { + return nil, errors.New("test error") +} + +func TestOptimisticExecution(t *testing.T) { + oe := NewOptimisticExecution(log.NewNopLogger(), testFinalizeBlock) + assert.True(t, oe.Enabled()) + oe.Execute(&abci.RequestProcessProposal{ + Hash: []byte("test"), + }) + assert.True(t, oe.Initialized()) + + resp, err := oe.WaitResult() + assert.Nil(t, resp) + assert.EqualError(t, err, "test error") + + assert.False(t, oe.AbortIfNeeded([]byte("test"))) + assert.True(t, oe.AbortIfNeeded([]byte("wrong_hash"))) + + oe.Reset() +} diff --git a/baseapp/state.go b/baseapp/state.go index addc89cb34..3deb8c1556 100644 --- a/baseapp/state.go +++ b/baseapp/state.go @@ -1,11 +1,14 @@ package baseapp import ( + "sync" + sdk "github.com/cosmos/cosmos-sdk/types" ) type state struct { ms sdk.CacheMultiStore + mtx sync.RWMutex ctx sdk.Context } @@ -19,3 +22,10 @@ func (st *state) CacheMultiStore() sdk.CacheMultiStore { func (st *state) Context() sdk.Context { return st.ctx } + +// SetContext updates the state's context to the context provided. +func (st *state) SetContext(ctx sdk.Context) { + st.mtx.Lock() + defer st.mtx.Unlock() + st.ctx = ctx +} diff --git a/baseapp/streaming.go b/baseapp/streaming.go index 64f10e1db5..bf26d99a7a 100644 --- a/baseapp/streaming.go +++ b/baseapp/streaming.go @@ -22,6 +22,8 @@ type ABCIListener interface { ListenDeliverTx(ctx context.Context, req abci.RequestDeliverTx, res abci.ResponseDeliverTx) error // ListenCommit updates the steaming service with the latest Commit event ListenCommit(ctx context.Context, res abci.ResponseCommit) error + // ListenFinalizeBlock updates the streaming service with the latest FinalizeBlock messages + ListenFinalizeBlock(ctx context.Context, req abci.RequestFinalizeBlock, res abci.ResponseFinalizeBlock) error } // StreamingService interface for registering WriteListeners with the BaseApp and updating the service with the ABCI messages using the hooks diff --git a/baseapp/test_helpers.go b/baseapp/test_helpers.go index a8ecee084d..849b313af2 100644 --- a/baseapp/test_helpers.go +++ b/baseapp/test_helpers.go @@ -16,13 +16,13 @@ func (app *BaseApp) SimCheck(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, * if err != nil { return sdk.GasInfo{}, nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, _, err := app.runTx(runTxModeCheck, bz) + gasInfo, result, _, err := app.runTx(runTxModeCheck, bz) return gasInfo, result, err } // Simulate executes a tx in simulate mode to get result and gas info. func (app *BaseApp) Simulate(txBytes []byte) (sdk.GasInfo, *sdk.Result, error) { - gasInfo, result, _, _, err := app.runTx(runTxModeSimulate, txBytes) + gasInfo, result, _, err := app.runTx(runTxModeSimulate, txBytes) return gasInfo, result, err } @@ -32,7 +32,7 @@ func (app *BaseApp) SimDeliver(txEncoder sdk.TxEncoder, tx sdk.Tx) (sdk.GasInfo, if err != nil { return sdk.GasInfo{}, nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "%s", err) } - gasInfo, result, _, _, err := app.runTx(runTxModeDeliver, bz) + gasInfo, result, _, err := app.runTx(execModeFinalize, bz) return gasInfo, result, err } @@ -43,7 +43,7 @@ func (app *BaseApp) NewContext(isCheckTx bool, header tmproto.Header) sdk.Contex WithMinGasPrices(app.minGasPrices) } - return sdk.NewContext(app.deliverState.ms, header, false, app.logger) + return sdk.NewContext(app.finalizeBlockState.ms, header, false, app.logger) } func (app *BaseApp) NewUncachedContext(isCheckTx bool, header tmproto.Header) sdk.Context { @@ -51,5 +51,5 @@ func (app *BaseApp) NewUncachedContext(isCheckTx bool, header tmproto.Header) sd } func (app *BaseApp) GetContextForDeliverTx(txBytes []byte) sdk.Context { - return app.getContextForTx(runTxModeDeliver, txBytes) + return app.getContextForTx(execModeFinalize, txBytes) } diff --git a/client/rpc/tx.go b/client/rpc/tx.go index f77d6cf0a1..02b9852b86 100644 --- a/client/rpc/tx.go +++ b/client/rpc/tx.go @@ -55,20 +55,20 @@ func newTxResponseDeliverTx(res *coretypes.ResultBroadcastTxCommit) *sdk.TxRespo txHash = res.Hash.String() } - parsedLogs, _ := sdk.ParseABCILogs(res.DeliverTx.Log) + parsedLogs, _ := sdk.ParseABCILogs(res.TxResult.Log) return &sdk.TxResponse{ Height: res.Height, TxHash: txHash, - Codespace: res.DeliverTx.Codespace, - Code: res.DeliverTx.Code, - Data: strings.ToUpper(hex.EncodeToString(res.DeliverTx.Data)), - RawLog: res.DeliverTx.Log, + Codespace: res.TxResult.Codespace, + Code: res.TxResult.Code, + Data: strings.ToUpper(hex.EncodeToString(res.TxResult.Data)), + RawLog: res.TxResult.Log, Logs: parsedLogs, - Info: res.DeliverTx.Info, - GasWanted: res.DeliverTx.GasWanted, - GasUsed: res.DeliverTx.GasUsed, - Events: res.DeliverTx.Events, + Info: res.TxResult.Info, + GasWanted: res.TxResult.GasWanted, + GasUsed: res.TxResult.GasUsed, + Events: res.TxResult.Events, } } @@ -121,9 +121,9 @@ func QueryEventForTxCmd() *cobra.Command { case evt := <-eventCh: if txe, ok := evt.Data.(tmtypes.EventDataTx); ok { res := &coretypes.ResultBroadcastTxCommit{ - DeliverTx: txe.Result, - Hash: tmtypes.Tx(txe.Tx).Hash(), - Height: txe.Height, + TxResult: txe.Result, + Hash: tmtypes.Tx(txe.Tx).Hash(), + Height: txe.Height, } return clientCtx.PrintProto(newResponseFormatBroadcastTxCommit(res)) } diff --git a/core/comet/doc.go b/core/comet/doc.go new file mode 100644 index 0000000000..c6553d097b --- /dev/null +++ b/core/comet/doc.go @@ -0,0 +1,7 @@ +/* +Package comet defines the Comet Service interface and BlockInfo types which applications +should use in order to get access to the current block's evidence, validators hash, proposer address. + +This information is specific to Comet +*/ +package comet diff --git a/core/comet/service.go b/core/comet/service.go new file mode 100644 index 0000000000..d2985b2174 --- /dev/null +++ b/core/comet/service.go @@ -0,0 +1,81 @@ +package comet + +import ( + "context" + "time" +) + +// BlockInfoService is an interface that can be used to get information specific to Comet +type BlockInfoService interface { + GetCometBlockInfo(context.Context) BlockInfo +} + +// BlockInfo is the information comet provides apps in ABCI +type BlockInfo interface { + GetEvidence() EvidenceList // Evidence misbehavior of the block + // ValidatorsHash returns the hash of the validators + // For Comet, it is the hash of the next validator set + GetValidatorsHash() []byte + GetProposerAddress() []byte // ProposerAddress returns the address of the block proposer + GetLastCommit() CommitInfo // DecidedLastCommit returns the last commit info +} + +// MisbehaviorType is the type of misbehavior for a validator +type MisbehaviorType int32 + +const ( + Unknown MisbehaviorType = 0 + DuplicateVote MisbehaviorType = 1 + LightClientAttack MisbehaviorType = 2 +) + +// Validator is the validator information of ABCI +type Validator interface { + Address() []byte + Power() int64 +} + +type EvidenceList interface { + Len() int + Get(int) Evidence +} + +// Evidence is the misbehavior information of ABCI +type Evidence interface { + Type() MisbehaviorType + Validator() Validator + Height() int64 + Time() time.Time + TotalVotingPower() int64 +} + +// CommitInfo is the commit information of ABCI +type CommitInfo interface { + Round() int32 + Votes() VoteInfos +} + +// VoteInfos is an interface to get specific votes in a efficient way +type VoteInfos interface { + Len() int + Get(int) VoteInfo +} + +// BlockIdFlag indicates which BlockID the signature is for +type BlockIDFlag int32 + +const ( + BlockIDFlagUnknown BlockIDFlag = 0 + // BlockIDFlagAbsent - no vote was received from a validator. + BlockIDFlagAbsent BlockIDFlag = 1 + // BlockIDFlagCommit - voted for the Commit.BlockID. + BlockIDFlagCommit BlockIDFlag = 2 + // BlockIDFlagNil - voted for nil. + BlockIDFlagNil BlockIDFlag = 3 +) + +// VoteInfo is the vote information of ABCI +type VoteInfo interface { + Validator() Validator + GetBlockIDFlag() BlockIDFlag +} diff --git a/core/header/doc.go b/core/header/doc.go new file mode 100644 index 0000000000..4ba3bcdbe8 --- /dev/null +++ b/core/header/doc.go @@ -0,0 +1,7 @@ +/* +Package header defines a generalized Header type that all consensus & networking layers must provide. + +If modules need access to the current block header information, like height, hash, time, or chain ID +they should use the Header Service interface. +*/ +package header diff --git a/core/header/service.go b/core/header/service.go new file mode 100644 index 0000000000..cce7e6de30 --- /dev/null +++ b/core/header/service.go @@ -0,0 +1,20 @@ +package header + +import ( + "context" + "time" +) + +// Service defines the interface in which you can get header information +type Service interface { + GetHeaderInfo(context.Context) Info +} + +// Info defines a struct that contains information about the header +type Info struct { + Height int64 // Height returns the height of the block + Hash []byte // Hash returns the hash of the block header + Time time.Time // Time returns the time of the block + ChainID string // ChainId returns the chain ID of the block + AppHash []byte // AppHash used in the current block header +} diff --git a/go.mod b/go.mod index 6c4a71a994..55036d532f 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( cosmossdk.io/errors v1.0.1 cosmossdk.io/log v1.3.1 cosmossdk.io/math v1.3.0 - cosmossdk.io/tools/rosetta v0.2.1 github.com/99designs/keyring v1.2.1 github.com/armon/go-metrics v0.4.1 github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 @@ -210,6 +209,7 @@ replace ( golang.org/x/exp => golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb // stick with compatible version of rapid in v0.47.x line pgregory.net/rapid => pgregory.net/rapid v0.5.5 + cosmossdk.io/tools/rosetta => ./tools/rosetta ) retract ( diff --git a/runtime/app.go b/runtime/app.go index b5cfcc54ce..54d7a821cc 100644 --- a/runtime/app.go +++ b/runtime/app.go @@ -121,10 +121,6 @@ func (a *App) Load(loadLatest bool) error { a.SetEndBlocker(a.EndBlocker) } - if len(a.config.OrderMigrations) != 0 { - a.ModuleManager.SetOrderMigrations(a.config.OrderMigrations...) - } - if loadLatest { if err := a.LoadLatestVersion(); err != nil { return err @@ -135,13 +131,13 @@ func (a *App) Load(loadLatest bool) error { } // BeginBlocker application updates every begin block -func (a *App) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { - return a.ModuleManager.BeginBlock(ctx, req) +func (a *App) BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) { + return a.ModuleManager.BeginBlock(ctx) } // EndBlocker application updates every end block -func (a *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - return a.ModuleManager.EndBlock(ctx, req) +func (a *App) EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) { + return a.ModuleManager.EndBlock(ctx) } // InitChainer initializes the chain. diff --git a/runtime/types.go b/runtime/types.go index 551a791f13..a64663f7df 100644 --- a/runtime/types.go +++ b/runtime/types.go @@ -20,10 +20,10 @@ type AppI interface { LegacyAmino() *codec.LegacyAmino // Application updates every begin block. - BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock + BeginBlocker(ctx sdk.Context) (sdk.BeginBlock, error) // Application updates every end block. - EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock + EndBlocker(ctx sdk.Context) (sdk.EndBlock, error) // Application update at chain (i.e app) initialization. InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain diff --git a/simapp/app.go b/simapp/app.go index 31e5cd7f7f..bee14ecf18 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -191,7 +191,7 @@ type SimApp struct { GroupKeeper groupkeeper.Keeper NFTKeeper nftkeeper.Keeper ConsensusParamsKeeper consensusparamkeeper.Keeper - VestingKeeper vestingkeeper.VestingKeeper + VestingKeeper vestingkeeper.VestingKeeper // the module manager ModuleManager *module.Manager @@ -316,7 +316,7 @@ func NewSimApp( keys[vestingtypes.StoreKey]) app.StakingKeeper = stakingkeeper.NewKeeper( - appCodec, keys[stakingtypes.StoreKey], app.AccountKeeper, app.BankKeeper, app.VestingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), + appCodec, keys[stakingtypes.StoreKey], app.AccountKeeper, app.BankKeeper, app.VestingKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) app.MintKeeper = mintkeeper.NewKeeper(appCodec, keys[minttypes.StoreKey], app.StakingKeeper, app.AccountKeeper, app.BankKeeper, authtypes.FeeCollectorName, authtypes.NewModuleAddress(govtypes.ModuleName).String()) @@ -568,8 +568,8 @@ func (app *SimApp) setPostHandler() { func (app *SimApp) Name() string { return app.BaseApp.Name() } // BeginBlocker application updates every begin block -func (app *SimApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { - return app.ModuleManager.BeginBlock(ctx, req) +func (app *SimApp) BeginBlocker(ctx sdk.Context) error { + return app.ModuleManager.BeginBlock(ctx) } // EndBlocker application updates every end block diff --git a/simapp/test_helpers.go b/simapp/test_helpers.go index de9cc9cd91..f3a7568ab8 100644 --- a/simapp/test_helpers.go +++ b/simapp/test_helpers.go @@ -10,7 +10,6 @@ import ( abci "github.com/cometbft/cometbft/abci/types" tmjson "github.com/cometbft/cometbft/libs/json" "github.com/cometbft/cometbft/libs/log" - tmproto "github.com/cometbft/cometbft/proto/tendermint/types" tmtypes "github.com/cometbft/cometbft/types" "github.com/stretchr/testify/require" @@ -145,12 +144,6 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs // commit genesis changes app.Commit() - app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{ - Height: app.LastBlockHeight() + 1, - AppHash: app.LastCommitID().Hash, - ValidatorsHash: valSet.Hash(), - NextValidatorsHash: valSet.Hash(), - }}) return app } diff --git a/store/iavl/store.go b/store/iavl/store.go index b6c86fe03f..1d63beac4d 100644 --- a/store/iavl/store.go +++ b/store/iavl/store.go @@ -407,3 +407,8 @@ func getProofFromTree(tree *iavl.MutableTree, key []byte, exists bool) *tmcrypto op := types.NewIavlCommitmentOp(key, commitmentProof) return &tmcrypto.ProofOps{Ops: []tmcrypto.ProofOp{op.ProofOp()}} } + +// WorkingHash returns the hash of the current working tree. +func (st *Store) WorkingHash() []byte { + return st.tree.WorkingHash() +} diff --git a/store/mem/store.go b/store/mem/store.go index bba5fde8d0..702a346186 100644 --- a/store/mem/store.go +++ b/store/mem/store.go @@ -58,3 +58,5 @@ func (s *Store) GetPruning() pruningtypes.PruningOptions { } func (s Store) LastCommitID() (id types.CommitID) { return } + +func (s Store) WorkingHash() (hash []byte) { return } diff --git a/store/rootmulti/dbadapter.go b/store/rootmulti/dbadapter.go index 02b11ebd3f..a54b5a53de 100644 --- a/store/rootmulti/dbadapter.go +++ b/store/rootmulti/dbadapter.go @@ -39,3 +39,7 @@ func (cdsa commitDBStoreAdapter) SetPruning(_ pruningtypes.PruningOptions) {} func (cdsa commitDBStoreAdapter) GetPruning() pruningtypes.PruningOptions { return pruningtypes.NewPruningOptions(pruningtypes.PruningUndefined) } + +func (cdsa commitDBStoreAdapter) WorkingHash() []byte { + return commithash +} diff --git a/store/rootmulti/store.go b/store/rootmulti/store.go index 308feb1703..7232aa211c 100644 --- a/store/rootmulti/store.go +++ b/store/rootmulti/store.go @@ -1165,3 +1165,47 @@ func flushLatestVersion(batch dbm.Batch, version int64) { panic(err) } } + +// keysFromStoreKeyMap returns a slice of keys for the provided map lexically sorted by StoreKey.Name() +func keysFromStoreKeyMap[V any](m map[types.StoreKey]V) []types.StoreKey { + keys := make([]types.StoreKey, 0, len(m)) + for key := range m { + keys = append(keys, key) + } + sort.Slice(keys, func(i, j int) bool { + ki, kj := keys[i], keys[j] + return ki.Name() < kj.Name() + }) + return keys +} + +// WorkingHash returns the current hash of the store. +// it will be used to get the current app hash before commit. +func (rs *Store) WorkingHash() []byte { + storeInfos := make([]types.StoreInfo, 0, len(rs.stores)) + storeKeys := keysFromStoreKeyMap(rs.stores) + + for _, key := range storeKeys { + store := rs.stores[key] + + if store.GetStoreType() != types.StoreTypeIAVL { + continue + } + + if !rs.removalMap[key] { + si := types.StoreInfo{ + Name: key.Name(), + CommitId: types.CommitID{ + Hash: store.WorkingHash(), + }, + } + storeInfos = append(storeInfos, si) + } + } + + sort.SliceStable(storeInfos, func(i, j int) bool { + return storeInfos[i].Name < storeInfos[j].Name + }) + + return types.CommitInfo{StoreInfos: storeInfos}.Hash() +} diff --git a/store/streaming/file/service.go b/store/streaming/file/service.go index 46bfada812..bc9f860f8e 100644 --- a/store/streaming/file/service.go +++ b/store/streaming/file/service.go @@ -130,6 +130,10 @@ func (fss *StreamingService) ListenEndBlock(ctx context.Context, req abci.Reques return nil } +func (a *StreamingService) ListenFinalizeBlock(ctx context.Context, req abci.RequestFinalizeBlock, res abci.ResponseFinalizeBlock) error { + return nil +} + // ListenCommit satisfies the ABCIListener interface. It is executed during the // ABCI Commit request and is responsible for writing all staged data to files. // It will only return a non-nil error when stopNodeOnErr is set. diff --git a/store/transient/store.go b/store/transient/store.go index 531cd8e25c..bfaa8d2210 100644 --- a/store/transient/store.go +++ b/store/transient/store.go @@ -47,3 +47,7 @@ func (ts *Store) LastCommitID() (id types.CommitID) { func (ts *Store) GetStoreType() types.StoreType { return types.StoreTypeTransient } + +func (ts *Store) WorkingHash() []byte { + return []byte{} +} diff --git a/store/types/store.go b/store/types/store.go index 9921731956..130f5fa3dd 100644 --- a/store/types/store.go +++ b/store/types/store.go @@ -22,6 +22,9 @@ type Committer interface { Commit() CommitID LastCommitID() CommitID + // WorkingHash returns the hash of the KVStore's state before commit. + WorkingHash() []byte + SetPruning(pruningtypes.PruningOptions) GetPruning() pruningtypes.PruningOptions } diff --git a/testutil/sims/app_helpers.go b/testutil/sims/app_helpers.go index c4a834fecf..964958290a 100644 --- a/testutil/sims/app_helpers.go +++ b/testutil/sims/app_helpers.go @@ -108,7 +108,6 @@ func SetupAtGenesis(appConfig depinject.Config, extraOutputs ...interface{}) (*r // NextBlock starts a new block. func NextBlock(app *runtime.App, ctx sdk.Context, jumpTime time.Duration) sdk.Context { - app.EndBlock(abci.RequestEndBlock{Height: ctx.BlockHeight()}) app.Commit() @@ -116,8 +115,6 @@ func NextBlock(app *runtime.App, ctx sdk.Context, jumpTime time.Duration) sdk.Co nextHeight := ctx.BlockHeight() + 1 newHeader := tmproto.Header{Height: nextHeight, Time: newBlockTime} - app.BeginBlock(abci.RequestBeginBlock{Header: newHeader}) - return app.NewUncachedContext(false, newHeader) } @@ -186,12 +183,6 @@ func SetupWithConfiguration(appConfig depinject.Config, startupConfig StartupCon // commit genesis changes if !startupConfig.AtGenesis { app.Commit() - app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{ - Height: app.LastBlockHeight() + 1, - AppHash: app.LastCommitID().Hash, - ValidatorsHash: valSet.Hash(), - NextValidatorsHash: valSet.Hash(), - }}) } return app, nil diff --git a/testutil/sims/tx_helpers.go b/testutil/sims/tx_helpers.go index c223427607..842d777e25 100644 --- a/testutil/sims/tx_helpers.go +++ b/testutil/sims/tx_helpers.go @@ -5,7 +5,6 @@ import ( "testing" "time" - types2 "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/stretchr/testify/require" @@ -114,7 +113,6 @@ func SignCheckDeliver( } // Simulate a sending a transaction and committing a block - app.BeginBlock(types2.RequestBeginBlock{Header: header}) gInfo, res, err := app.SimDeliver(txCfg.TxEncoder(), tx) if expPass { @@ -125,7 +123,6 @@ func SignCheckDeliver( require.Nil(t, res) } - app.EndBlock(types2.RequestEndBlock{}) app.Commit() return gInfo, res, err diff --git a/tools/rosetta/client_online.go b/tools/rosetta/client_online.go index 200485c0ab..a3a1ec4217 100644 --- a/tools/rosetta/client_online.go +++ b/tools/rosetta/client_online.go @@ -498,19 +498,11 @@ func (c *Client) blockTxs(ctx context.Context, height *int64) (crgtypes.BlockTra panic("block results transactions do now match block transactions") } // process begin and end block txs - beginBlockTx := &rosettatypes.Transaction{ - TransactionIdentifier: &rosettatypes.TransactionIdentifier{Hash: c.converter.ToRosetta().BeginBlockTxHash(blockInfo.BlockID.Hash)}, + finalizeBlockTx := &rosettatypes.Transaction{ + TransactionIdentifier: &rosettatypes.TransactionIdentifier{Hash: c.converter.ToRosetta().FinalizeBlockTxHash(blockInfo.BlockID.Hash)}, Operations: AddOperationIndexes( nil, - c.converter.ToRosetta().BalanceOps(StatusTxSuccess, blockResults.BeginBlockEvents), - ), - } - - endBlockTx := &rosettatypes.Transaction{ - TransactionIdentifier: &rosettatypes.TransactionIdentifier{Hash: c.converter.ToRosetta().EndBlockTxHash(blockInfo.BlockID.Hash)}, - Operations: AddOperationIndexes( - nil, - c.converter.ToRosetta().BalanceOps(StatusTxSuccess, blockResults.EndBlockEvents), + c.converter.ToRosetta().BalanceOps(StatusTxSuccess, blockResults.FinalizeBlockEvents), ), } @@ -525,9 +517,8 @@ func (c *Client) blockTxs(ctx context.Context, height *int64) (crgtypes.BlockTra } finalTxs := make([]*rosettatypes.Transaction, 0, 2+len(deliverTx)) - finalTxs = append(finalTxs, beginBlockTx) finalTxs = append(finalTxs, deliverTx...) - finalTxs = append(finalTxs, endBlockTx) + finalTxs = append(finalTxs, finalizeBlockTx) return crgtypes.BlockTransactionsResponse{ BlockResponse: c.converter.ToRosetta().BlockResponse(blockInfo), diff --git a/tools/rosetta/converter.go b/tools/rosetta/converter.go index 66b070461d..17bd0febac 100644 --- a/tools/rosetta/converter.go +++ b/tools/rosetta/converter.go @@ -52,6 +52,8 @@ type ToRosettaConverter interface { // BlockResponse returns a block response given a result block BlockResponse(block *tmcoretypes.ResultBlock) crgtypes.BlockResponse // BeginBlockToTx converts the given begin block hash to rosetta transaction hash + FinalizeBlockTxHash(blockHash []byte) string + // BeginBlockToTx converts the given begin block hash to rosetta transaction hash BeginBlockTxHash(blockHash []byte) string // EndBlockTxHash converts the given endblock hash to rosetta transaction hash EndBlockTxHash(blockHash []byte) string @@ -68,7 +70,7 @@ type ToRosettaConverter interface { // SigningComponents returns rosetta's components required to build a signable transaction SigningComponents(tx authsigning.Tx, metadata *ConstructionMetadata, rosPubKeys []*rosettatypes.PublicKey) (txBytes []byte, payloadsToSign []*rosettatypes.SigningPayload, err error) // Tx converts a tendermint transaction and tx result if provided to a rosetta tx - Tx(rawTx tmtypes.Tx, txResult *abci.ResponseDeliverTx) (*rosettatypes.Transaction, error) + Tx(rawTx tmtypes.Tx, txResult *abci.ExecTxResult) (*rosettatypes.Transaction, error) // TxIdentifiers converts a tendermint tx to transaction identifiers TxIdentifiers(txs []tmtypes.Tx) []*rosettatypes.TransactionIdentifier // BalanceOps converts events to balance operations @@ -259,7 +261,7 @@ func (c converter) Ops(status string, msg sdk.Msg) ([]*rosettatypes.Operation, e } // Tx converts a tendermint raw transaction and its result (if provided) to a rosetta transaction -func (c converter) Tx(rawTx tmtypes.Tx, txResult *abci.ResponseDeliverTx) (*rosettatypes.Transaction, error) { +func (c converter) Tx(rawTx tmtypes.Tx, txResult *abci.ExecTxResult) (*rosettatypes.Transaction, error) { // decode tx tx, err := c.txDecode(rawTx) if err != nil { @@ -766,3 +768,11 @@ func (c converter) SignerData(anyAccount *codectypes.Any) (*SignerData, error) { Sequence: acc.GetSequence(), }, nil } + +// FinalizeBlockTxHash produces a mock beginblock hash that rosetta can query +// for finalizeBlock operations, it also serves the purpose of representing +// part of the state changes happening at finalizeblock level (balance ones) +func (c converter) FinalizeBlockTxHash(hash []byte) string { + final := append([]byte{FinalizeBlockHashStart}, hash...) + return fmt.Sprintf("%X", final) +} diff --git a/tools/rosetta/types.go b/tools/rosetta/types.go index 0d1eada892..74442cad9f 100644 --- a/tools/rosetta/types.go +++ b/tools/rosetta/types.go @@ -17,10 +17,11 @@ const ( // which are not represented as transactions we mock only the balance changes // happening at those levels as transactions. (check BeginBlockTxHash for more info) const ( - DeliverTxSize = sha256.Size - BeginEndBlockTxSize = DeliverTxSize + 1 - EndBlockHashStart = 0x0 - BeginBlockHashStart = 0x1 + DeliverTxSize = sha256.Size + BeginEndBlockTxSize = DeliverTxSize + 1 + EndBlockHashStart = 0x0 + BeginBlockHashStart = 0x1 + FinalizeBlockHashStart = 0x1 ) const ( diff --git a/types/abci.go b/types/abci.go index ebae95e92c..c3c94f34dc 100644 --- a/types/abci.go +++ b/types/abci.go @@ -11,13 +11,19 @@ type InitChainer func(ctx Context, req abci.RequestInitChain) abci.ResponseInitC // // Note: applications which set create_empty_blocks=false will not have regular block timing and should use // e.g. BFT timestamps rather than block height for any periodic BeginBlock logic -type BeginBlocker func(ctx Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock +type BeginBlocker func(Context) (BeginBlock, error) // EndBlocker runs code after the transactions in a block and return updates to the validator set // // Note: applications which set create_empty_blocks=false will not have regular block timing and should use // e.g. BFT timestamps rather than block height for any periodic EndBlock logic -type EndBlocker func(ctx Context, req abci.RequestEndBlock) abci.ResponseEndBlock +type EndBlocker func(Context) (EndBlock, error) + +// EndBlock defines a type which contains endblock events and validator set updates +type EndBlock struct { + ValidatorUpdates []abci.ValidatorUpdate + Events []abci.Event +} // PeerFilter responds to p2p filtering queries from Tendermint type PeerFilter func(info string) abci.ResponseQuery @@ -27,3 +33,20 @@ type ProcessProposalHandler func(Context, abci.RequestProcessProposal) abci.Resp // PrepareProposalHandler defines a function type alias for preparing a proposal type PrepareProposalHandler func(Context, abci.RequestPrepareProposal) abci.ResponsePrepareProposal + +type ResponsePreBlock struct { + ConsensusParamsChanged bool +} + +// PreBlocker runs code before the `BeginBlocker` and defines a function type alias for executing logic right +// before FinalizeBlock is called (but after its context has been set up). It is +// intended to allow applications to perform computation on vote extensions and +// persist their results in state. +// +// Note: returning an error will make FinalizeBlock fail. +type PreBlocker func(Context, *abci.RequestFinalizeBlock) (*ResponsePreBlock, error) + +// BeginBlock defines a type which contains beginBlock events +type BeginBlock struct { + Events []abci.Event +} diff --git a/types/context.go b/types/context.go index 0942c05fc7..9f54ae3111 100644 --- a/types/context.go +++ b/types/context.go @@ -4,6 +4,8 @@ import ( "context" "time" + "cosmossdk.io/core/comet" + "cosmossdk.io/core/header" abci "github.com/cometbft/cometbft/abci/types" tmbytes "github.com/cometbft/cometbft/libs/bytes" "github.com/cometbft/cometbft/libs/log" @@ -14,6 +16,21 @@ import ( storetypes "github.com/cosmos/cosmos-sdk/store/types" ) +// ExecMode defines the execution mode which can be set on a Context. +type ExecMode uint8 + +// All possible execution modes. +const ( + ExecModeCheck ExecMode = iota + ExecModeReCheck + ExecModeSimulate + ExecModePrepareProposal + ExecModeProcessProposal + ExecModeVoteExtension + ExecModeVerifyVoteExtension + ExecModeFinalize +) + /* Context is an immutable object contains all information needed to process a request. @@ -41,6 +58,9 @@ type Context struct { priority int64 // The tx priority, only relevant in CheckTx kvGasConfig storetypes.GasConfig transientKVGasConfig storetypes.GasConfig + headerInfo header.Info + execMode ExecMode + cometInfo comet.BlockInfo } // Proposed rename, not done to avoid API breakage @@ -328,3 +348,23 @@ func UnwrapSDKContext(ctx context.Context) Context { } return ctx.Value(SdkContextKey).(Context) } + +// WithHeaderInfo returns a Context with an updated header info +func (c Context) WithHeaderInfo(headerInfo header.Info) Context { + // Settime to UTC + headerInfo.Time = headerInfo.Time.UTC() + c.headerInfo = headerInfo + return c +} + +// WithExecMode returns a Context with an updated ExecMode. +func (c Context) WithExecMode(m ExecMode) Context { + c.execMode = m + return c +} + +// WithCometInfo returns a Context with an updated comet info +func (c Context) WithCometInfo(cometInfo comet.BlockInfo) Context { + c.cometInfo = cometInfo + return c +} diff --git a/types/errors/abci.go b/types/errors/abci.go index 542ab5dbce..2de32bafda 100644 --- a/types/errors/abci.go +++ b/types/errors/abci.go @@ -1,6 +1,7 @@ package errors import ( + errorsmod "cosmossdk.io/errors" abci "github.com/cometbft/cometbft/abci/types" ) @@ -42,3 +43,17 @@ func QueryResult(err error, debug bool) abci.ResponseQuery { Log: log, } } + +// ResponseExecTxResultWithEvents returns an ABCI ExecTxResult object with fields +// filled in from the given error, gas values and events. +func ResponseExecTxResultWithEvents(err error, gw, gu uint64, events []abci.Event, debug bool) *abci.ExecTxResult { + space, code, log := errorsmod.ABCIInfo(err, debug) + return &abci.ExecTxResult{ + Codespace: space, + Code: code, + Log: log, + GasWanted: int64(gw), + GasUsed: int64(gu), + Events: events, + } +} diff --git a/types/module/module.go b/types/module/module.go index 95e35f3546..960bc99a0e 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -30,6 +30,7 @@ package module import ( "encoding/json" + "errors" "fmt" "sort" @@ -196,13 +197,13 @@ type HasConsensusVersion interface { // BeginBlockAppModule is an extension interface that contains information about the AppModule and BeginBlock. type BeginBlockAppModule interface { AppModule - BeginBlock(sdk.Context, abci.RequestBeginBlock) + BeginBlock(sdk.Context) error } // EndBlockAppModule is an extension interface that contains information about the AppModule and EndBlock. type EndBlockAppModule interface { AppModule - EndBlock(sdk.Context, abci.RequestEndBlock) []abci.ValidatorUpdate + EndBlock(sdk.Context) ([]abci.ValidatorUpdate, error) } // GenesisOnlyAppModule is an AppModule that only has import/export functionality @@ -553,50 +554,69 @@ func (m Manager) RunMigrations(ctx sdk.Context, cfg Configurator, fromVM Version // BeginBlock performs begin block functionality for all modules. It creates a // child context with an event manager to aggregate events emitted from all // modules. -func (m *Manager) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { +func (m *Manager) BeginBlock(ctx sdk.Context) (sdk.BeginBlock, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) - for _, moduleName := range m.OrderBeginBlockers { - module, ok := m.Modules[moduleName].(BeginBlockAppModule) - if ok { - module.BeginBlock(ctx, req) + if module, ok := m.Modules[moduleName].(BeginBlockAppModule); ok { + if err := module.BeginBlock(ctx); err != nil { + return sdk.BeginBlock{}, err + } } } - return abci.ResponseBeginBlock{ + return sdk.BeginBlock{ Events: ctx.EventManager().ABCIEvents(), - } + }, nil +} + +// HasEndBlocker is the extension interface that modules should implement to run +// custom logic after transaction processing in a block. +type HasEndBlocker interface { + AppModule + + // EndBlock is a method that will be run after transactions are processed in + // a block. + EndBlock(sdk.Context) error } // EndBlock performs end block functionality for all modules. It creates a // child context with an event manager to aggregate events emitted from all // modules. -func (m *Manager) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { +func (m *Manager) EndBlock(ctx sdk.Context) (sdk.EndBlock, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) validatorUpdates := []abci.ValidatorUpdate{} for _, moduleName := range m.OrderEndBlockers { - module, ok := m.Modules[moduleName].(EndBlockAppModule) - if !ok { - continue - } - moduleValUpdates := module.EndBlock(ctx, req) - - // use these validator updates if provided, the module manager assumes - // only one module will update the validator set - if len(moduleValUpdates) > 0 { - if len(validatorUpdates) > 0 { - panic("validator EndBlock updates already set by a previous module") + if module, ok := m.Modules[moduleName].(HasEndBlocker); ok { + err := module.EndBlock(ctx) + if err != nil { + return sdk.EndBlock{}, err } + } else if module, ok := m.Modules[moduleName].(EndBlockAppModule); ok { + moduleValUpdates, err := module.EndBlock(ctx) + if err != nil { + return sdk.EndBlock{}, err + } + // use these validator updates if provided, the module manager assumes + // only one module will update the validator set + if len(moduleValUpdates) > 0 { + if len(validatorUpdates) > 0 { + return sdk.EndBlock{}, errors.New("validator EndBlock updates already set by a previous module") + } - validatorUpdates = moduleValUpdates + for _, updates := range moduleValUpdates { + validatorUpdates = append(validatorUpdates, abci.ValidatorUpdate{PubKey: updates.PubKey, Power: updates.Power}) + } + } + } else { + continue } } - return abci.ResponseEndBlock{ + return sdk.EndBlock{ ValidatorUpdates: validatorUpdates, Events: ctx.EventManager().ABCIEvents(), - } + }, nil } // GetVersionMap gets consensus version from all modules diff --git a/x/authz/module/module.go b/x/authz/module/module.go index 909d6e5986..61a8f63cf3 100644 --- a/x/authz/module/module.go +++ b/x/authz/module/module.go @@ -160,8 +160,9 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return 2 } // BeginBlock returns the begin blocker for the authz module. -func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { +func (am AppModule) BeginBlock(ctx sdk.Context) error { BeginBlocker(ctx, am.keeper) + return nil } func init() { diff --git a/x/capability/module.go b/x/capability/module.go index b511f2041d..c96eb44d7f 100644 --- a/x/capability/module.go +++ b/x/capability/module.go @@ -147,7 +147,7 @@ func (AppModule) ConsensusVersion() uint64 { return 1 } // BeginBlock executes all ABCI BeginBlock logic respective to the capability module. // BeginBlocker calls InitMemStore to assert that the memory store is initialized. // It's safe to run multiple times. -func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { +func (am AppModule) BeginBlock(ctx sdk.Context) error { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) am.keeper.InitMemStore(ctx) @@ -155,6 +155,7 @@ func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { if am.sealKeeper && !am.keeper.IsSealed() { am.keeper.Seal() } + return nil } // GenerateGenesisState creates a randomized GenState of the capability module. diff --git a/x/crisis/module.go b/x/crisis/module.go index 96cf10d5ba..b5d32bc25a 100644 --- a/x/crisis/module.go +++ b/x/crisis/module.go @@ -177,9 +177,9 @@ func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } // EndBlock returns the end blocker for the crisis module. It returns no validator // updates. -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { +func (am AppModule) EndBlock(ctx sdk.Context) ([]abci.ValidatorUpdate, error) { EndBlocker(ctx, *am.keeper) - return []abci.ValidatorUpdate{} + return []abci.ValidatorUpdate{}, nil } // App Wiring Setup diff --git a/x/distribution/abci.go b/x/distribution/abci.go index 320f3961fe..9551de76c2 100644 --- a/x/distribution/abci.go +++ b/x/distribution/abci.go @@ -3,8 +3,6 @@ package distribution import ( "time" - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/distribution/keeper" @@ -13,22 +11,22 @@ import ( // BeginBlocker sets the proposer for determining distribution during endblock // and distribute rewards for the previous block. -func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) { +func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) // determine the total power signing the block var previousTotalPower int64 - for _, voteInfo := range req.LastCommitInfo.GetVotes() { + for _, voteInfo := range ctx.VoteInfos() { previousTotalPower += voteInfo.Validator.Power } // TODO this is Tendermint-dependent // ref https://github.com/cosmos/cosmos-sdk/issues/3095 if ctx.BlockHeight() > 1 { - k.AllocateTokens(ctx, previousTotalPower, req.LastCommitInfo.GetVotes()) + k.AllocateTokens(ctx, previousTotalPower, ctx.VoteInfos()) } // record the proposer for when we payout on the next block - consAddr := sdk.ConsAddress(req.Header.ProposerAddress) + consAddr := sdk.ConsAddress(ctx.BlockHeader().ProposerAddress) k.SetPreviousProposerConsAddr(ctx, consAddr) } diff --git a/x/distribution/module.go b/x/distribution/module.go index eb85e4d458..67da2e23c1 100644 --- a/x/distribution/module.go +++ b/x/distribution/module.go @@ -173,8 +173,9 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } // BeginBlock returns the begin blocker for the distribution module. -func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { - BeginBlocker(ctx, req, am.keeper) +func (am AppModule) BeginBlock(ctx sdk.Context) error { + BeginBlocker(ctx, am.keeper) + return nil } // AppModuleSimulation functions diff --git a/x/evidence/abci.go b/x/evidence/abci.go index 740b3a162c..9a6c3a1045 100644 --- a/x/evidence/abci.go +++ b/x/evidence/abci.go @@ -1,11 +1,8 @@ package evidence import ( - "fmt" "time" - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/evidence/keeper" @@ -14,19 +11,8 @@ import ( // BeginBlocker iterates through and handles any newly discovered evidence of // misbehavior submitted by Tendermint. Currently, only equivocation is handled. -func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) { +func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) - for _, tmEvidence := range req.ByzantineValidators { - switch tmEvidence.Type { - // It's still ongoing discussion how should we treat and slash attacks with - // premeditation. So for now we agree to treat them in the same way. - case abci.MisbehaviorType_DUPLICATE_VOTE, abci.MisbehaviorType_LIGHT_CLIENT_ATTACK: - evidence := types.FromABCIEvidence(tmEvidence) - k.HandleEquivocationEvidence(ctx, evidence.(*types.Equivocation)) - - default: - k.Logger(ctx).Error(fmt.Sprintf("ignored unknown evidence type: %s", tmEvidence.Type)) - } - } + // TODO(wenhui) } diff --git a/x/evidence/module.go b/x/evidence/module.go index 3b0ed4b5e5..4e05ce6c5c 100644 --- a/x/evidence/module.go +++ b/x/evidence/module.go @@ -165,8 +165,9 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return 1 } // BeginBlock executes all ABCI BeginBlock logic respective to the evidence module. -func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { - BeginBlocker(ctx, req, am.keeper) +func (am AppModule) BeginBlock(ctx sdk.Context) error { + BeginBlocker(ctx, am.keeper) + return nil } // AppModuleSimulation functions diff --git a/x/feegrant/module/module.go b/x/feegrant/module/module.go index 7fada9f62c..02d10052e6 100644 --- a/x/feegrant/module/module.go +++ b/x/feegrant/module/module.go @@ -173,9 +173,9 @@ func (AppModule) ConsensusVersion() uint64 { return 2 } // EndBlock returns the end blocker for the feegrant module. It returns no validator // updates. -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { +func (am AppModule) EndBlock(ctx sdk.Context) ([]abci.ValidatorUpdate, error) { EndBlocker(ctx, am.keeper) - return []abci.ValidatorUpdate{} + return []abci.ValidatorUpdate{}, nil } func init() { diff --git a/x/gov/module.go b/x/gov/module.go index 1755638ed6..d0af299b3f 100644 --- a/x/gov/module.go +++ b/x/gov/module.go @@ -317,9 +317,9 @@ func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } // EndBlock returns the end blocker for the gov module. It returns no validator // updates. -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { +func (am AppModule) EndBlock(ctx sdk.Context) ([]abci.ValidatorUpdate, error) { EndBlocker(ctx, am.keeper) - return []abci.ValidatorUpdate{} + return []abci.ValidatorUpdate{}, nil } // AppModuleSimulation functions diff --git a/x/group/module/module.go b/x/group/module/module.go index 24b4613508..5dbf9ac7e4 100644 --- a/x/group/module/module.go +++ b/x/group/module/module.go @@ -158,9 +158,9 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } // EndBlock implements the group module's EndBlock. -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { +func (am AppModule) EndBlock(ctx sdk.Context) ([]abci.ValidatorUpdate, error) { EndBlocker(ctx, am.keeper) - return []abci.ValidatorUpdate{} + return []abci.ValidatorUpdate{}, nil } // ____________________________________________________________________________ diff --git a/x/mint/module.go b/x/mint/module.go index 03293e9282..383fd7dc06 100644 --- a/x/mint/module.go +++ b/x/mint/module.go @@ -179,8 +179,9 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } // BeginBlock returns the begin blocker for the mint module. -func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { +func (am AppModule) BeginBlock(ctx sdk.Context) error { BeginBlocker(ctx, am.keeper, am.inflationCalculator) + return nil } // AppModuleSimulation functions diff --git a/x/simulation/simulate.go b/x/simulation/simulate.go index 6affec9249..7f8db584c6 100644 --- a/x/simulation/simulate.go +++ b/x/simulation/simulate.go @@ -166,7 +166,6 @@ func SimulateFromSeed( // Run the BeginBlock handler logWriter.AddEntry(BeginBlockEntry(int64(height))) - app.BeginBlock(request) ctx := app.NewContext(false, header) @@ -189,7 +188,6 @@ func SimulateFromSeed( operations := blockSimulator(r, app, ctx, accs, header) opCount += operations + numQueuedOpsRan + numQueuedTimeOpsRan - res := app.EndBlock(abci.RequestEndBlock{}) header.Height++ header.Time = header.Time.Add( time.Duration(minTimePerBlock) * time.Second) @@ -216,7 +214,6 @@ func SimulateFromSeed( // Update the validator set, which will be reflected in the application // on the next block validators = nextValidators - nextValidators = updateValidators(tb, r, params, validators, res.ValidatorUpdates, eventStats.Tally) // update the exported params if config.ExportParamsPath != "" && config.ExportParamsHeight == height { diff --git a/x/slashing/abci.go b/x/slashing/abci.go index adbe0db78f..35182c1de2 100644 --- a/x/slashing/abci.go +++ b/x/slashing/abci.go @@ -3,8 +3,6 @@ package slashing import ( "time" - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/slashing/keeper" @@ -13,13 +11,8 @@ import ( // BeginBlocker check for infraction evidence or downtime of validators // on every begin block -func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) { +func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) - // Iterate over all the validators which *should* have signed this block - // store whether or not they have actually signed it and slash/unbond any - // which have missed too many blocks in a row (downtime slashing) - for _, voteInfo := range req.LastCommitInfo.GetVotes() { - k.HandleValidatorSignature(ctx, voteInfo.Validator.Address, voteInfo.Validator.Power, voteInfo.SignedLastBlock) - } + // TODO(wenhui) } diff --git a/x/slashing/module.go b/x/slashing/module.go index 90e1d7f5b6..e1e9406b54 100644 --- a/x/slashing/module.go +++ b/x/slashing/module.go @@ -170,8 +170,9 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } // BeginBlock returns the begin blocker for the slashing module. -func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { - BeginBlocker(ctx, req, am.keeper) +func (am AppModule) BeginBlock(ctx sdk.Context) error { + BeginBlocker(ctx, am.keeper) + return nil } // AppModuleSimulation functions diff --git a/x/staking/module.go b/x/staking/module.go index 9ddaaeab18..844697de2e 100644 --- a/x/staking/module.go +++ b/x/staking/module.go @@ -182,14 +182,15 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw func (AppModule) ConsensusVersion() uint64 { return consensusVersion } // BeginBlock returns the begin blocker for the staking module. -func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { +func (am AppModule) BeginBlock(ctx sdk.Context) error { BeginBlocker(ctx, am.keeper) + return nil } // EndBlock returns the end blocker for the staking module. It returns no validator // updates. -func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - return EndBlocker(ctx, am.keeper) +func (am AppModule) EndBlock(ctx sdk.Context) ([]abci.ValidatorUpdate, error) { + return EndBlocker(ctx, am.keeper), nil } func init() { diff --git a/x/upgrade/abci.go b/x/upgrade/abci.go index 188e0cd02b..527d7c03cf 100644 --- a/x/upgrade/abci.go +++ b/x/upgrade/abci.go @@ -4,8 +4,6 @@ import ( "fmt" "time" - abci "github.com/cometbft/cometbft/abci/types" - "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/upgrade/keeper" @@ -20,7 +18,7 @@ import ( // The purpose is to ensure the binary is switched EXACTLY at the desired block, and to allow // a migration to be executed if needed upon this switch (migration defined in the new binary) // skipUpgradeHeightArray is a set of block heights for which the upgrade must be skipped -func BeginBlocker(k *keeper.Keeper, ctx sdk.Context, _ abci.RequestBeginBlock) { +func BeginBlocker(k *keeper.Keeper, ctx sdk.Context) { defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker) plan, found := k.GetUpgradePlan(ctx) diff --git a/x/upgrade/module.go b/x/upgrade/module.go index 2df9b84a7b..cb8d09443b 100644 --- a/x/upgrade/module.go +++ b/x/upgrade/module.go @@ -157,8 +157,9 @@ func (AppModule) ConsensusVersion() uint64 { return consensusVersion } // BeginBlock calls the upgrade module hooks // // CONTRACT: this is registered in BeginBlocker *before* all other modules' BeginBlock functions -func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { - BeginBlocker(am.keeper, ctx, req) +func (am AppModule) BeginBlock(ctx sdk.Context) error { + BeginBlocker(am.keeper, ctx) + return nil } //