Skip to content

esplora: don't bubble transient backend errors to lightningd - #103

Open
vincenzopalazzo wants to merge 2 commits into
mainfrom
esplora/no-abort-on-transient-failure
Open

esplora: don't bubble transient backend errors to lightningd#103
vincenzopalazzo wants to merge 2 commits into
mainfrom
esplora/no-abort-on-transient-failure

Conversation

@vincenzopalazzo

Copy link
Copy Markdown
Contributor

Summary

  • When upstream esplora returns 5xx / 429 / connection-reset, sync_block_by_height and sync_estimate_fees propagate the post-retry Err up to clightningrpc-plugin. That serializes as a JSON-RPC error response, and lightningd's bitcoind plugin handler (bitcoind.c:bitcoin_plugin_error) treats any error response on getrawblockbyheight / estimatefees as fatal — it aborts the daemon with SIGABRT.
  • Both methods already have a contract that says what to do on "no data available":
    • sync_block_by_height → set blockhash and block to null
    • sync_estimate_fees → set every fee field to null
  • Wrap each method body in an IIFE, log the underlying error at WARN, and return the contract-defined null payload. Success path is unchanged.

Why this matters

Observed in production on a node running mainnet + testnet against mempool.space. Going back through the testnet log:

2025-07-11  estimatefees       (504 stream timeout)        → SIGABRT
2025-08-21  getrawblockbyheight (429 rate limit)           → SIGABRT
2025-08-23  getrawblockbyheight (429 rate limit)           → SIGABRT
2025-08-26  getrawblockbyheight (429 rate limit)           → SIGABRT
2026-05-05  getrawblockbyheight (504 gateway timeout × 4)  → SIGABRT
2026-05-05  getrawblockbyheight (connection reset by peer) → SIGABRT

Every one of these is upstream esplora having a transient hiccup and folgore translating it into a daemon abort. The merged #102 fixed the panic-on-bad-bytes case in raw_to_num; this PR closes the remaining contract-violation path that turns transient failures into crashes.

Test plan

  • cargo build --release -p folgore-esplora
  • cargo fmt -p folgore-esplora -- --check (clean)
  • cargo clippy -p folgore-esplora --no-deps -- -D warnings (clean)
  • Manual: point the plugin at a deliberately broken esplora endpoint and observe lightningd continues running (will log "got nothing for block X" and retry on the next tick) instead of aborting.

🤖 Generated with Claude Code

Your Name and others added 2 commits May 5, 2026 20:01
When the upstream esplora endpoint returns a 5xx / 429 / connection
reset, `sync_block_by_height` and `sync_estimate_fees` propagate the
post-retry `Err` up to clightningrpc-plugin, which serializes it as a
JSON-RPC error response. lightningd's bitcoind plugin handler
(`bitcoind.c:bitcoin_plugin_error`) treats any error response on
`getrawblockbyheight` or `estimatefees` as fatal and aborts the daemon
with SIGABRT. In production this surfaced as repeated `FATAL SIGNAL 6`
crashes whenever mempool.space had a hiccup.

The plugin contract already says the right thing for both methods:

- `sync_block_by_height` (folgore-common, line ~69):
  "The plugin must set all fields to null if no block was found at the
  specified height." — extending that to "no block currently retrievable"
  is consistent with the spirit of the contract and is what lightningd
  expects (it will simply poll again on the next tick).

- `sync_estimate_fees` (folgore-common, line ~65):
  "If fee estimation fails, the plugin must set all the fields to null."
  — this case was never wired up; the previous code returned `Err`.

Wrap each method body in an IIFE so all internal `?` short-circuits land
in one place, log the underlying error at WARN, and reply with the
contract-defined null payload. No change for the success path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…path

The hand-rolled fallback object in sync_estimate_fees omitted feerate_floor,
which lightningd v25 treats as a protocol error: estimatefees_callback ->
bitcoin_plugin_error -> fatal() -> abort(). The shared helper in
folgore-common/client/fee_estimator.rs already produces the correct
null-but-shape-complete payload, so use it.

Closes #104.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vincenzopalazzo

Copy link
Copy Markdown
Contributor Author

Pushed 4103b80 extending this PR to also cover sync_estimate_fees — the same "transient backend error must not abort lightningd" pattern was missed for the fee-estimate path. Reproduced live on 2026-05-06: [35] SSL connect error from blockstream.info on /fee-estimates → folgore returned a hand-rolled all-nulls object missing feerate_floorbitcoind.c:bitcoin_plugin_errorfatal() → SIGNAL 6.

Full root-cause writeup and crash backtrace in #104 (closed by 4103b80).

Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com

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.

1 participant