Skip to content

Fix(mempool): CheckTxHandler should handle tx with "invalid sequence" error  #590

@cloudgray

Description

@cloudgray

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
	}
}

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions