Skip to content

refactor: unordered transactions ante handling #24573

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 29 commits into from
Apr 28, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
10e7310
make unordered nonce checking part of sigverify
technicallyty Apr 24, 2025
c7c9a08
fix tests and remove unordered tx manager thing
technicallyty Apr 25, 2025
ef9a516
upgrade docs, fix apps
technicallyty Apr 25, 2025
971f549
fix test
technicallyty Apr 25, 2025
9ead09b
lint fix
technicallyty Apr 25, 2025
b3be99c
add test for increment sequence
technicallyty Apr 25, 2025
3f25119
comments
technicallyty Apr 25, 2025
39e0768
Merge branch 'main' into technicallyty/SDK-314/unordered-tx-ante-refa…
technicallyty Apr 25, 2025
409fa4f
spelling
technicallyty Apr 25, 2025
1966dbb
rabbit feedback
technicallyty Apr 25, 2025
a81f333
Merge branch 'technicallyty/SDK-314/unordered-tx-ante-refactor' of ss…
technicallyty Apr 25, 2025
cfffb44
remove unused test utils
technicallyty Apr 25, 2025
2fc58be
group vars
technicallyty Apr 25, 2025
14e6284
slim down test copying
technicallyty Apr 25, 2025
1298d61
app di options
technicallyty Apr 25, 2025
d065d69
fix
technicallyty Apr 25, 2025
ce19112
update
technicallyty Apr 25, 2025
ca2e35c
docs and rename
technicallyty Apr 25, 2025
fca3b39
snip -> other decorators
technicallyty Apr 25, 2025
00164ef
rename option
technicallyty Apr 25, 2025
027a5b3
Merge branch 'main' into technicallyty/SDK-314/unordered-tx-ante-refa…
technicallyty Apr 25, 2025
a82c6b0
use module config rather than supplied option
technicallyty Apr 27, 2025
f731600
update api replacements
technicallyty Apr 27, 2025
91d91c5
Update proto/cosmos/auth/module/v1/module.proto
technicallyty Apr 28, 2025
925cb67
rename IsUnorderedTransactionsEnabled
technicallyty Apr 28, 2025
601bc79
Merge branch 'main' into technicallyty/SDK-314/unordered-tx-ante-refa…
technicallyty Apr 28, 2025
ea2ecdb
make mocks
technicallyty Apr 28, 2025
f36f46f
make the test a suite
technicallyty Apr 28, 2025
2cc55cd
mark test as helper
technicallyty Apr 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 1 addition & 5 deletions simapp/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,9 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler, options.SigVerifyOptions...),
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
}

if options.UnorderedNonceManager != nil {
anteDecorators = append(anteDecorators, ante.NewUnorderedTxDecorator(options.UnorderedNonceManager, options.UnorderedTxOptions...))
}

