-
Notifications
You must be signed in to change notification settings - Fork 95
Description
Problem
The AnteHandler rejects a valid transaction whose nonce is higher than the committed state’s nonce but lower than the nonce of a transaction already present in the mempool.
Problem Details
In Ethereum’s mempool, a transaction (TX1
) with the same nonce as an existing transaction (TX0
) is allowed.
If TX1
has a higher priority, it replaces TX0
.
However, in cosmos/evm, the AnteHandler rejects TX1
.
As a result, the cosmos/evm mempool cannot handle transactions the same way Ethereum’s mempool does.
ante/evm/09_increment_sequence.go
if txNonce != accountNonce {
if txNonce > accountNonce {
return errorsmod.Wrapf(
mempool.ErrNonceGap,
"tx nonce: %d, account accountNonce: %d", txNonce, accountNonce,
)
}
return errorsmod.Wrapf(
errortypes.ErrInvalidSequence,
"invalid nonce; got %d, expected %d", txNonce, accountNonce,
)
}
While ErrNonceGap
is handled by CheckTxHandler and that tx is inserted into mempool as "queued transaction", ErrInvalidSequence is not handled appropriately.
However, there is already validation logic that checks whether tx nonce is lower than nonce with committed state, not uncommitted state. (mempool/txppool/validation.go)
next := opts.State.GetNonce(from)
if next > tx.Nonce() {
return fmt.Errorf("%w: next nonce %v, tx nonce %v", core.ErrNonceTooLow, next, tx.Nonce())
}
This validation is called when new tx is added to mempool. (mempool/txpool/legacypool/legacypool.go)
Proposed Solution
CheckTxHandler should insert tx into mempool if that tx got "invalid seqence" error from anteHandler. (mempool/check_tx.go)
As-Is
// detect if there is a nonce gap error (only returned for EVM transactions)
if errors.Is(err, ErrNonceGap) {
// send it to the mempool for further triage
err := mempool.InsertInvalidNonce(request.Tx)
if err != nil {
return sdkerrors.ResponseCheckTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, anteEvents, false), nil
}
}
To-Be
// detect if there is a nonce gap error (only returned for EVM transactions)
if errors.Is(err, ErrNonceGap) || errors.Is(err, sdkerrors.ErrInvalidSequence) {
// send it to the mempool for further triage
err := mempool.InsertInvalidNonce(request.Tx)
if err != nil {
return sdkerrors.ResponseCheckTxWithEvents(err, gInfo.GasWanted, gInfo.GasUsed, anteEvents, false), nil
}
}