Repository navigation
op-acceptance-tests,op-devstack: cover eth version negotiation and transaction propagation between op-reth and op-geth - #23443
Draft
einar-oplabs wants to merge 3 commits into
Conversation
…tion and transaction propagation between op-reth and op-geth Add an acceptance test package that peers an op-reth and an op-geth follower as basic peers, requires them to negotiate the highest eth version both advertise, requires a transaction submitted to either to reach the other's pool and a block, and has op-geth announce more transaction hashes at once than op-reth tracks per peer after the reth 2.6 bump, requiring op-reth to keep the session and keep syncing. The devstack gains basic (untrusted) EL peering, typed admin_peers lookups, batch transaction submission, pool waits and offline signing; txplan gains a static chain ID option, and an explicit fee cap no longer gets recomputed when the tip cap is pinned after it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the chain The sender follower starts behind the chain and can only fill the gap through the receiver, its only execution-layer peer, so the pair is returned only once the sender's head has reached the sequencer's. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…d op-geth peering tests Expect the highest eth version both peers advertise, list handshaking peers too, gate the burst and the transfer on both followers holding the funds, say what the burst can and cannot show about op-reth's per-peer cap, bound the drain by what a cap-sized fetch downstream can strand, log every poll of the pool waits, and drop the setup re-checks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
einar-oplabs
force-pushed
the
einar/23402-eth71-opgeth-peering
branch
from
October 15, 2026 16:01
0b4f897 to
56e7d65
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nothing checked how op-reth and op-geth peer and exchange transactions:
rust/op-reth/crates/node/tests/it/p2p_version.rspins the version two op-reth nodes negotiate, and every devstack peering is trusted, which exempts the session from the reputation handling the reth 2.6 bump (#23046) changes along with the default protocol (eth/71) and the transaction fetcher (reth#26924, a per-peer cap of 4096 announced hashes). This adds an acceptance test package that peers an op-reth follower and an op-geth follower as basic peers and checks the negotiated version, transaction propagation in both directions, and op-reth under an announcement burst from op-geth, written against hooks that are the same ondevelopand on the bump.What the tests cover
All three tests run on a
pairSystem(op-acceptance-tests/tests/op_reth_opgeth_peering/system_test.go): an op-reth sequencer, an op-reth follower and an op-geth follower, 1s blocks. One follower is the sender: it is kept off the mixed runtime's trusted mesh (IsolateFromL2P2P), its consensus client is peered with the sequencer's alone, and it dials the other follower (the receiver) with the newPeerWithBasic, so the receiver is its only execution-layer peer and the session is trusted on neither side. The receiver keeps the trusted peering with the sequencer that the runtime set up. A transaction submitted to the sender can therefore only reach the sequencer through the receiver, so its inclusion proves it crossed the peering under test; with the sequencer paused, the receiver's pool can be observed. The system is returned once the sender has filled its gap to the chain through the receiver.TestOpRethAndOpGethNegotiateTheHighestCommonEthVersion, subtestsop-reth_dials_op-gethandop-geth_dials_op-reth: both sides must report the same negotiated eth version throughadmin_peers, equal to the highest version both advertise (sysgo.HighestCommonEthVersionover the twocapslists), and the advertised versions are logged.TestTransactionCrossesTheOpRethOpGethPeeringIntoABlock, subtestsop-reth_to_op-gethandop-geth_to_op-reth: with the sequencer paused, a transfer submitted to the sender must show up in the receiver's pool (eth_getTransactionByHashwith no block), and once the sequencer resumes both followers must see it in the same block.TestOpRethKeepsItsOpGethPeerThroughAnAnnouncementBurst: op-geth is the sender. 4112 fresh accounts (the cap plus one fan-out of 16) each submit one transaction with 4096 bytes of zero calldata to op-geth in batch calls while the sequencer is paused. op-geth sends a transaction larger than itstxMaxBroadcastSize(4096 bytes) to no peer whole: it announces the hash (eth/handler.go,BroadcastTransactions), so the burst is 4112NewPooledTransactionHashesentries to op-reth within about a second, which op-reth has to fetch. The test requires op-reth's pool to reach at least 4096 of them, op-reth to keep the same session with op-geth (same remote port inadmin_peers; a drop followed by op-geth's static re-dial would show a new one) and to log nothing at error level, and once the sequencer resumes, op-reth's pool to drain down to at most 16 and its head to keep advancing. How many of the burst op-reth fetched and how many each follower still holds afterwards are logged.What the burst can and cannot show: the number of hashes announced exceeds what op-reth tracks per peer after the bump, but op-reth fetches while the burst is still arriving, and whether its unfetched set from op-geth ever reached 4096 (the point where it drops further announcements) is not observable through RPC. The test therefore proves that op-reth takes a cap-sized announcement burst from an untrusted op-geth peer in its stride, not that the drop path ran. Proving the latter needs reth's
announced_hashes_dropped_peer_limitmetric, which the mixed runtime does not expose. The drain bound of 16 rather than 0 is for the same cap downstream: op-reth passes the burst to the sequencer, which fetches what op-reth only announced to it, so a cap-sized fetch there can strand up to one fan-out in op-reth's pool; every run below drained to 0.Each sender has exactly one transaction, so a hash the fetcher drops cannot leave a nonce gap behind; the accounts are funded through a tree of distributors (
funding_test.go) because reth holds at most 16 transactions per account (--txpool.max-account-slots, and the runtime passes--txpool.nolocals), which also bounds every funding round to one batch per payer.Matrix: direction {op-reth to op-geth, op-geth to op-reth} as subtests (the dialing side is the sender, so both dial directions are covered too); CL {op-node, kona-node} through
DEVSTACK_L2CL_KIND. Skips onDEVSTACK_L2EL_KIND=op-geth(the sequencer is this tree's op-reth).Local verification
Debug op-reth built from this stack's Rust tree (identical to
develop's), kona-node release binary fromdevelop'srust/kona,-parallel 2, undercaffeinate:developop-reth, op-node (default): all five cells pass, 69s for the package.developop-reth,DEVSTACK_L2CL_KIND=kona-node: all five cells pass, 100s.Against #23046's op-reth (reth 2.6.0, built from the bump head 93d9ec9, op-node): all five cells pass, 71s.
Negotiated versions, as the verification criterion asks: op-geth v1.101702.4-rc.1 advertises
eth/69only (eth/protocols/eth/protocol.go,ProtocolVersions = {ETH69}).develop's op-reth advertiseseth/66toeth/69, the bump'seth/66toeth/71; the pair negotiates eth/69 on both, in both dial directions, so on the bump op-reth falls back two versions from its own eth/71 to op-geth's highest. The peering is untrusted on both sides (asserted insidePeerWithBasic); op-reth marks op-geth's entry static when op-reth dials, op-geth marks op-reth's static when op-geth dials.Burst: op-reth's pool held 4107 to 4112 of the 4112 at the first poll after the batches were accepted, on
developand on the bump alike (4105 to 4112 across all runs), the session kept its remote port and eth/69, no op-reth error log, and after the sequencer resumed every burst transaction was included within 6s: 0 left in op-reth and 0 left in op-geth in every run.Unit:
go test ./op-service/txplan/ ./op-devstack/sysgo/ -run 'TestHighest|TestPeerInfo|TestPeerIDMatches|TestPollPeerList|TestPinnedFields'andgo test -run TestFunding ./op-acceptance-tests/tests/op_reth_opgeth_peering/pass;linter/bin/op-golangci-lint runon the changed packages reports no issue.Observations
Failed to get payload ... err="Unsupported fork"while it is behind the chain: op-node derives attributes from L1 and asks op-geth to seal them, which op-geth refuses under the devnet's Karst-at-genesis config. The follower catches up through the receiver instead, and the same lines appear ondevelop; noted, not investigated.dial tcp 127.0.0.1:<port>: i/o timeoutfor 30s. The pair keeps the sender's consensus client on a single peer (the sequencer's), which never showed it in nine runs since.Seams
sysgo.ConnectP2PBasic/dsl.L2ELNode.PeerWithBasic: a basic peering (static entry on the dialer, nothing on the acceptor, trusted on neither side, asserted after connecting).PeerWithstays trusted on both sides;DisconnectPeerWithundoesPeerWithonly.dsl.L2ELNode.VerifyPeeredWith,PeerInfoOf,Peersoversysgo.PeerInfo/ListPeers/ELPeerInfo(caps, negotiated eth version or 0 during the handshake, inbound/static/trusted, session addresses),sysgo.HighestEthVersionandHighestCommonEthVersion.dsl.L2ELNode.SendTransactions(batchedeth_sendRawTransaction),WaitForPooledTransaction,TxPoolStatus,TxPoolReached,TxPoolHoldsAtMost,TxPoolDrained.dsl.EOA.PlanOffline,Sign,Submit;txplan.WithStaticChainID.txplan.WithGasFeeCapnow drops the default fee-cap computation the wayWithGasLimitdrops the estimator, so a pinned fee cap stays pinned when the tip cap is pinned after it or a block is fetched (it was silently recomputed from the tip cap and the base fee); the one existing caller,op-acceptance-tests/tests/interop/loadtest/l2.go, now gets the 10,000 gwei cap it asked for, which its budget already accounts for.go-code-reviewerwas run and every finding addressed: thebigintlint, the burst's claim re-scoped as above and its drain bound, both followers gated on the funding blocks before the burst and the transfer, the expected version as the highest common one rather than the smaller maximum,Peerslisting handshaking peers too, the session check guarded to the accepting side, per-poll logging in the pool waits, and the setup re-checks, unusedFnvariant, duplicated node-info lookups and inaccurate comments removed.Stacked on #23440, #23438, #23436, #23431, #23427, #23421 and #23418.
Closes #23402
🤖 Generated with Claude Code