return sdk.ChainAnteDecorators(anteDecorators...), nil
}
10 changes: 6 additions & 4 deletions simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ package simapp
import (
"encoding/json"
"fmt"
"io"
"maps"

abci "github.com/cometbft/cometbft/abci/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/cosmos/gogoproto/proto"
"github.com/spf13/cast"
"io"
"maps"

autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1"
Expand Down Expand Up @@ -306,6 +307,7 @@ func NewSimApp(
authcodec.NewBech32Codec(sdk.Bech32MainPrefix),
sdk.Bech32MainPrefix,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
authkeeper.WithUnorderedTransactions(true),
)

app.BankKeeper = bankkeeper.NewBaseKeeper(
Expand Down Expand Up @@ -467,7 +469,7 @@ func NewSimApp(

app.GovKeeper = *govKeeper.SetHooks(
govtypes.NewMultiGovHooks(
// register the governance hooks
// register the governance hooks
),
)

Expand Down Expand Up @@ -497,7 +499,7 @@ func NewSimApp(

app.EpochsKeeper.SetHooks(
epochstypes.NewMultiEpochHooks(
// insert epoch hooks receivers here
// insert epoch hooks receivers here
),
)

Expand Down
14 changes: 5 additions & 9 deletions x/auth/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ type HandlerOptions struct {
SignModeHandler *txsigning.HandlerMap
SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params types.Params) error
TxFeeChecker TxFeeChecker
// UnorderedNonceManager is an opt-in feature for x/auth.
// When set, this application will be able to receive and process unordered transactions.
UnorderedNonceManager UnorderedNonceManager
UnorderedTxOptions []UnorderedTxDecoratorOptions
// SigVerifyOptions are the options for the signature verification decorator.
// This allows for modification of signature verification behavior, such as how long an unordered transaction can
// be valid, or how much gas to charge for unordered transactions.
SigVerifyOptions []SigVerificationDecoratorOption
}

// NewAnteHandler returns an AnteHandler that checks and increments sequence
Expand Down Expand Up @@ -53,13 +53,9 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
NewValidateSigCountDecorator(options.AccountKeeper),
NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler, options.SigVerifyOptions...),
NewIncrementSequenceDecorator(options.AccountKeeper),
}

if options.UnorderedNonceManager != nil {
anteDecorators = append(anteDecorators, NewUnorderedTxDecorator(options.UnorderedNonceManager, options.UnorderedTxOptions...))
}

return sdk.ChainAnteDecorators(anteDecorators...), nil
}
1 change: 1 addition & 0 deletions x/auth/ante/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type AccountKeeper interface {
SetAccount(ctx context.Context, acc sdk.AccountI)
GetModuleAddress(moduleName string) sdk.AccAddress
AddressCodec() address.Codec
IsUnorderedTransactionsEnabled() bool
}

// UnorderedNonceManager defines the contract needed for UnorderedNonce management.
Expand Down
124 changes: 117 additions & 7 deletions x/auth/ante/sigverify.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/base64"
"encoding/hex"
"fmt"
"time"

"google.golang.org/protobuf/types/known/anypb"

