diff --git a/server/v2/cometbft/go.mod b/server/v2/cometbft/go.mod index fce3c28529c8..bd09d8ee32e3 100644 --- a/server/v2/cometbft/go.mod +++ b/server/v2/cometbft/go.mod @@ -23,7 +23,7 @@ require ( cosmossdk.io/schema v0.4.0 //main cosmossdk.io/server/v2 v2.0.0-20241211154953-a38a6a2c8bc8 // main cosmossdk.io/server/v2/appmanager v0.0.0-20241203212527-7d117425d880 // main - cosmossdk.io/server/v2/stf v0.0.0-20241204101618-7fa2356c07aa // main + cosmossdk.io/server/v2/stf v0.0.0-20241212104257-e6948eeda877 // main cosmossdk.io/store/v2 v2.0.0-20241209145349-34f407d6367a // main cosmossdk.io/x/consensus v0.0.0-00010101000000-000000000000 github.com/cometbft/cometbft v1.0.0-rc2.0.20241127125717-4ce33b646ac9 diff --git a/server/v2/cometbft/go.sum b/server/v2/cometbft/go.sum index e8109181205d..90268f12ec7e 100644 --- a/server/v2/cometbft/go.sum +++ b/server/v2/cometbft/go.sum @@ -28,8 +28,8 @@ cosmossdk.io/server/v2 v2.0.0-20241211154953-a38a6a2c8bc8 h1:Z1tRewzCemRc4iwKPFG cosmossdk.io/server/v2 v2.0.0-20241211154953-a38a6a2c8bc8/go.mod h1:RAectNg/rAaq0AHOuxbxY2YVTYTVBJCTVg5wHpCIZhE= cosmossdk.io/server/v2/appmanager v0.0.0-20241203212527-7d117425d880 h1:0mtB8fSvDjD835WwWF4rGk9qy5TjVjk2jsW14L37v0E= cosmossdk.io/server/v2/appmanager v0.0.0-20241203212527-7d117425d880/go.mod h1:elhlrldWtm+9U4PxE0G3wjz83yQwVVGVAOncXJPY1Xc= -cosmossdk.io/server/v2/stf v0.0.0-20241204101618-7fa2356c07aa h1:2V9nqgL50nw45HcQw1UBRQ/y0QBzrgfLIStPSxFnMtY= -cosmossdk.io/server/v2/stf v0.0.0-20241204101618-7fa2356c07aa/go.mod h1:4e9SzLyeGptQ3tSR6nKCNwCu7Ye4uUS2WIJih29dG2c= +cosmossdk.io/server/v2/stf v0.0.0-20241212104257-e6948eeda877 h1:9NTR/GHLFkKOZEBqLs3fMaVKUfsEfuboj+JUcq0PmWI= +cosmossdk.io/server/v2/stf v0.0.0-20241212104257-e6948eeda877/go.mod h1:XDY0jKCQwtK0NACzQ6BKepv+2GWbHuYsjLQhxl+itpY= cosmossdk.io/store v1.0.0-rc.0.0.20241204123127-eb3bf8b0469d h1:KQM4Q6kjwlM4HuDZRV8/ZDXX3whjfStndYNTsRrbboQ= cosmossdk.io/store v1.0.0-rc.0.0.20241204123127-eb3bf8b0469d/go.mod h1:oZBBY4BrkYnghr6MFL0MP5mGqpkPedHcWkXwXddd6tU= cosmossdk.io/store/v2 v2.0.0-20241209145349-34f407d6367a h1:v300rqUJV1QuNhCcMlhzwgOEWATIknxv8qqwxNfDv0E= diff --git a/server/v2/stf/stf.go b/server/v2/stf/stf.go deleted file mode 100644 index 4b85c5ffe499..000000000000 --- a/server/v2/stf/stf.go +++ /dev/null @@ -1,683 +0,0 @@ -package stf - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "strings" - - appmodulev2 "cosmossdk.io/core/appmodule/v2" - corecontext "cosmossdk.io/core/context" - "cosmossdk.io/core/event" - "cosmossdk.io/core/gas" - "cosmossdk.io/core/header" - "cosmossdk.io/core/log" - "cosmossdk.io/core/router" - "cosmossdk.io/core/server" - "cosmossdk.io/core/store" - "cosmossdk.io/core/transaction" - "cosmossdk.io/schema/appdata" - stfgas "cosmossdk.io/server/v2/stf/gas" - "cosmossdk.io/server/v2/stf/internal" -) - -type eContextKey struct{} - -var executionContextKey = eContextKey{} - -// STF is a struct that manages the state transition component of the app. -type STF[T transaction.Tx] struct { - logger log.Logger - - msgRouter coreRouterImpl - queryRouter coreRouterImpl - - doPreBlock func(ctx context.Context, txs []T) error - doBeginBlock func(ctx context.Context) error - doEndBlock func(ctx context.Context) error - doValidatorUpdate func(ctx context.Context) ([]appmodulev2.ValidatorUpdate, error) - - doTxValidation func(ctx context.Context, tx T) error - postTxExec func(ctx context.Context, tx T, success bool) error - - branchFn branchFn // branchFn is a function that given a readonly state it returns a writable version of it. - makeGasMeter makeGasMeterFn - makeGasMeteredState makeGasMeteredStateFn -} - -// New returns a new STF instance. -func New[T transaction.Tx]( - logger log.Logger, - msgRouterBuilder *MsgRouterBuilder, - queryRouterBuilder *MsgRouterBuilder, - doPreBlock func(ctx context.Context, txs []T) error, - doBeginBlock func(ctx context.Context) error, - doEndBlock func(ctx context.Context) error, - doTxValidation func(ctx context.Context, tx T) error, - doValidatorUpdate func(ctx context.Context) ([]appmodulev2.ValidatorUpdate, error), - postTxExec func(ctx context.Context, tx T, success bool) error, - branch func(store store.ReaderMap) store.WriterMap, -) (*STF[T], error) { - msgRouter, err := msgRouterBuilder.build() - if err != nil { - return nil, fmt.Errorf("build msg router: %w", err) - } - queryRouter, err := queryRouterBuilder.build() - if err != nil { - return nil, fmt.Errorf("build query router: %w", err) - } - - return &STF[T]{ - logger: logger, - msgRouter: msgRouter, - queryRouter: queryRouter, - doPreBlock: doPreBlock, - doBeginBlock: doBeginBlock, - doEndBlock: doEndBlock, - doValidatorUpdate: doValidatorUpdate, - doTxValidation: doTxValidation, - postTxExec: postTxExec, // TODO - branchFn: branch, - makeGasMeter: stfgas.DefaultGasMeter, - makeGasMeteredState: stfgas.DefaultWrapWithGasMeter, - }, nil -} - -// DeliverBlock is our state transition function. -// It takes a read only view of the state to apply the block to, -// executes the block and returns the block results and the new state. -func (s STF[T]) DeliverBlock( - ctx context.Context, - block *server.BlockRequest[T], - state store.ReaderMap, -) (blockResult *server.BlockResponse, newState store.WriterMap, err error) { - // creates a new branchFn state, from the readonly view of the state - // that can be written to. - newState = s.branchFn(state) - hi := header.Info{ - Hash: block.Hash, - AppHash: block.AppHash, - ChainID: block.ChainId, - Time: block.Time, - Height: int64(block.Height), - } - // set header info - err = s.setHeaderInfo(newState, hi) - if err != nil { - return nil, nil, fmt.Errorf("unable to set initial header info, %w", err) - } - - exCtx := s.makeContext(ctx, ConsensusIdentity, newState, internal.ExecModeFinalize) - exCtx.setHeaderInfo(hi) - - // reset events - exCtx.events = make([]event.Event, 0) - // pre block is called separate from begin block in order to prepopulate state - preBlockEvents, err := s.preBlock(exCtx, block.Txs) - if err != nil { - return nil, nil, err - } - - if err = isCtxCancelled(ctx); err != nil { - return nil, nil, err - } - - // reset events - exCtx.events = make([]event.Event, 0) - // begin block - var beginBlockEvents []event.Event - if !block.IsGenesis { - // begin block - beginBlockEvents, err = s.beginBlock(exCtx) - if err != nil { - return nil, nil, err - } - } - - // check if we need to return early - if err = isCtxCancelled(ctx); err != nil { - return nil, nil, err - } - - // execute txs - txResults := make([]server.TxResult, len(block.Txs)) - // TODO: skip first tx if vote extensions are enabled (marko) - for i, txBytes := range block.Txs { - // check if we need to return early or continue delivering txs - if err = isCtxCancelled(ctx); err != nil { - return nil, nil, err - } - txResults[i] = s.deliverTx(exCtx, newState, txBytes, transaction.ExecModeFinalize, hi, int32(i+1)) - } - // reset events - exCtx.events = make([]event.Event, 0) - // end block - endBlockEvents, valset, err := s.endBlock(exCtx) - if err != nil { - return nil, nil, err - } - - return &server.BlockResponse{ - ValidatorUpdates: valset, - PreBlockEvents: preBlockEvents, - BeginBlockEvents: beginBlockEvents, - TxResults: txResults, - EndBlockEvents: endBlockEvents, - }, newState, nil -} - -// deliverTx executes a TX and returns the result. -func (s STF[T]) deliverTx( - ctx context.Context, - state store.WriterMap, - tx T, - execMode transaction.ExecMode, - hi header.Info, - txIndex int32, -) server.TxResult { - // recover in the case of a panic - var recoveryError error - defer func() { - if r := recover(); r != nil { - recoveryError = fmt.Errorf("panic during transaction execution: %s", r) - s.logger.Error("panic during transaction execution", "error", recoveryError) - } - }() - // handle error from GetGasLimit - gasLimit, gasLimitErr := tx.GetGasLimit() - if gasLimitErr != nil { - return server.TxResult{ - Error: gasLimitErr, - } - } - - if recoveryError != nil { - return server.TxResult{ - Error: recoveryError, - } - } - validateGas, validationEvents, err := s.validateTx(ctx, state, gasLimit, tx, execMode) - if err != nil { - return server.TxResult{ - Error: err, - } - } - events := make([]event.Event, 0) - // set the event indexes, set MsgIndex to 0 in validation events - for i, e := range validationEvents { - e.BlockNumber = uint64(hi.Height) - e.BlockStage = appdata.TxProcessingStage - e.TxIndex = txIndex - e.MsgIndex = 0 - e.EventIndex = int32(i + 1) - events = append(events, e) - } - - execResp, execGas, execEvents, err := s.execTx(ctx, state, gasLimit-validateGas, tx, execMode, hi) - // set the TxIndex in the exec events - for _, e := range execEvents { - e.BlockNumber = uint64(hi.Height) - e.BlockStage = appdata.TxProcessingStage - e.TxIndex = txIndex - events = append(events, e) - } - - return server.TxResult{ - Events: events, - GasUsed: execGas + validateGas, - GasWanted: gasLimit, - Resp: execResp, - Error: err, - } -} - -// validateTx validates a transaction given the provided WritableState and gas limit. -// If the validation is successful, state is committed -func (s STF[T]) validateTx( - ctx context.Context, - state store.WriterMap, - gasLimit uint64, - tx T, - execMode transaction.ExecMode, -) (gasUsed uint64, events []event.Event, err error) { - validateState := s.branchFn(state) - hi, err := s.getHeaderInfo(validateState) - if err != nil { - return 0, nil, err - } - validateCtx := s.makeContext(ctx, RuntimeIdentity, validateState, execMode) - validateCtx.setHeaderInfo(hi) - validateCtx.setGasLimit(gasLimit) - err = s.doTxValidation(validateCtx, tx) - if err != nil { - return 0, nil, err - } - - consumed := validateCtx.meter.Limit() - validateCtx.meter.Remaining() - - return consumed, validateCtx.events, applyStateChanges(state, validateState) -} - -// execTx executes the tx messages on the provided state. If the tx fails then the state is discarded. -func (s STF[T]) execTx( - ctx context.Context, - state store.WriterMap, - gasLimit uint64, - tx T, - execMode transaction.ExecMode, - hi header.Info, -) ([]transaction.Msg, uint64, []event.Event, error) { - execState := s.branchFn(state) - - msgsResp, gasUsed, runTxMsgsEvents, txErr := s.runTxMsgs(ctx, execState, gasLimit, tx, execMode, hi) - if txErr != nil { - // in case of error during message execution, we do not apply the exec state. - // instead we run the post exec handler in a new branchFn from the initial state. - postTxState := s.branchFn(state) - postTxCtx := s.makeContext(ctx, RuntimeIdentity, postTxState, execMode) - postTxCtx.setHeaderInfo(hi) - - postTxErr := s.postTxExec(postTxCtx, tx, false) - if postTxErr != nil { - // if the post tx handler fails, then we do not apply any state change to the initial state. - // we just return the exec gas used and a joined error from TX error and post TX error. - return nil, gasUsed, nil, errors.Join(txErr, postTxErr) - } - // in case post tx is successful, then we commit the post tx state to the initial state, - // and we return post tx events alongside exec gas used and the error of the tx. - applyErr := applyStateChanges(state, postTxState) - if applyErr != nil { - return nil, 0, nil, applyErr - } - // set the event indexes, set MsgIndex to -1 in post tx events - for i := range postTxCtx.events { - postTxCtx.events[i].EventIndex = int32(i + 1) - postTxCtx.events[i].MsgIndex = -1 - } - - return nil, gasUsed, postTxCtx.events, txErr - } - // tx execution went fine, now we use the same state to run the post tx exec handler, - // in case the execution of the post tx fails, then no state change is applied and the - // whole execution step is rolled back. - postTxCtx := s.makeContext(ctx, RuntimeIdentity, execState, execMode) // NO gas limit. - postTxCtx.setHeaderInfo(hi) - postTxErr := s.postTxExec(postTxCtx, tx, true) - if postTxErr != nil { - // if post tx fails, then we do not apply any state change, we return the post tx error, - // alongside the gas used. - return nil, gasUsed, nil, postTxErr - } - // both the execution and post tx execution step were successful, so we apply the state changes - // to the provided state, and we return responses, and events from exec tx and post tx exec. - applyErr := applyStateChanges(state, execState) - if applyErr != nil { - return nil, 0, nil, applyErr - } - // set the event indexes, set MsgIndex to -1 in post tx events - for i := range postTxCtx.events { - postTxCtx.events[i].EventIndex = int32(i + 1) - postTxCtx.events[i].MsgIndex = -1 - } - - return msgsResp, gasUsed, append(runTxMsgsEvents, postTxCtx.events...), nil -} - -// runTxMsgs will execute the messages contained in the TX with the provided state. -func (s STF[T]) runTxMsgs( - ctx context.Context, - state store.WriterMap, - gasLimit uint64, - tx T, - execMode transaction.ExecMode, - hi header.Info, -) ([]transaction.Msg, uint64, []event.Event, error) { - txSenders, err := tx.GetSenders() - if err != nil { - return nil, 0, nil, err - } - msgs, err := tx.GetMessages() - if err != nil { - return nil, 0, nil, err - } - msgResps := make([]transaction.Msg, len(msgs)) - - execCtx := s.makeContext(ctx, RuntimeIdentity, state, execMode) - execCtx.setHeaderInfo(hi) - execCtx.setGasLimit(gasLimit) - events := make([]event.Event, 0) - for i, msg := range msgs { - execCtx.sender = txSenders[i] - execCtx.events = make([]event.Event, 0) // reset events - resp, err := s.msgRouter.Invoke(execCtx, msg) - if err != nil { - return nil, 0, nil, err // do not wrap the error or we lose the original error type - } - msgResps[i] = resp - for j, e := range execCtx.events { - e.MsgIndex = int32(i + 1) - e.EventIndex = int32(j + 1) - events = append(events, e) - } - - // add message event - events = append(events, createMessageEvent(msg, int32(i+1), int32(len(execCtx.events)+1))) - } - - consumed := execCtx.meter.Limit() - execCtx.meter.Remaining() - return msgResps, consumed, events, nil -} - -// Create a message event, with two kv: action, the type url of the message -// and module, the module of the message. -func createMessageEvent(msg transaction.Msg, msgIndex, eventIndex int32) event.Event { - // Assumes that module name is the second element of the msg type URL - // e.g. "cosmos.bank.v1beta1.MsgSend" => "bank" - // It returns an empty string if the input is not a valid type URL - getModuleNameFromTypeURL := func(input string) string { - moduleName := strings.Split(input, ".") - if len(moduleName) > 1 { - return moduleName[1] - } - - return "" - } - - return event.Event{ - MsgIndex: msgIndex, - EventIndex: eventIndex, - Type: "message", - Attributes: func() ([]appdata.EventAttribute, error) { - typeURL := msgTypeURL(msg) - return []appdata.EventAttribute{ - {Key: "action", Value: "/" + typeURL}, - {Key: "module", Value: getModuleNameFromTypeURL(typeURL)}, - }, nil - }, - Data: func() (json.RawMessage, error) { - typeURL := msgTypeURL(msg) - attrs := []appdata.EventAttribute{ - {Key: "action", Value: "/" + typeURL}, - {Key: "module", Value: getModuleNameFromTypeURL(typeURL)}, - } - - return json.Marshal(attrs) - }, - } -} - -// preBlock executes the pre block logic. -func (s STF[T]) preBlock( - ctx *executionContext, - txs []T, -) ([]event.Event, error) { - err := s.doPreBlock(ctx, txs) - if err != nil { - return nil, err - } - - for i := range ctx.events { - ctx.events[i].BlockNumber = uint64(ctx.headerInfo.Height) - ctx.events[i].BlockStage = appdata.PreBlockStage - ctx.events[i].EventIndex = int32(i + 1) - } - - return ctx.events, nil -} - -// beginBlock executes the begin block logic. -func (s STF[T]) beginBlock( - ctx *executionContext, -) (beginBlockEvents []event.Event, err error) { - err = s.doBeginBlock(ctx) - if err != nil { - return nil, err - } - - for i := range ctx.events { - ctx.events[i].BlockNumber = uint64(ctx.headerInfo.Height) - ctx.events[i].BlockStage = appdata.BeginBlockStage - ctx.events[i].EventIndex = int32(i + 1) - } - - return ctx.events, nil -} - -// endBlock executes the end block logic. -func (s STF[T]) endBlock( - ctx *executionContext, -) ([]event.Event, []appmodulev2.ValidatorUpdate, error) { - err := s.doEndBlock(ctx) - if err != nil { - return nil, nil, err - } - events := ctx.events - ctx.events = make([]event.Event, 0) // reset events - valsetUpdates, err := s.validatorUpdates(ctx) - if err != nil { - return nil, nil, err - } - events = append(events, ctx.events...) - for i := range events { - events[i].BlockNumber = uint64(ctx.headerInfo.Height) - events[i].BlockStage = appdata.EndBlockStage - events[i].EventIndex = int32(i + 1) - } - - return events, valsetUpdates, nil -} - -// validatorUpdates returns the validator updates for the current block. It is called by endBlock after the endblock execution has concluded -func (s STF[T]) validatorUpdates( - ctx *executionContext, -) ([]appmodulev2.ValidatorUpdate, error) { - valSetUpdates, err := s.doValidatorUpdate(ctx) - if err != nil { - return nil, err - } - return valSetUpdates, nil -} - -// Simulate simulates the execution of a tx on the provided state. -func (s STF[T]) Simulate( - ctx context.Context, - state store.ReaderMap, - gasLimit uint64, - tx T, -) (server.TxResult, store.WriterMap) { - simulationState := s.branchFn(state) - hi, err := s.getHeaderInfo(simulationState) - if err != nil { - return server.TxResult{}, nil - } - txr := s.deliverTx(ctx, simulationState, tx, internal.ExecModeSimulate, hi, 0) - - return txr, simulationState -} - -// ValidateTx will run only the validation steps required for a transaction. -// Validations are run over the provided state, with the provided gas limit. -func (s STF[T]) ValidateTx( - ctx context.Context, - state store.ReaderMap, - gasLimit uint64, - tx T, -) server.TxResult { - validationState := s.branchFn(state) - gasUsed, events, err := s.validateTx(ctx, validationState, gasLimit, tx, transaction.ExecModeCheck) - return server.TxResult{ - Events: events, - GasUsed: gasUsed, - Error: err, - } -} - -// Query executes the query on the provided state with the provided gas limits. -func (s STF[T]) Query( - ctx context.Context, - state store.ReaderMap, - gasLimit uint64, - req transaction.Msg, -) (transaction.Msg, error) { - queryState := s.branchFn(state) - hi, err := s.getHeaderInfo(queryState) - if err != nil { - return nil, err - } - queryCtx := s.makeContext(ctx, nil, queryState, internal.ExecModeSimulate) - queryCtx.setHeaderInfo(hi) - queryCtx.setGasLimit(gasLimit) - return s.queryRouter.Invoke(queryCtx, req) -} - -// clone clones STF. -func (s STF[T]) clone() STF[T] { - return STF[T]{ - logger: s.logger, - msgRouter: s.msgRouter, - queryRouter: s.queryRouter, - doPreBlock: s.doPreBlock, - doBeginBlock: s.doBeginBlock, - doEndBlock: s.doEndBlock, - doValidatorUpdate: s.doValidatorUpdate, - doTxValidation: s.doTxValidation, - postTxExec: s.postTxExec, - branchFn: s.branchFn, - makeGasMeter: s.makeGasMeter, - makeGasMeteredState: s.makeGasMeteredState, - } -} - -// executionContext is a struct that holds the context for the execution of a tx. -type executionContext struct { - context.Context - - // unmeteredState is storage without metering. Changes here are propagated to state which is the metered - // version. - unmeteredState store.WriterMap - // state is the gas metered state. - state store.WriterMap - // meter is the gas meter. - meter gas.Meter - // events are the current events. - events []event.Event - // sender is the causer of the state transition. - sender transaction.Identity - // headerInfo contains the block info. - headerInfo header.Info - // execMode retains information about the exec mode. - execMode transaction.ExecMode - - branchFn branchFn - makeGasMeter makeGasMeterFn - makeGasMeteredStore makeGasMeteredStateFn - - msgRouter router.Service - queryRouter router.Service -} - -// setHeaderInfo sets the header info in the state to be used by queries in the future. -func (e *executionContext) setHeaderInfo(hi header.Info) { - e.headerInfo = hi -} - -// setGasLimit will update the gas limit of the *executionContext -func (e *executionContext) setGasLimit(limit uint64) { - meter := e.makeGasMeter(limit) - meteredState := e.makeGasMeteredStore(meter, e.unmeteredState) - - e.meter = meter - e.state = meteredState -} - -func (e *executionContext) Value(key any) any { - if key == executionContextKey { - return e - } - - return e.Context.Value(key) -} - -// TODO: too many calls to makeContext can be expensive -// makeContext creates and returns a new execution context for the STF[T] type. -// It takes in the following parameters: -// - ctx: The context.Context object for the execution. -// - sender: The transaction.Identity object representing the sender of the transaction. -// - state: The store.WriterMap object for accessing and modifying the state. -// - gasLimit: The maximum amount of gas allowed for the execution. -// - execMode: The corecontext.ExecMode object representing the execution mode. -// -// It returns a pointer to the executionContext struct -func (s STF[T]) makeContext( - ctx context.Context, - sender transaction.Identity, - store store.WriterMap, - execMode transaction.ExecMode, -) *executionContext { - valuedCtx := context.WithValue(ctx, corecontext.ExecModeKey, execMode) - return newExecutionContext( - valuedCtx, - s.makeGasMeter, - s.makeGasMeteredState, - s.branchFn, - sender, - store, - execMode, - s.msgRouter, - s.queryRouter, - ) -} - -func newExecutionContext( - ctx context.Context, - makeGasMeterFn makeGasMeterFn, - makeGasMeteredStoreFn makeGasMeteredStateFn, - branchFn branchFn, - sender transaction.Identity, - state store.WriterMap, - execMode transaction.ExecMode, - msgRouter coreRouterImpl, - queryRouter coreRouterImpl, -) *executionContext { - meter := makeGasMeterFn(gas.NoGasLimit) - meteredState := makeGasMeteredStoreFn(meter, state) - - return &executionContext{ - Context: ctx, - unmeteredState: state, - state: meteredState, - meter: meter, - events: make([]event.Event, 0), - headerInfo: header.Info{}, - execMode: execMode, - sender: sender, - branchFn: branchFn, - makeGasMeter: makeGasMeterFn, - makeGasMeteredStore: makeGasMeteredStoreFn, - msgRouter: msgRouter, - queryRouter: queryRouter, - } -} - -// applyStateChanges applies the state changes from the source store to the destination store. -// It retrieves the state changes from the source store using GetStateChanges method, -// and then applies those changes to the destination store using ApplyStateChanges method. -// If an error occurs during the retrieval or application of state changes, it is returned. -func applyStateChanges(dst, src store.WriterMap) error { - changes, err := src.GetStateChanges() - if err != nil { - return err - } - return dst.ApplyStateChanges(changes) -} - -// isCtxCancelled reports if the context was canceled. -func isCtxCancelled(ctx context.Context) error { - select { - case <-ctx.Done(): - return ctx.Err() - default: - return nil - } -} diff --git a/server/v2/stf/stf_test.go b/server/v2/stf/stf_test.go deleted file mode 100644 index 04ae8bf5859b..000000000000 --- a/server/v2/stf/stf_test.go +++ /dev/null @@ -1,510 +0,0 @@ -package stf - -import ( - "context" - "crypto/sha256" - "errors" - "strings" - "testing" - "time" - - gogotypes "github.com/cosmos/gogoproto/types" - - appmodulev2 "cosmossdk.io/core/appmodule/v2" - "cosmossdk.io/core/event" - coregas "cosmossdk.io/core/gas" - "cosmossdk.io/core/server" - "cosmossdk.io/core/store" - "cosmossdk.io/core/transaction" - "cosmossdk.io/schema/appdata" - "cosmossdk.io/server/v2/stf/branch" - "cosmossdk.io/server/v2/stf/gas" - "cosmossdk.io/server/v2/stf/mock" -) - -const senderAddr = "sender" - -func addMsgHandlerToSTF[T any, PT interface { - *T - transaction.Msg -}, - U any, UT interface { - *U - transaction.Msg - }]( - t *testing.T, - stf *STF[mock.Tx], - handler func(ctx context.Context, msg PT) (UT, error), -) { - t.Helper() - msgRouterBuilder := NewMsgRouterBuilder() - err := msgRouterBuilder.RegisterHandler( - msgTypeURL(PT(new(T))), - func(ctx context.Context, msg transaction.Msg) (msgResp transaction.Msg, err error) { - typedReq := msg.(PT) - typedResp, err := handler(ctx, typedReq) - if err != nil { - return nil, err - } - - return typedResp, nil - }, - ) - if err != nil { - t.Errorf("Failed to register handler: %v", err) - } - - msgRouter, err := msgRouterBuilder.build() - if err != nil { - t.Errorf("Failed to build message router: %v", err) - } - stf.msgRouter = msgRouter -} - -func TestSTF(t *testing.T) { - state := mock.DB() - mockTx := mock.Tx{ - Sender: []byte(senderAddr), - Msg: &gogotypes.BoolValue{Value: true}, - GasLimit: 100_000, - } - - sum := sha256.Sum256([]byte("test-hash")) - - s := &STF[mock.Tx]{ - doPreBlock: func(ctx context.Context, txs []mock.Tx) error { - ctx.(*executionContext).events = append(ctx.(*executionContext).events, event.NewEvent("pre-block")) - return nil - }, - doBeginBlock: func(ctx context.Context) error { - kvSet(t, ctx, "begin-block") - ctx.(*executionContext).events = append(ctx.(*executionContext).events, event.NewEvent("begin-block")) - return nil - }, - doEndBlock: func(ctx context.Context) error { - kvSet(t, ctx, "end-block") - ctx.(*executionContext).events = append(ctx.(*executionContext).events, event.NewEvent("end-block")) - return nil - }, - doValidatorUpdate: func(ctx context.Context) ([]appmodulev2.ValidatorUpdate, error) { - ctx.(*executionContext).events = append(ctx.(*executionContext).events, event.NewEvent("validator-update")) - return nil, nil - }, - doTxValidation: func(ctx context.Context, tx mock.Tx) error { - kvSet(t, ctx, "validate") - ctx.(*executionContext).events = append( - ctx.(*executionContext).events, - event.NewEvent("validate-tx", event.NewAttribute(senderAddr, string(tx.Sender))), - event.NewEvent( - "validate-tx", - event.NewAttribute(senderAddr, string(tx.Sender)), - event.NewAttribute("index", "2"), - ), - ) - return nil - }, - postTxExec: func(ctx context.Context, tx mock.Tx, success bool) error { - kvSet(t, ctx, "post-tx-exec") - ctx.(*executionContext).events = append( - ctx.(*executionContext).events, - event.NewEvent("post-tx-exec", event.NewAttribute(senderAddr, string(tx.Sender))), - event.NewEvent( - "post-tx-exec", - event.NewAttribute(senderAddr, string(tx.Sender)), - event.NewAttribute("index", "2"), - ), - ) - return nil - }, - branchFn: branch.DefaultNewWriterMap, - makeGasMeter: gas.DefaultGasMeter, - makeGasMeteredState: gas.DefaultWrapWithGasMeter, - } - - addMsgHandlerToSTF(t, s, func(ctx context.Context, msg *gogotypes.BoolValue) (*gogotypes.BoolValue, error) { - kvSet(t, ctx, "exec") - ctx.(*executionContext).events = append( - ctx.(*executionContext).events, - event.NewEvent("handle-msg", event.NewAttribute("msg", msg.String())), - event.NewEvent( - "handle-msg", - event.NewAttribute("msg", msg.String()), - event.NewAttribute("index", "2"), - ), - ) - return nil, nil - }) - - t.Run("begin and end block", func(t *testing.T) { - _, newState, err := s.DeliverBlock(context.Background(), &server.BlockRequest[mock.Tx]{ - Height: uint64(1), - Time: time.Date(2024, 2, 3, 18, 23, 0, 0, time.UTC), - AppHash: sum[:], - Hash: sum[:], - }, state) - if err != nil { - t.Errorf("DeliverBlock error: %v", err) - } - stateHas(t, newState, "begin-block") - stateHas(t, newState, "end-block") - }) - - t.Run("basic tx", func(t *testing.T) { - result, newState, err := s.DeliverBlock(context.Background(), &server.BlockRequest[mock.Tx]{ - Height: uint64(1), - Time: time.Date(2024, 2, 3, 18, 23, 0, 0, time.UTC), - AppHash: sum[:], - Hash: sum[:], - Txs: []mock.Tx{mockTx}, - }, state) - if err != nil { - t.Errorf("DeliverBlock error: %v", err) - } - stateHas(t, newState, "validate") - stateHas(t, newState, "exec") - stateHas(t, newState, "post-tx-exec") - - if len(result.TxResults) != 1 { - t.Errorf("Expected 1 TxResult, got %d", len(result.TxResults)) - } - txResult := result.TxResults[0] - if txResult.GasUsed == 0 { - t.Errorf("GasUsed should not be zero") - } - if txResult.GasWanted != mockTx.GasLimit { - t.Errorf("Expected GasWanted to be %d, got %d", mockTx.GasLimit, txResult.GasWanted) - } - - // Check PreBlockEvents - preBlockEvents := result.PreBlockEvents - if len(preBlockEvents) != 1 { - t.Fatalf("Expected 1 PreBlockEvent, got %d", len(preBlockEvents)) - } - if preBlockEvents[0].Type != "pre-block" { - t.Errorf("Expected PreBlockEvent Type 'pre-block', got %s", preBlockEvents[0].Type) - } - if preBlockEvents[0].BlockStage != appdata.PreBlockStage { - t.Errorf("Expected PreBlockStage %d, got %d", appdata.PreBlockStage, preBlockEvents[0].BlockStage) - } - if preBlockEvents[0].EventIndex != 1 { - t.Errorf("Expected PreBlockEventIndex 1, got %d", preBlockEvents[0].EventIndex) - } - // Check BeginBlockEvents - beginBlockEvents := result.BeginBlockEvents - if len(beginBlockEvents) != 1 { - t.Fatalf("Expected 1 BeginBlockEvent, got %d", len(beginBlockEvents)) - } - if beginBlockEvents[0].Type != "begin-block" { - t.Errorf("Expected BeginBlockEvent Type 'begin-block', got %s", beginBlockEvents[0].Type) - } - if beginBlockEvents[0].BlockStage != appdata.BeginBlockStage { - t.Errorf("Expected BeginBlockStage %d, got %d", appdata.BeginBlockStage, beginBlockEvents[0].BlockStage) - } - if beginBlockEvents[0].EventIndex != 1 { - t.Errorf("Expected BeginBlockEventIndex 1, got %d", beginBlockEvents[0].EventIndex) - } - // Check EndBlockEvents - endBlockEvents := result.EndBlockEvents - if len(endBlockEvents) != 2 { - t.Fatalf("Expected 2 EndBlockEvents, got %d", len(endBlockEvents)) - } - if endBlockEvents[0].Type != "end-block" { - t.Errorf("Expected EndBlockEvent Type 'end-block', got %s", endBlockEvents[0].Type) - } - if endBlockEvents[1].Type != "validator-update" { - t.Errorf("Expected EndBlockEvent Type 'validator-update', got %s", endBlockEvents[1].Type) - } - if endBlockEvents[1].BlockStage != appdata.EndBlockStage { - t.Errorf("Expected EndBlockStage %d, got %d", appdata.EndBlockStage, endBlockEvents[1].BlockStage) - } - if endBlockEvents[0].EventIndex != 1 { - t.Errorf("Expected EndBlockEventIndex 1, got %d", endBlockEvents[0].EventIndex) - } - if endBlockEvents[1].EventIndex != 2 { - t.Errorf("Expected EndBlockEventIndex 2, got %d", endBlockEvents[1].EventIndex) - } - // check TxEvents - events := txResult.Events - if len(events) != 7 { - t.Fatalf("Expected 7 TxEvents, got %d", len(events)) - } - for i, event := range events { - if event.BlockStage != appdata.TxProcessingStage { - t.Errorf("Expected BlockStage %d, got %d", appdata.TxProcessingStage, event.BlockStage) - } - if event.TxIndex != 1 { - t.Errorf("Expected TxIndex 1, got %d", event.TxIndex) - } - if event.EventIndex != int32(i%2+1) && - (event.Type == "message" && event.EventIndex != 3) { // special case for message event type as it happens in the msg handling flow - t.Errorf("Expected EventIndex %d, got %d", i%2+1, event.EventIndex) - } - - attrs, err := event.Attributes() - if err != nil { - t.Fatalf("Error getting event attributes: %v", err) - } - if len(attrs) < 1 || len(attrs) > 2 { - t.Errorf("Expected 1 or 2 attributes, got %d", len(attrs)) - } - - if len(attrs) == 2 && event.Type != "message" { - if attrs[1].Key != "index" || attrs[1].Value != "2" { - t.Errorf("Expected attribute key 'index' and value '2', got key '%s' and value '%s'", attrs[1].Key, attrs[1].Value) - } - } - switch i { - case 0, 1: - if event.Type != "validate-tx" { - t.Errorf("Expected event type 'validate-tx', got %s", event.Type) - } - if event.MsgIndex != 0 { - t.Errorf("Expected MsgIndex 0, got %d", event.MsgIndex) - } - if attrs[0].Key != senderAddr || attrs[0].Value != senderAddr { - t.Errorf("Expected sender attribute key 'sender' and value 'sender', got key '%s' and value '%s'", attrs[0].Key, attrs[0].Value) - } - case 2, 3: - if event.Type != "handle-msg" { - t.Errorf("Expected event type 'handle-msg', got %s", event.Type) - } - if event.MsgIndex != 1 { - t.Errorf("Expected MsgIndex 1, got %d", event.MsgIndex) - } - if attrs[0].Key != "msg" || attrs[0].Value != "&BoolValue{Value:true,XXX_unrecognized:[],}" { - t.Errorf("Expected msg attribute with value '&BoolValue{Value:true,XXX_unrecognized:[],}', got '%s'", attrs[0].Value) - } - case 4: - if event.Type != "message" { - t.Errorf("Expected event type 'message', got %s", event.Type) - } - - if event.MsgIndex != 1 { - t.Errorf("Expected MsgIndex 1, got %d", event.MsgIndex) - } - - if attrs[0].Key != "action" || attrs[0].Value != "/google.protobuf.BoolValue" { - t.Errorf("Expected msg attribute with value '/google.protobuf.BoolValue', got '%s'", attrs[0].Value) - } - case 5, 6: - if event.Type != "post-tx-exec" { - t.Errorf("Expected event type 'post-tx-exec', got %s", event.Type) - } - if event.MsgIndex != -1 { - t.Errorf("Expected MsgIndex -1, got %d", event.MsgIndex) - } - if attrs[0].Key != senderAddr || attrs[0].Value != senderAddr { - t.Errorf("Expected sender attribute key 'sender' and value 'sender', got key '%s' and value '%s'", attrs[0].Key, attrs[0].Value) - } - } - } - }) - - t.Run("exec tx out of gas", func(t *testing.T) { - s := s.clone() - - mockTx := mock.Tx{ - Sender: []byte(senderAddr), - Msg: &gogotypes.BoolValue{Value: true}, // msg does not matter at all because our handler does nothing. - GasLimit: 0, // NO GAS! - } - - // this handler will propagate the storage error back, we expect - // out of gas immediately at tx validation level. - s.doTxValidation = func(ctx context.Context, tx mock.Tx) error { - w, err := ctx.(*executionContext).state.GetWriter(actorName) - if err != nil { - t.Errorf("GetWriter error: %v", err) - } - err = w.Set([]byte("gas_failure"), []byte{}) - if err == nil { - t.Error("Expected error, got nil") - } - return err - } - - result, newState, err := s.DeliverBlock(context.Background(), &server.BlockRequest[mock.Tx]{ - Height: uint64(1), - Time: time.Date(2024, 2, 3, 18, 23, 0, 0, time.UTC), - AppHash: sum[:], - Hash: sum[:], - Txs: []mock.Tx{mockTx}, - }, state) - if err != nil { - t.Errorf("DeliverBlock error: %v", err) - } - stateNotHas(t, newState, "gas_failure") // assert during out of gas no state changes leaked. - if !errors.Is(result.TxResults[0].Error, coregas.ErrOutOfGas) { - t.Errorf("Expected ErrOutOfGas, got %v", result.TxResults[0].Error) - } - }) - - t.Run("fail exec tx", func(t *testing.T) { - // update the stf to fail on the handler - s := s.clone() - addMsgHandlerToSTF(t, &s, func(ctx context.Context, msg *gogotypes.BoolValue) (*gogotypes.BoolValue, error) { - return nil, errors.New("failure") - }) - - blockResult, newState, err := s.DeliverBlock(context.Background(), &server.BlockRequest[mock.Tx]{ - Height: uint64(1), - Time: time.Date(2024, 2, 3, 18, 23, 0, 0, time.UTC), - AppHash: sum[:], - Hash: sum[:], - Txs: []mock.Tx{mockTx}, - }, state) - if err != nil { - t.Errorf("DeliverBlock error: %v", err) - } - if !strings.Contains(blockResult.TxResults[0].Error.Error(), "failure") { - t.Errorf("Expected error to contain 'failure', got %v", blockResult.TxResults[0].Error) - } - stateHas(t, newState, "begin-block") - stateHas(t, newState, "end-block") - stateHas(t, newState, "validate") - stateNotHas(t, newState, "exec") - stateHas(t, newState, "post-tx-exec") - }) - - t.Run("tx is success but post tx failed", func(t *testing.T) { - s := s.clone() - s.postTxExec = func(ctx context.Context, tx mock.Tx, success bool) error { - return errors.New("post tx failure") - } - blockResult, newState, err := s.DeliverBlock(context.Background(), &server.BlockRequest[mock.Tx]{ - Height: uint64(1), - Time: time.Date(2024, 2, 3, 18, 23, 0, 0, time.UTC), - AppHash: sum[:], - Hash: sum[:], - Txs: []mock.Tx{mockTx}, - }, state) - if err != nil { - t.Errorf("DeliverBlock error: %v", err) - } - if !strings.Contains(blockResult.TxResults[0].Error.Error(), "post tx failure") { - t.Errorf("Expected error to contain 'post tx failure', got %v", blockResult.TxResults[0].Error) - } - stateHas(t, newState, "begin-block") - stateHas(t, newState, "end-block") - stateHas(t, newState, "validate") - stateNotHas(t, newState, "exec") - stateNotHas(t, newState, "post-tx-exec") - }) - - t.Run("tx failed and post tx failed", func(t *testing.T) { - s := s.clone() - addMsgHandlerToSTF(t, &s, func(ctx context.Context, msg *gogotypes.BoolValue) (*gogotypes.BoolValue, error) { - return nil, errors.New("exec failure") - }) - s.postTxExec = func(ctx context.Context, tx mock.Tx, success bool) error { return errors.New("post tx failure") } - blockResult, newState, err := s.DeliverBlock(context.Background(), &server.BlockRequest[mock.Tx]{ - Height: uint64(1), - Time: time.Date(2024, 2, 3, 18, 23, 0, 0, time.UTC), - AppHash: sum[:], - Hash: sum[:], - Txs: []mock.Tx{mockTx}, - }, state) - if err != nil { - t.Errorf("DeliverBlock error: %v", err) - } - if !strings.Contains(blockResult.TxResults[0].Error.Error(), "exec failure\npost tx failure") { - t.Errorf("Expected error to contain 'exec failure\npost tx failure', got %v", blockResult.TxResults[0].Error) - } - stateHas(t, newState, "begin-block") - stateHas(t, newState, "end-block") - stateHas(t, newState, "validate") - stateNotHas(t, newState, "exec") - stateNotHas(t, newState, "post-tx-exec") - }) - - t.Run("fail validate tx", func(t *testing.T) { - // update stf to fail on the validation step - s := s.clone() - s.doTxValidation = func(ctx context.Context, tx mock.Tx) error { return errors.New("failure") } - blockResult, newState, err := s.DeliverBlock(context.Background(), &server.BlockRequest[mock.Tx]{ - Height: uint64(1), - Time: time.Date(2024, 2, 3, 18, 23, 0, 0, time.UTC), - AppHash: sum[:], - Hash: sum[:], - Txs: []mock.Tx{mockTx}, - }, state) - if err != nil { - t.Errorf("DeliverBlock error: %v", err) - } - if !strings.Contains(blockResult.TxResults[0].Error.Error(), "failure") { - t.Errorf("Expected error to contain 'failure', got %v", blockResult.TxResults[0].Error) - } - stateHas(t, newState, "begin-block") - stateHas(t, newState, "end-block") - stateNotHas(t, newState, "validate") - stateNotHas(t, newState, "exec") - }) - - t.Run("test validate tx with exec mode", func(t *testing.T) { - // update stf to fail on the validation step - s := s.clone() - s.doTxValidation = func(ctx context.Context, tx mock.Tx) error { - if ctx.(*executionContext).execMode == transaction.ExecModeCheck { - return errors.New("failure") - } - return nil - } - // test ValidateTx as it validates with check execMode - res := s.ValidateTx(context.Background(), state, mockTx.GasLimit, mockTx) - if res.Error == nil { - t.Error("Expected error, got nil") - } - - // test validate tx with exec mode as finalize - _, _, err := s.validateTx(context.Background(), s.branchFn(state), mockTx.GasLimit, - mockTx, transaction.ExecModeFinalize) - if err != nil { - t.Errorf("validateTx error: %v", err) - } - }) -} - -var actorName = []byte("cookies") - -func kvSet(t *testing.T, ctx context.Context, v string) { - t.Helper() - state, err := ctx.(*executionContext).state.GetWriter(actorName) - if err != nil { - t.Errorf("Set error: %v", err) - } else { - err = state.Set([]byte(v), []byte(v)) - if err != nil { - t.Errorf("Set error: %v", err) - } - } -} - -func stateHas(t *testing.T, accountState store.ReaderMap, key string) { - t.Helper() - state, err := accountState.GetReader(actorName) - if err != nil { - t.Errorf("GetReader error: %v", err) - } - has, err := state.Has([]byte(key)) - if err != nil { - t.Errorf("Has error: %v", err) - } - if !has { - t.Errorf("State did not have key: %s", key) - } -} - -func stateNotHas(t *testing.T, accountState store.ReaderMap, key string) { - t.Helper() - state, err := accountState.GetReader(actorName) - if err != nil { - t.Errorf("GetReader error: %v", err) - } - has, err := state.Has([]byte(key)) - if err != nil { - t.Errorf("Has error: %v", err) - } - if has { - t.Errorf("State was not supposed to have key: %s", key) - } -} diff --git a/simapp/v2/go.mod b/simapp/v2/go.mod index 8a64dbb3437b..775add7d00e4 100644 --- a/simapp/v2/go.mod +++ b/simapp/v2/go.mod @@ -61,7 +61,7 @@ require ( cosmossdk.io/errors/v2 v2.0.0-20240731132947-df72853b3ca5 // indirect cosmossdk.io/schema v0.4.0 // indirect cosmossdk.io/server/v2/appmanager v0.0.0-20241203212527-7d117425d880 // indirect; main - cosmossdk.io/server/v2/stf v0.0.0-20241204101618-7fa2356c07aa // indirect; main + cosmossdk.io/server/v2/stf v0.0.0-20241212104257-e6948eeda877 // indirect; main cosmossdk.io/store v1.1.1-0.20240909133312-50288938d1b6 // indirect; main cosmossdk.io/x/tx v1.0.0-alpha.2 // indirect; main filippo.io/edwards25519 v1.1.0 // indirect diff --git a/simapp/v2/go.sum b/simapp/v2/go.sum index 6c034827a28b..c0290fca6677 100644 --- a/simapp/v2/go.sum +++ b/simapp/v2/go.sum @@ -221,8 +221,8 @@ cosmossdk.io/server/v2 v2.0.0-20241211154953-a38a6a2c8bc8 h1:Z1tRewzCemRc4iwKPFG cosmossdk.io/server/v2 v2.0.0-20241211154953-a38a6a2c8bc8/go.mod h1:RAectNg/rAaq0AHOuxbxY2YVTYTVBJCTVg5wHpCIZhE= cosmossdk.io/server/v2/appmanager v0.0.0-20241203212527-7d117425d880 h1:0mtB8fSvDjD835WwWF4rGk9qy5TjVjk2jsW14L37v0E= cosmossdk.io/server/v2/appmanager v0.0.0-20241203212527-7d117425d880/go.mod h1:elhlrldWtm+9U4PxE0G3wjz83yQwVVGVAOncXJPY1Xc= -cosmossdk.io/server/v2/stf v0.0.0-20241204101618-7fa2356c07aa h1:2V9nqgL50nw45HcQw1UBRQ/y0QBzrgfLIStPSxFnMtY= -cosmossdk.io/server/v2/stf v0.0.0-20241204101618-7fa2356c07aa/go.mod h1:4e9SzLyeGptQ3tSR6nKCNwCu7Ye4uUS2WIJih29dG2c= +cosmossdk.io/server/v2/stf v0.0.0-20241212104257-e6948eeda877 h1:9NTR/GHLFkKOZEBqLs3fMaVKUfsEfuboj+JUcq0PmWI= +cosmossdk.io/server/v2/stf v0.0.0-20241212104257-e6948eeda877/go.mod h1:XDY0jKCQwtK0NACzQ6BKepv+2GWbHuYsjLQhxl+itpY= cosmossdk.io/store v1.0.0-rc.0.0.20241204123127-eb3bf8b0469d h1:KQM4Q6kjwlM4HuDZRV8/ZDXX3whjfStndYNTsRrbboQ= cosmossdk.io/store v1.0.0-rc.0.0.20241204123127-eb3bf8b0469d/go.mod h1:oZBBY4BrkYnghr6MFL0MP5mGqpkPedHcWkXwXddd6tU= cosmossdk.io/store/v2 v2.0.0-20241209145349-34f407d6367a h1:v300rqUJV1QuNhCcMlhzwgOEWATIknxv8qqwxNfDv0E=