#41466 [BC-Medium] Incorrect sequence number tracking in mempool commit
Submitted on Mar 15th 2025 at 16:16:48 UTC by @Rhaydden for Attackathon | Movement Labs
Report ID: #41466
Report Type: Blockchain/DLT
Report severity: Medium
Target: https://github.com/immunefi-team/attackathon-movement/tree/main/protocol-units/execution/maptos/opt-executor
Impacts:
Causing network processing nodes to process transactions from the mempool beyond set parameters
Description
Brief/Intro
commit_transaction
in TransactionPipe
impl wronglly uses the on-chain sequence number instead of the transaction's actual sequence number when committing transactions to the mempool. This allows invalid transaction ordering, enabling transactions with lower sequence numbers to be accepted after higher ones. In production, this could lead to double-spends, failed txns, and consensus failures due to invalid transaction sequences.
Vulnerability Details
The core_mempool.commit_transaction()
call in submit_transaction
uses sequence_number
(on-chain value) instead of the transaction's transaction_sequence_number
. This breakss the mempool's sequence tracking contract.
https://github.com/immunefi-team/attackathon-movement//blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/protocol-units/execution/maptos/opt-executor/src/background/transaction_pipe.rs#L299
self.core_mempool.commit_transaction(&sender, sequence_number); // Using on-chain sequence number
The variable sequence_number
is obtained from the validation process through:
let sequence_number = match self.has_invalid_sequence_number(&transaction)? {
SequenceNumberValidity::Valid(sequence_number) => sequence_number,
SequenceNumberValidity::Invalid(status) => {
return Ok(status);
}
};
In has_invalid_sequence_number
, the sequence number is retrieved using the current on-chain state rather than the transaction's actual sequence number.
Looking at how commit_transaction
is implemented in the Aptos TransactionStore
contract:
https://github.com/aptos-labs/aptos-core/blob/308d59ec2e7d9c3937c8b6b4fca6dd7e97fd3196/mempool/src/core_mempool/transaction_store.rs#L584C4-L590C6
pub fn commit_transaction(&mut self, account: &AccountAddress, sequence_number: u64) {
let current_seq_number = self.get_sequence_number(account).map_or(0, |v| *v);
let new_seq_number = max(current_seq_number, sequence_number + 1);
self.sequence_numbers.insert(*account, new_seq_number);
self.clean_committed_transactions(account, new_seq_number);
self.process_ready_transactions(account, new_seq_number);
}
This function:
Gets the current tracked sequence number for the account
Sets the new sequence number to be the maximum of (current_seq_number, sequence_number + 1)
Updates the account's sequence number in the mempool
Processes potentially ready transactions for the account
Problem is when a txn with a higher sequence number (e.g., 5) than the current on-chain state sequence number (e.g., 3) is submitted. The mempool incorrectly updates its tracking to use the on-chain sequence number + 1 (4) instead of the transaction's sequence number + 1 (6). This causes subsequent transactions from the same account with sequence numbers between these values (e.g., 4, 5) to be incorrectly rejected as "too old" or "invalid sequence number"
Aptos maintains strict sequence number ordering by tracking the highest sequence number seen. Aptos uses max()
to ensure monotonic increases.
Ourown impl breaks these guarantees by using on-chain sequence numbers instead of transaction sequence numbers. Its not maintaining proper tracking of highest seen sequence numbers thus, allowing out-of-order transaction acceptance.
sequence_number
in transaction_pipe.rs
is the on-chain value (from has_invalid_sequence_number()
), not the transaction's actual sequence number. This causes the mempool to track an incorrect "next expected sequence number", allowing older transactions to bypass sequence checks after newer ones are added.
Impact Details
This falls under "Causing network processing nodes to process transactions from the mempool beyond set parameters" with the following specific impacts:
Mempool can accept transactions out of sequence (e.g., TX4 after TX5). breaks fundamental txn ordering guarantees.
Nodes process transactions in incorrect sequence
Most especiallly, this enables a potential transaction reordering attack case.
References
Aptos mempool implementation: transaction_store.rs
---> https://github.com/aptos-labs/aptos-core/blob/308d59ec2e7d9c3937c8b6b4fca6dd7e97fd3196/mempool/src/core_mempool/transaction_store.rs#L584C4-L590C6
https://github.com/immunefi-team/attackathon-movement//blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/protocol-units/execution/maptos/opt-executor/src/background/transaction_pipe.rs#L299
Proof of Concept
Proof of Concept
Initial State:
On-chain sequence for Alice: 3
Mempool stored sequence: 3
Submit TX5 (seq=5):
Passes
has_invalid_sequence_number
check (5 ≤ 3 + 32).commit_transaction(3)
updates mempool to seq=4 (3+1).
Submit TX4 (seq=4):
Check: 4 ≥ 4 (current mempool seq) → Accepted.
Result: Mempool contains TX4 and TX5 → Invalid ordering → Consensus rejects block.
Fix
Use the transaction's actual sequence number (transaction_sequence_number
) instead of the validated sequence number from the on-chain state (sequence_number
) when committing the transaction to the mempool.
- self.core_mempool.commit_transaction(&sender, sequence_number);
+ self.core_mempool.commit_transaction(&sender, transaction_sequence_number);
Was this helpful?