diff --git a/baseapp/abci.go b/baseapp/abci.go index a266c94e..c8495b7b 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -1228,3 +1228,47 @@ func (app *BaseApp) LoadLatest(ctx context.Context, req *abci.RequestLoadLatest) app.initialHeight = app.cms.LastCommitID().Version return &abci.ResponseLoadLatest{}, nil } + +func (app *BaseApp) GetTxPriorityHint(_ context.Context, req *abci.RequestGetTxPriorityHint) (_resp *abci.ResponseGetTxPriorityHint, _err error) { + defer func() { + if r := recover(); r != nil { + // Fall back to no-op priority if we panic for any reason. This is to avoid DoS + // vectors where a malicious actor crafts a transaction that panics the + // prioritizer. Since the prioritizer is used as a hint only, it's safe to fall + // back to zero priority in this case and log the panic for monitoring purposes. + app.logger.Error("tx prioritizer base app panicked. Falling back on no priority", "error", r) + if _err == nil { + _resp = &abci.ResponseGetTxPriorityHint{Priority: 0} + } + // Do not overwrite an existing error if one was already set to keep panics a + // non-event at this stage but safeguard against them. + } + }() + + defer telemetry.MeasureSince(time.Now(), "abci", "get_tx_priority_hint") + + tx, err := app.txDecoder(req.Tx) + if err != nil { + return nil, err + } + if tx == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "nil tx") + } + + // TODO: should we bother validating the messages here? + msgs := tx.GetMsgs() + if err := validateBasicTxMsgs(msgs); err != nil { + return nil, err + } + var priority int64 + if app.txPrioritizer != nil { + sdkCtx := app.getContextForTx(runTxModeCheck, req.Tx) + priority, err = app.txPrioritizer(sdkCtx, tx) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrLogic, fmt.Sprintf("error getting tx priority: %s", err.Error())) + } + } + return &abci.ResponseGetTxPriorityHint{ + Priority: priority, + }, nil +} diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index c0a67985..fad6e2f4 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -97,6 +97,7 @@ type BaseApp struct { //nolint: maligned preCommitHandler sdk.PreCommitHandler closeHandler sdk.CloseHandler inplaceTestnetInitializer sdk.InplaceTestnetInitializer + txPrioritizer sdk.TxPrioritizer appStore baseappVersions diff --git a/baseapp/options.go b/baseapp/options.go index 4d2747fd..988a7129 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -379,3 +379,9 @@ func (app *BaseApp) SetQueryMultiStore(ms sdk.CommitMultiStore) { func (app *BaseApp) SetMigrationHeight(height int64) { app.migrationHeight = height } + +// SetTxPrioritizer sets the transaction prioritizer for the BaseApp. If unset, +// calls to GetTxPriorityHint for all valid transactions will return 0. +func (app *BaseApp) SetTxPrioritizer(prioritizer sdk.TxPrioritizer) { + app.txPrioritizer = prioritizer +} diff --git a/go.mod b/go.mod index 6a50c090..6095ec9b 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -go 1.21 +go 1.24.5 module github.com/cosmos/cosmos-sdk @@ -134,6 +134,7 @@ require ( github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mtibben/percent v0.2.1 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20210609091139-0a56a4bca00b // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.7 // indirect github.com/petermattis/goid v0.0.0-20230317030725-371a4b8eda08 // indirect @@ -196,7 +197,9 @@ replace ( github.com/sei-protocol/sei-db => github.com/sei-protocol/sei-db v0.0.51 // Latest goleveldb is broken, we have to stick to this version github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 - github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.6.0 + //// TODO: To be replaced with a concrete version number. See: + //// - https://github.com/sei-protocol/sei-tendermint/pull/301 + github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.6.4-0.20250905203244-31c1f39f5fce // latest grpc doesn't work with with our modified proto compiler, so we need to enforce // the following version across all dependencies. google.golang.org/grpc => google.golang.org/grpc v1.33.2 diff --git a/go.sum b/go.sum index 4e499bef..1d130848 100644 --- a/go.sum +++ b/go.sum @@ -843,6 +843,8 @@ github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIw github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= @@ -966,8 +968,8 @@ github.com/sei-protocol/sei-db v0.0.51 h1:jK6Ps+jDbGdWIPZttaWk7VIsq8aLWWlkTp9axI github.com/sei-protocol/sei-db v0.0.51/go.mod h1:m5g7p0QeAS3dNJHIl28zQpzOgxQmvYqPb7t4hwgIOCA= github.com/sei-protocol/sei-iavl v0.1.9 h1:y4mVYftxLNRs6533zl7N0/Ch+CzRQc04JDfHolIxgBE= github.com/sei-protocol/sei-iavl v0.1.9/go.mod h1:7PfkEVT5dcoQE+s/9KWdoXJ8VVVP1QpYYPLdxlkSXFk= -github.com/sei-protocol/sei-tendermint v0.6.0 h1:H/qN54IUUnqMKbqL9rVt61ViIgPzpHixtKd43LV+C6I= -github.com/sei-protocol/sei-tendermint v0.6.0/go.mod h1:hLgRpS2d6VM8XzlhEtFeosCYkpuviU2ztqmOairIivc= +github.com/sei-protocol/sei-tendermint v0.6.4-0.20250905203244-31c1f39f5fce h1:sxqrZQgEzjs/8MofaB5g/IfpffK42xk2BymJq/irh50= +github.com/sei-protocol/sei-tendermint v0.6.4-0.20250905203244-31c1f39f5fce/go.mod h1:SSZv0P1NBP/4uB3gZr5XJIan3ks3Ui8FJJzIap4r6uc= github.com/sei-protocol/sei-tm-db v0.0.5 h1:3WONKdSXEqdZZeLuWYfK5hP37TJpfaUa13vAyAlvaQY= github.com/sei-protocol/sei-tm-db v0.0.5/go.mod h1:Cpa6rGyczgthq7/0pI31jys2Fw0Nfrc+/jKdP1prVqY= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= diff --git a/types/abci.go b/types/abci.go index 6921d45e..3d056cd2 100644 --- a/types/abci.go +++ b/types/abci.go @@ -37,3 +37,5 @@ type LoadVersionHandler func() error type PreCommitHandler func(ctx Context) error type CloseHandler func() error type InplaceTestnetInitializer func(cryptotypes.PubKey) error + +type TxPrioritizer func(Context, Tx) (int64, error) diff --git a/types/context.go b/types/context.go index 930994f2..d0bb009b 100644 --- a/types/context.go +++ b/types/context.go @@ -46,6 +46,7 @@ type Context struct { eventManager *EventManager evmEventManager *EVMEventManager priority int64 // The tx priority, only relevant in CheckTx + hasPriority bool // Whether the tx has a priority set pendingTxChecker abci.PendingTxChecker // Checker for pending transaction, only relevant in CheckTx checkTxCallback func(Context, error) // callback to make at the end of CheckTx. Input param is the error (nil-able) of `runMsgs` deliverTxCallback func(Context) // callback to make at the end of DeliverTx. @@ -241,12 +242,19 @@ func (c Context) StoreTracer() gaskv.IStoreTracer { return c.storeTracer } -// WithEventManager returns a Context with an updated tx priority +// WithPriority returns a Context with an updated tx priority. func (c Context) WithPriority(p int64) Context { c.priority = p + c.hasPriority = true return c } +// HasPriority returns true iff the priority is set for this Context even if it +// was set to zero. +func (c Context) HasPriority() bool { + return c.hasPriority +} + // HeaderHash returns a copy of the header hash obtained during abci.RequestBeginBlock func (c Context) HeaderHash() tmbytes.HexBytes { hash := make([]byte, len(c.headerHash)) diff --git a/types/context_test.go b/types/context_test.go index 1dc062ac..ad6156f8 100644 --- a/types/context_test.go +++ b/types/context_test.go @@ -7,6 +7,7 @@ import ( "time" "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" abci "github.com/tendermint/tendermint/abci/types" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" @@ -226,3 +227,32 @@ func (s *contextTestSuite) TestUnwrapSDKContext() { ctx = context.Background() s.Require().Panics(func() { types.UnwrapSDKContext(ctx) }) } + +func TestContext_Priority(t *testing.T) { + var ( + requireNoPriority = func(t *testing.T, ctx types.Context) { + require.Zero(t, ctx.Priority()) + require.False(t, ctx.HasPriority()) + } + requirePriority = func(t *testing.T, ctx types.Context, priority int64) { + require.Equal(t, priority, ctx.Priority()) + require.True(t, ctx.HasPriority()) + } + ) + + // Assert that a new context has no priority set and does not have priority. + var subject types.Context + requireNoPriority(t, subject) + + // Assert that setting a priority sets the priority and marks the context as + // having priority set. But does not change the original context. + prioritisedSubject := subject.WithPriority(100) + requirePriority(t, prioritisedSubject, 100) + requireNoPriority(t, subject) + + // Assert that setting priority to 0 updates the priority but still marks the + // context as having priority set. But does not change the original context. + deprioritisedSubject := subject.WithPriority(0) + requirePriority(t, deprioritisedSubject, 0) + requireNoPriority(t, subject) +}