41437 [BC-High] an edge case allows duplicate transactions to be added to the mempool of the sequencer

#41437 [BC-High] An edge-case allows duplicate transactions to be added to the mempool of the sequencer

Submitted on Mar 15th 2025 at 07:38:26 UTC by @Capybara for Attackathon | Movement Labsarrow-up-right

  • Report ID: #41437

  • Report Type: Blockchain/DLT

  • Report severity: High

  • Target: https://github.com/immunefi-team/attackathon-movement/tree/main/protocol-units/mempool/util

  • Impacts:

    • Causing network processing nodes to process transactions from the mempool beyond set parameters

Description

Brief/Intro

An edge-case allows duplicate transactions to be added to the mempool of the sequencer.

Blocks are created from a limited amount of transactions extracted from the mempool, therefore having duplicated transactions in the mempool results in a block being produced with less real user transactions than it should.

From the list of impacts for this contest, the appropriate impact for an edge-case that causes the sequencer to produce blocks with less user transactions than it should is: Causing network processing nodes to process transactions from the mempool beyond set parameters.

Vulnerability Details

The mempool transaction key

In simple terms, the mempool works like a key-value database.

Transactions are added under a key, computed using the function below:

Code from: https://github.com/immunefi-team/attackathon-movement/blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/protocol-units/mempool/move-rocks/src/lib.rs#L23-L36

The timestamp of a MempoolTransaction

One of the values used to compute the key is a "timestamp" generated by the sequencer receiving the batch of transactions to add to the mempool, using the function below:

Code from: https://github.com/immunefi-team/attackathon-movement/blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/protocol-units/mempool/util/src/lib.rs#L226-L233

Recalculating the system timestamp for every transaction in the batch

When processing a batch of transactions, a safe sequencer implementation should read the system time only once and produce a timestamp floored to the nearest slot for all transactions in the batch.

Unfortunately, what the current implementation does is to recalculate the floored timestamp for every transaction in the batch.

The timestamp of a system is constantly increasing, which may lead to some transactions receiving a floored timestamp that is later than others.

The likelihood is increased in the Movement Network because the SLOT_SECONDS is a very low constant (2).

Code from: https://github.com/immunefi-team/attackathon-movement/blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/protocol-units/mempool/util/src/lib.rs#L84-L93

Storing a batch of transactions in the mempool

As a reference, below is the function that stores the transaction in the database:

Code from: https://github.com/immunefi-team/attackathon-movement/blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/protocol-units/mempool/move-rocks/src/lib.rs#L119-L159

The section relevant to the bug, is a loop through every transaction in the batch:

First, the algorithm confirms the transaction does not already exists in the dabase by looking for the transaction id:

Then, it puts (but does not write yet into the db) the current transaction under the key generated in the previous sections, and keeps looping to make sure none of the transactions it's about to commit already exists in the mempool:

Finally, it writes in a batch to the db:

Duplicating transactions

Putting it all together:

  1. Transactions are stored in the mempool under a key.

  2. The key is generated at runtime using the timestamp of the system converted into seconds.

  3. It is possible to submit a batch of transactions to be added to the mempool.

  4. A malicious batch of transactions that contains the same transaction repeated, can generate different floored timestamps depending of the UNIX time of the system at the time of processing the batch, which leads to different Mempool keys for the same transactions, and as a result the same transactions gets stored multiple times in the mempool.

  5. Blocks are produced by extracting transactions from the mempool until the block size is filled. This edge case allows filling part of the block space with duplicate transactions, leaving out legit transactions from other users.

I hope this effort contributes to the security and stability of the Movement Network chain.

Proof of Concept

Proof of Concepts

I have 3 proof of concepts for this report, to help the Movement Network devs understand and confirm this edge case as valid.

First, let's conduct a simple test to verify that two identical transactions created in the sequencer, with a one-second difference in timestamps, may result in two distinct floored timestamps.

Add test to: ./attackathon-movement/protocol-units/mempool/util/src/lib.rs

Now, let's test a simulation of the UNIX timestamp increasing at runtime, while processing duplicated transactions, leading to different keys for the mempool and storing the same transaction twice in the mempool.

For this test, we could either set the UNIX timestamp of the operating system to a specific value (which increases the difficulty of creating a proof of concept that is easy to reproduce for everyone involved) or process a large amount of calculations to allow the timestamp to increase at runtime by up to 1 second.

Below is the test, heavily commented at every step:

Add test to: ./attackathon-movement/protocol-units/mempool/util/src/lib.rs

Finally, let's confirm with a proof of concept that when reading transactions from the mempool to fill a block, it reads the duplicated transactions and they count when calculating if the block is already filled or it needs more txs.

The code that fills the block with txs from the mempool is:

Code from: https://github.com/immunefi-team/attackathon-movement/blob/a2790c6ac17b7cf02a69aea172c2b38d2be8ce00/protocol-units/sequencing/memseq/sequencer/src/lib.rs#L101-L131

Code for the test:

Add to: /attackathon-movement/protocol-units/mempool/move-rocks/src/lib.rs

Was this helpful?