Skip to content

engine_newSafeL2Block pulls txpool transactions and corrupts derivation #179

Description

@panos-xyz

Summary

engine_newSafeL2Block is expected to deterministically reconstruct the L1-committed L2 block from SafeL2Data. In morph-reth v1.2.0 it reuses the normal payload builder, which always appends best transactions from the local txpool.

A follower can therefore insert transactions from future L2 blocks into an earlier derived block. Its chain then diverges from the sequencer and derivation eventually stalls when the same transactions appear at their committed heights.

Environment

  • Image: ghcr.io/morph-l2/morph-reth:latest
  • Version: v1.2.0
  • Revision: ce119b59b99190d640599e622e2a162cb47cf8ed
  • Devnet command: make devnet-up-reth in morph-l2/morph
  • Sequencer EL: morph-el-0 / host port 8545
  • Layer-1 verification EL: morph-el-1 / host port 8645

The current main branch is also affected; it is only one CI dependency commit ahead of v1.2.0.

Observed behavior

Batch 2 committed L2 blocks 200 through 398. On the sequencer:

  • block 200 was empty;
  • transaction 0x29c0e6a805748fa240e314cc2cc8dead97f7afdd9b7b390df834e45af228e2f4 (sender nonce 8) was in block 314;
  • transaction 0x819fadd2e852dd3e0771d151dd2924f6b37f31eb7affdd150a752194637cadc7 (sender nonce 9) was in block 318.

The derivation follower instead produced block 200 containing both transactions:

sequencer block 200:
  hash=0x39c4fa982dffefba7d7ab49364264575b349b730a6399107ebcc796cd4ef5d36
  txs=0

follower block 200:
  hash=0xdb5b9ecc4c6b6e061c1196dc0045cbfc372af09442a10c36c2568d6dc2eb1d93
  txs=[nonce 8, nonce 9]

The chains agree through block 199 and diverge exactly at block 200. The follower then reaches block 313 and fails while deriving block 314:

invalid L1 message transaction in payload attributes
error="nonce 8 too low, expected 10"

failed to NewSafeL2Block
error="failed to build block: failed to get built payload: missing payload"

After retries, the Engine API calls time out. The follower remains at block 313 while the sequencer continues advancing.

Root cause

RealMorphL2EngineApi::new_safe_l2_block moves SafeL2Data.transactions into AssembleL2BlockParams and calls the shared build_l2_payload path.

That path puts the supplied transactions into MorphPayloadAttributes.transactions. The payload builder defines those attributes as L1 messages, executes them first, and then unconditionally executes transactions from the local pool:

// Always execute pool transactions (L2 transactions from mempool)
let best_txs = best(ctx.best_transaction_attributes(base_fee));
ctx.execute_pool_transactions(...)?;

On a derivation follower, future sequencer transactions are already present in the local pool through transaction gossip. An empty committed block therefore absorbs those future transactions. When the committed transaction is supplied again at its real block height, its nonce is already consumed.

There is also an interface mismatch: SafeL2Data.transactions is documented as the complete ordered transaction list needed to reconstruct the block, while MorphPayloadAttributes.transactions is documented and processed as L1 messages only.

Expected behavior

engine_newSafeL2Block must be deterministic:

  • execute exactly the ordered transactions supplied in SafeL2Data;
  • never read or append transactions from the local txpool;
  • produce the same block hash as the sequencer for the same parent and committed block inputs.

Normal sequencer assembly can continue using the txpool.

Requested change

  • Add an explicit payload-building policy that disables txpool selection for new_safe_l2_block.
  • Treat SafeL2Data.transactions as the complete ordered block transaction list rather than as an L1-message-only list.
  • Keep the normal sequencer assembly behavior unchanged.
  • Add a regression test that:
    1. preloads the follower pool with transactions committed to later blocks;
    2. derives an earlier empty block and asserts that it remains empty;
    3. derives the transactions at their committed heights without a nonce error;
    4. asserts follower and sequencer block hashes match.

Impact

The layer-1 verification/full-history follower diverges and stalls, so its safe and finalized tags stop advancing. Services that require both full history and L2 finality cannot safely use the follower RPC.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions