fix(svm): re-broadcast Solana tx during confirmation polling#3494
Open
droplet-rl wants to merge 1 commit into
Open
fix(svm): re-broadcast Solana tx during confirmation polling#3494droplet-rl wants to merge 1 commit into
droplet-rl wants to merge 1 commit into
Conversation
`_sendAndPoll` previously broadcast a tx exactly once and then polled `getSignatureStatuses` for the full budget (25 cycles × 600ms = 15s). If the first-targeted leader skipped or dropped the tx — common under congestion or with an uncompetitive priority fee — the tx would sit in mempool until some later leader happened to pick it up, often beyond our polling budget. Empirical evidence on `zion-across-l2-executor-solana` (~31 occurrences in 15 days): every timed-out signature finalised on chain within ±5s of the timeout firing, one of them 2s *after* the timeout. So the tx landed; we just gave up before observing it. RPC lag is not the issue. Re-broadcast the same serialized wire tx every 3 polling cycles (~1.8s, a few Solana slots) with `skipPreflight: true`. The signature is deterministic from body+sigs so re-broadcasts are idempotent; the polling loop remains the source of truth for confirmation status. Errors thrown by re-broadcast (e.g. AlreadyProcessed once the tx has landed, or transient provider failures) are intentionally swallowed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Summary
_sendAndPoll(src/utils/TransactionUtils.ts) broadcasts a Solana tx exactly once viasignAndSendTransaction, then pollsgetSignatureStatusesfor the full budget (25 cycles × 600ms = 15s). If the first-targeted leader skips or drops the tx — common under congestion or with an uncompetitive priority fee — the tx sits in mempool until some later leader happens to ingest it, often beyond our polling budget. That's the failure mode that's been firing asat
Dataworker.ts:3088(introduced by #3469) onzion-across-l2-executor-solana.Evidence the tx is landing late, not the RPC lagging
Lined up the on-chain
blockTimeagainst the Slack error timestamp for 7 recent failures:2pDsTZYW…2TwxY6pM…5XzGj7ob…4i8oPkEJ…4cz5sHCJ…2rLphv2d…4UzFw7kD…Every signature finalised on chain within ±5s of our timeout firing, one of them ~2s after the timeout — so the tx had landed; we just gave up before observing it. RPC lag isn't the culprit.
Frequency on
zion-across-l2-executor-solana: 31 of these errors since 2026-06-01 (~2/day). No funds at risk — the next dataworker run sees the leaf as already executed and skips — but it's a steady page-equivalent and a real liveness gap on a heavily-watched bot.Change
In
_sendAndPoll, re-broadcast the same signed wire tx everySOLANA_REBROADCAST_EVERY_CYCLES = 3polling cycles (~1.8s, a few Solana slots) withskipPreflight: true. The signature is deterministic from the tx body + sigs, so re-broadcasts are idempotent — the polling loop remains the source of truth for confirmation. Errors thrown by re-broadcast (e.g.AlreadyProcessedonce the tx has landed, transient provider failures) are intentionally swallowed.Implementation detail: extracted the prepare/sign/broadcast steps from
signAndSendTransactioninto a private_prepareAndSendSolanaTransactionhelper that returns the signature plus the serialized wire tx and the send options used, so_sendAndPollcan re-broadcast the same tx without re-signing or re-fetching the blockhash. PublicsignAndSendTransactionsignature is unchanged.Trade-offs considered
SVM_REFUND_LEAF_SEND_POLL_CYCLES? Extending the budget would mask the symptom (some txs would happen to land within the wider window) but every send pays the wall-clock cost on every failure, and the actual problem — leader skipping our send and no resend ever happening — would persist. Re-broadcasting is what the standard Solanasend-and-confirmloop does.skipPreflight: trueon re-broadcasts? Preflight already ran on the initial send and either passed (so subsequent state-based rejections on a no-op re-broadcast are spurious) or failed (in which case we wouldn't be here). Skipping it keeps the RPC roundtrip cheap.sendTransactionwithAlreadyProcessed— that's fine, it means the next poll will seeconfirmed. Other transient errors are equally non-fatal because the polling loop is what decides success/failure.Out of scope / follow-ups
SVM_PRIORITY_FEE_OVERRIDEthe executor bot is running with. Even with rebroadcast, an uncompetitive priority fee will keep us in the tail of leader queues — would be worth bumping if it's at default.@across-protocol/sdkif any downstream consumers use a similar send-and-poll pattern.Doc updates
No
README.md/AGENTS.mdupdates needed — internal hardening of an existing private code path with no operator-facing contract change.Test plan
yarn typecheckyarn lintnpx hardhat test test/TransactionUtils.svm.ts— 8 passing, including 3 new tests:skipPreflight=trueevery 3 polling cyclessendTransactionand keeps pollingzion-across-l2-executor-solanaand watch for the next ~48h. Win condition: zero newdid not confirm within 15000mspages, or — if any do fire — the on-chain landing time is now well past our polling window rather than within ±5s of it (which would indicate priority fee, not send-side, as the remaining gap).Threads
🤖 Generated with Claude Code