Skip to content

fix(node): accept peer transactions before the first block after startup - #220

Open
panos-xyz wants to merge 3 commits into
mainfrom
fix/startup-sync-state-idle
Open

panos-xyz wants to merge 3 commits into
mainfrom
fix/startup-sync-state-idle

Conversation

@panos-xyz

@panos-xyz panos-xyz commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

reth marks the network as syncing on every start and ignores transaction gossip until the node has switched from syncing to idle once, which otherwise happens with the first block the consensus client imports. That includes the pool announcement each peer sends once when a session opens; the peer records those hashes as known and never announces them again.

On Morph, blocks only ever come from the consensus client through the engine API, so there is nothing to catch up on over p2p. On a sequencer the gap is a real problem: after every restart, the transactions RPC nodes were holding, including everything users sent during the downtime, never reach the sequencer, and each affected sender's later transactions wait behind the missing nonce indefinitely. The gas-price and token-price oracles stall the same way when they submit through an RPC node. morph-geth accepts transactions from process start, so this only shows up with the switch to reth.

This builds the network through a new MorphNetworkBuilder, which delegates to EthereumNetworkBuilder and switches the network's sync state Syncing → Idle once as soon as it is up. reth gates transaction gossip on is_initially_syncing(), which stays false for good after that first switch, so the Syncing state the launcher sets afterwards no longer drops peer transactions.

The first revision of this PR enabled --debug.startup-sync-state-idle by default instead. That flag switches to idle only when the consensus engine task starts, but the launcher starts the network, and its dials to trusted peers, before it sets Syncing, so a session opening in between still lost its pool announcement. The flag is not needed with this change, and it is not sufficient on its own.

Behaviour change

  • Transaction gossip, including the pool announcement a peer sends when its session opens, is accepted from the moment the network starts.
  • is_syncing() is unchanged: eth_syncing still reports syncing until the first imported block, and bad-transaction peer penalties stay suppressed until then, as before.
  • Block import, engine handling and consensus validation are unchanged.

Testing

  • New e2e test network::peer_pool_reaches_node_before_first_block: a peer holding a pending transaction connects to a node that has not imported a block, and the transaction must reach the node. It times out without the network builder change and passes with it.
  • Restarts with real binaries on a local dev genesis and no consensus client: node A holds one pending transaction, node B restarts with A as a trusted peer, so it dials A while it is starting. Restarts in which the transaction reached B:
    • main (4955f7c13): 0 of 3;
    • --debug.startup-sync-state-idle (first revision): 5 of 20;
    • this change: 20 of 20.
  • make test-e2e, workspace unit tests, make clippy-e2e, cargo clippy -p morph-reth --all-targets -- -D warnings, cargo fmt --all -- --check.

Found in the pre-migration sequencer/validator audit (SEQP-01).

Summary by CodeRabbit

  • New Features
    • Sequencer nodes can receive transactions from connected peers while syncing, so transactions shared before the connection is established can still be included in the sequencer’s transaction pool.
    • Network behavior is verified with separate sequencer and RPC nodes, including confirmation that transaction propagation works while the sequencer remains at its initial block.

reth marks the network as syncing on every start and ignores transaction
gossip until the engine commits its first canonical block. That includes the
pool announcement a peer sends once when a session opens; the peer records
those hashes as known and never announces them again.

Morph blocks only ever arrive from the consensus client, so there is nothing
to catch up on over p2p and the window is just the gap between start-up and
the first imported block. On a sequencer it swallows every transaction RPC
nodes were holding while it restarted: those never reach it, and each
affected sender's later transactions wait behind the missing nonce.
morph-geth accepts transactions from process start.

Enable reth's --debug.startup-sync-state-idle for `morph-reth node` by
default. The network is marked idle once the engine starts (after an
interrupted pipeline backfill, if one is needed), so gossip and session
announcements are accepted from start-up. eth_syncing now reports false from
start-up as well; it already did after the first imported block however far
behind the node was, so it was never a caught-up signal here.
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Warning

Review limit reached

Next included review available in 59 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 2c03155b-c506-4d8d-8bb3-ffb353d1913f

📥 Commits

Reviewing files that changed from the base of the PR and between 8e9762a and db5565a.

📒 Files selected for processing (2)
  • crates/node/src/test_utils.rs
  • crates/node/tests/it/network.rs

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: c44bfa24-e7b4-4642-86a8-d31f8760ce03

📥 Commits

Reviewing files that changed from the base of the PR and between bad4612 and 8e9762a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • Cargo.toml
  • crates/node/Cargo.toml
  • crates/node/src/components/mod.rs
  • crates/node/src/components/network.rs
  • crates/node/src/node.rs
  • crates/node/tests/it/main.rs
  • crates/node/tests/it/network.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The Morph node now uses MorphNetworkBuilder, which builds an Ethereum network and updates its sync state. An integration test checks that a pending transaction reaches a connected sequencer before its first block.

Changes

Startup transaction gossip

Layer / File(s) Summary
Add MorphNetworkBuilder
Cargo.toml, crates/node/Cargo.toml, crates/node/src/components/*
The node crate adds the reth-network dependency and exports MorphNetworkBuilder. The builder delegates network construction to EthereumNetworkBuilder, then sets the network sync state to Syncing and Idle.
Use MorphNetworkBuilder
crates/node/src/node.rs
MorphNode uses MorphNetworkBuilder in its component builder and documents the network component.
Test transaction propagation
crates/node/tests/it/main.rs, crates/node/tests/it/network.rs
The integration test checks that a pending transaction reaches the sequencer before it imports a block, and that the sequencer’s best block number remains 0.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant RPCNode
  participant Sequencer
  participant TransactionPool
  RPCNode->>RPCNode: Hold pending transaction
  RPCNode->>Sequencer: Connect peer networks
  Sequencer->>TransactionPool: Receive transaction gossip
  TransactionPool-->>Sequencer: Confirm transaction is present
Loading

Merge Risk: ⚪ Minimal · up to 8e976

The change is mergeable on the supplied evidence: it targets the startup transaction gap and includes a test for propagation before the first block. Confirm the pinned dependency’s sync-state behavior during normal validation.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 5 files. (2 skipped: 2… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: allowing peer transactions before the node imports its first block after startup. It matches the pull request objectives and code changes.
Full details: Docstring Coverage

Explanation

Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 5 files. (2 skipped: 2 unsupported.)

✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

The previous commit enabled reth's --debug.startup-sync-state-idle, which
switches the network to idle only when the consensus engine task starts. The
launcher starts the network, and with it the dials to trusted peers, before
it sets the Syncing state, so a session that opens between the two still has
its pool announcement dropped. A node restarting against a peer that holds a
pending transaction received it in 5 of 20 restarts.

reth gates transaction gossip on is_initially_syncing, which stays false for
good after the first Syncing -> Idle switch. Build the network through a
MorphNetworkBuilder that makes that switch as soon as the network is up, and
drop the CLI default. The same restarts now deliver the transaction every
time, and is_syncing, and with it eth_syncing, behaves exactly as before.
@panos-xyz panos-xyz changed the title fix(cli): accept peer transactions before the first block after startup fix(node): accept peer transactions before the first block after startup Sep 24, 2026
let mut sequencer = sequencers.pop().unwrap();
let mut rpc = rpcs.pop().unwrap();

let tx = make_transfer_tx(wallet.chain_id, wallet_at_index(1, wallet.chain_id), 0).await;
}
let tip = tip.expect("five blocks were imported");

let tx = make_transfer_tx(wallet.chain_id, wallet_at_index(1, wallet.chain_id), 0).await;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants