Skip to content

eth: preserve preconfirmation RPC availability - #2394

Merged
cffls merged 1 commit into
cffls/sequence-publisherfrom
vbhattac/preconf-sync-local-txpool
Sep 8, 2026
Merged

eth: preserve preconfirmation RPC availability#2394
cffls merged 1 commit into
cffls/sequence-publisherfrom
vbhattac/preconf-sync-local-txpool

Conversation

@vbhattaccmu

@vbhattaccmu vbhattaccmu commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR fixes eth_sendRawTransactionSync when preconfirmations are enabled but the RPC node has no direct block-producer endpoints configured.

The synchronous path now submits the transaction to the local txpool first, allowing normal P2P propagation to the block producer. It then waits for either a speculative preconfirmation receipt or the canonical receipt. Direct relay submission remains optional and is used only when bp-rpc-endpoints is configured.

This prevents eth_sendRawTransactionSync from failing with request dropped: rpc client unavailable to submit transactions on standard RPC and sentry nodes.

The change is intentionally limited to transaction submission and receipt waiting. It does not modify pending-block construction, PendingBlockAndReceipts, pending-state RPC behavior, block import, or consensus processing.

Executed tests

go test ./internal/ethapi ./eth/relay ./eth -count=1
PASS

go test -race ./internal/ethapi \
  -run 'TestSendRawTransactionSync_(Preconfirmation|RelayIsOptional)$' \
  -count=1
PASS

go test -race ./eth/relay \
  -run 'TestConfigAccessors$' \
  -count=1
PASS

make lint
PASS — 0 issues

git diff --check
PASS

Manual Kurtosis validation was also performed against an RPC node with preconfirmations enabled and no bp-rpc-endpoints.

A transaction submitted using cast send --sync returned a successful speculative receipt with blockHash: null. The same transaction was subsequently available as a canonical receipt with a populated block hash.

Rollout notes

This change is consensus-neutral and does not alter block execution, validation, import, or fork choice.

No database migration, resync, or coordinated network upgrade is required. The change is backward-compatible for nodes with direct producer endpoints configured.

RPC nodes no longer require bp-rpc-endpoints for eth_sendRawTransactionSync to function. Without those endpoints, transactions propagate through the local txpool and normal P2P networking. When producer endpoints are configured, the existing direct relay path remains available.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@vbhattaccmu

Copy link
Copy Markdown
Contributor Author

@claude review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The synthetic pending-state behavior affects several externally visible RPC semantics and warrants final human validation.

Pull request overview

Restores synchronous transaction submission when no direct producer relay is configured, while adding canonical pending-state fallbacks.

Changes:

  • Submit synchronous transactions through the local txpool and P2P gossip.
  • Make producer relay optional while preserving preconfirmation caching.
  • Provide pending block, receipt, log, and state fallbacks during Sequence Store gaps.
File summaries
File Description
internal/ethapi/preconf_receipt.go Removes the obsolete synchronous submitter interface.
internal/ethapi/api.go Uses local transaction admission for synchronous submissions.
internal/ethapi/api_test.go Tests local admission and optional relay behavior.
eth/relay/relay.go Exposes producer-relay availability.
eth/relay/relay_test.go Tests relay availability states.
eth/api_backend.go Preserves transaction caching and conditionally relays.
eth/api_backend_test.go Updates optional-relay expectations.
eth/api_backend_sequencer_test.go Tests canonical pending fallbacks.
eth/api_backend_pending.go Implements canonical pending block and state views.
Review details
  • Files reviewed: 9/9 changed files
  • Comments generated: 0
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline finding on PreconfRelayAvailable, I also looked at canonicalPendingSnapshot's head-consistency retry loop for unbounded spinning — it re-checks ctx.Err() on every iteration, so it's bounded by the caller's context deadline rather than being a true infinite busy-loop.

Extended reasoning...

The confirmed inline finding (silent no-op in SubmitTxForPreconf when PreconfRelayAvailable() is false, even in cases where bp-rpc-endpoints were configured but failed to dial at startup) already covers the substantive issue for this run and will be posted as an inline comment; no need to restate it here. The one additional item examined was the tight for-loop in canonicalPendingSnapshot (eth/api_backend_pending.go) that retries when the chain head changes between fetching the header and re-checking CurrentBlock — it lacks a sleep/backoff, but it does call ctx.Err() at the top of each iteration, so an RPC caller with a bounded context will not hang forever; this was investigated and ruled out as a standalone bug distinct from the confirmed finding, worth noting for the human reviewer's awareness given a prior review already exists on this PR.

Comment thread eth/api_backend.go
Comment on lines 838 to 845
func (b *EthAPIBackend) SubmitTxForPreconf(tx *types.Transaction) error {
if err := b.cachePreconfTransaction(tx); err != nil {
return err
}
if !b.relay.PreconfRelayAvailable() {
return nil
}
return b.relay.SubmitPreconfTransaction(tx)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 (optional) SubmitTxForPreconf now silently returns nil when RelayService.PreconfRelayAvailable() is false, so if bp-rpc-endpoints ARE configured but every producer fails to dial at Bor startup (newMultiClientWithLimit returns nil multiclient), preconf submissions permanently no-op with zero error/log — the pre-existing log.Error("...submission for preconf failed") signal is lost. Fix: log a warning (once or rate-limited) when relay is configured but unavailable, distinguishing "not configured" from "configured but all producers unreachable", so operators can detect the outage instead of relay silently going dark until restart.

Extended reasoning...

Before this diff, SubmitTxForPreconf always called b.relay.SubmitPreconfTransaction(tx), which returns an error when txRelay/multiclient is nil, and submitForPreconf() logged that error via log.Error. After this diff, PreconfRelayAvailable() short-circuits to return nil before ever calling the relay, so the same nil-multiclient condition (all bp-rpc-endpoints failed to dial at startup, per multiclient.go:121-124) now produces no error and no log line, even though the operator configured relay and expects it to run. The PR states existing bp-rpc-endpoints deployments are unaffected, but this specific transient-dial-failure case silently degrades observability.

Verification: nit. The behavior change is real and reachable but its impact is limited to lost log signal, not lost transactions. When bp-rpc-endpoints are configured yet every producer fails to dial at startup, NewService still builds a non-nil txRelay (eth/relay/relay.go:47-52) while newMultiClientWithLimit returns nil (eth/relay/multiclient.go:120-123), so `txRelay != nil && txRelay.multiclient ==…

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused changes preserve fallback propagation and include appropriate regression coverage.

Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@cffls
cffls merged commit 1932baa into cffls/sequence-publisher Sep 8, 2026
20 of 21 checks passed
@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 46.15385% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.99%. Comparing base (a2adc08) to head (bdba942).
⚠️ Report is 2 commits behind head on cffls/sequence-publisher.

Files with missing lines Patch % Lines
eth/api_backend.go 30.00% 6 Missing and 1 partial ⚠️

❌ Your patch status has failed because the patch coverage (46.15%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@                     Coverage Diff                      @@
##           cffls/sequence-publisher    #2394      +/-   ##
============================================================
+ Coverage                     56.98%   56.99%   +0.01%     
============================================================
  Files                           951      951              
  Lines                        174271   174278       +7     
============================================================
+ Hits                          99304    99331      +27     
+ Misses                        69299    69284      -15     
+ Partials                       5668     5663       -5     
Files with missing lines Coverage Δ
eth/relay/relay.go 82.17% <100.00%> (+0.36%) ⬆️
internal/ethapi/api.go 48.40% <100.00%> (+0.02%) ⬆️
internal/ethapi/preconf_receipt.go 85.00% <ø> (ø)
eth/api_backend.go 33.39% <30.00%> (-0.26%) ⬇️

... and 21 files with indirect coverage changes

Files with missing lines Coverage Δ
eth/relay/relay.go 82.17% <100.00%> (+0.36%) ⬆️
internal/ethapi/api.go 48.40% <100.00%> (+0.02%) ⬆️
internal/ethapi/preconf_receipt.go 85.00% <ø> (ø)
eth/api_backend.go 33.39% <30.00%> (-0.26%) ⬇️

... and 21 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

3 participants