Expand Down Expand Up @@ -218,17 +219,51 @@ func (sgcd SigGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
// CONTRACT: Pubkeys are set in context for all signers before this decorator runs
// CONTRACT: Tx must implement SigVerifiableTx interface
type SigVerificationDecorator struct {
ak AccountKeeper
signModeHandler *txsigning.HandlerMap
ak AccountKeeper
signModeHandler *txsigning.HandlerMap
maxTxTimeoutDuration time.Duration
unorderedTxGasCost uint64
unorderedTxManager UnorderedNonceManager
}

func NewSigVerificationDecorator(ak AccountKeeper, signModeHandler *txsigning.HandlerMap) SigVerificationDecorator {
return SigVerificationDecorator{
ak: ak,
signModeHandler: signModeHandler,
type SigVerificationDecoratorOption func(svd *SigVerificationDecorator)

func WithMaxTxTimeoutDuration(duration time.Duration) SigVerificationDecoratorOption {
return func(svd *SigVerificationDecorator) {
svd.maxTxTimeoutDuration = duration
}
}

func WithUnorderedTxGasCost(gasCost uint64) SigVerificationDecoratorOption {
return func(svd *SigVerificationDecorator) {
svd.unorderedTxGasCost = gasCost
}
}

const (
// DefaultMaxTimoutDuration defines a default maximum TTL a transaction can define.
DefaultMaxTimoutDuration = 10 * time.Minute
// DefaultUnorderedTxGasCost defines a default gas cost for unordered transactions.
// We must charge extra gas for unordered transactions
// as they incur extra processing time for cleaning up the expired txs in x/auth PreBlocker.
// Note: this value was chosen by 2x-ing the cost of fetching and removing an unordered nonce entry.
DefaultUnorderedTxGasCost = uint64(2240)
)

func NewSigVerificationDecorator(ak AccountKeeper, signModeHandler *txsigning.HandlerMap, opts ...SigVerificationDecoratorOption) SigVerificationDecorator {
svd := SigVerificationDecorator{
ak: ak,
signModeHandler: signModeHandler,
unorderedTxManager: ak.(UnorderedNonceManager),
}

for _, opt := range opts {
opt(&svd)
}

return svd
}

// OnlyLegacyAminoSigners checks SignatureData to see if all
// signers are using SIGN_MODE_LEGACY_AMINO_JSON. If this is the case
// then the corresponding SignatureV2 struct will not have account sequence
Expand Down Expand Up @@ -258,6 +293,11 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul

utx, ok := tx.(sdk.TxWithUnordered)
isUnordered := ok && utx.GetUnordered()
unorderedEnabled := svd.ak.IsUnorderedTransactionsEnabled()

if isUnordered && !unorderedEnabled {
return ctx, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "unordered transactions are disabled")
}

// stdSigs contains the sequence number, account number, and signatures.
// When simulating, this would just be a 0-length slice.
Expand Down Expand Up @@ -289,7 +329,11 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
}

// Check account sequence number.
if !isUnordered {
if isUnordered {
if err := svd.verifyUnorderedSequence(ctx, utx); err != nil {
return ctx, err
}
} else {
if sig.Sequence != acc.GetSequence() {
return ctx, errorsmod.Wrapf(
sdkerrors.ErrWrongSequence,
Expand Down Expand Up @@ -344,6 +388,69 @@ func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
return next(ctx, tx, simulate)
}

// verifyUnorderedSequence verifies the unordered nonce of an unordered transaction.
// This checks that:
// 1. The unordered transaction's timeout timestamp is set.
// 2. The unordered transaction's timeout timestamp is not in the past.
// 3. The unordered transaction's timeout timestamp is not more than the max TTL.
// 4. The unordered transaction's nonce has not been used previously.
//
// If all the checks above pass, the nonce is marked as used for each signer of the transaction.
func (svd SigVerificationDecorator) verifyUnorderedSequence(ctx sdk.Context, unorderedTx sdk.TxWithUnordered) error {
blockTime := ctx.BlockTime()
timeoutTimestamp := unorderedTx.GetTimeoutTimeStamp()
if timeoutTimestamp.IsZero() || timeoutTimestamp.Unix() == 0 {
return errorsmod.Wrap(
sdkerrors.ErrInvalidRequest,
"unordered transaction must have timeout_timestamp set",
)
}
if timeoutTimestamp.Before(blockTime) {
return errorsmod.Wrap(
sdkerrors.ErrInvalidRequest,
"unordered transaction has a timeout_timestamp that has already passed",
)
}
if timeoutTimestamp.After(blockTime.Add(svd.maxTxTimeoutDuration)) {
return errorsmod.Wrapf(
sdkerrors.ErrInvalidRequest,
"unordered tx ttl exceeds %s",
svd.maxTxTimeoutDuration.String(),
)
}

ctx.GasMeter().ConsumeGas(svd.unorderedTxGasCost, "unordered tx")

execMode := ctx.ExecMode()
if execMode == sdk.ExecModeSimulate {
return nil
}

signerAddrs, err := extractSignersBytes(unorderedTx)
if err != nil {
return err
}

for _, signerAddr := range signerAddrs {
if err := svd.unorderedTxManager.TryAddUnorderedNonce(ctx, signerAddr, unorderedTx.GetTimeoutTimeStamp()); err != nil {
return errorsmod.Wrapf(
sdkerrors.ErrInvalidRequest,
"failed to add unordered nonce: %s", err,
)
}
}

return nil
}

func extractSignersBytes(tx sdk.Tx) ([][]byte, error) {
sigTx, ok := tx.(authsigning.SigVerifiableTx)
if !ok {
return nil, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid tx type")
}
return sigTx.GetSigners()
}

// IncrementSequenceDecorator handles incrementing sequences of all signers.
// Use the IncrementSequenceDecorator decorator to prevent replay attacks. Note,
// there is need to execute IncrementSequenceDecorator on RecheckTx since
Expand All @@ -365,6 +472,9 @@ func NewIncrementSequenceDecorator(ak AccountKeeper) IncrementSequenceDecorator

func (isd IncrementSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
if utx, ok := tx.(sdk.TxWithUnordered); ok && utx.GetUnordered() {
if !isd.ak.IsUnorderedTransactionsEnabled() {
return ctx, errorsmod.Wrap(sdkerrors.ErrUnauthorized, "unordered transactions are disabled")
}
return next(ctx, tx, simulate)
}
sigTx, ok := tx.(authsigning.SigVerifiableTx)
Expand Down
Loading
Loading