Skip to content

Use Airavata - #131

Merged
andycreed0x merged 6 commits into
developfrom
default-liquid-node
Sep 29, 2026
Merged

andycreed0x merged 6 commits into
developfrom
default-liquid-node

Conversation

@andycreed0x

@andycreed0x andycreed0x commented Sep 17, 2026 •

Copy link
Copy Markdown
Collaborator

Walkthrough

Type: fix

Switches Liquid's default chain backend from Blockstream to AQUA's own Airavata electrs, with blockstream.info and liquid.network as ordered fallbacks (testnet previously had no fallback at all). A code review of the initial switch found the fallback logic itself was mostly dead: the transient-error matcher missed LWK's real failure shapes, there was no request timeout (~75s before even trying the next backend), and a retried broadcast could report an already-confirmed tx as a hard failure. This PR fixes those defects and extends the same ordered backend list to lw_tx_status, which previously stayed hardcoded to Blockstream.

Changes

File What changes
src/aqua/wallet.py Real transient-error detection (structural HTTP 5xx check, LWK parse errors, Electrum DNS failures) replacing dead status-text matches; explicit 15s request timeout; liquid.network added as a further fallback on both networks; per-backend concurrency (12 for Airavata, 3 for public backends); idempotent broadcast retry (already in block chain → success with the known txid, not an error); fallback clients built lazily inside the retry loop so one bad entry can't mask the real error
src/aqua/tools.py lw_tx_status now walks the same ordered backend list as the wallet (was hardcoded to blockstream.info); accepts liquid.network explorer URLs as input
src/aqua/cli/liquid.py tx-status --tx help text now says "Liquid explorer URL" instead of naming Blockstream specifically
docs/CONFIG.md Documents the per-network backend order, the concurrency/timeout rationale, and that an electrum_url override replaces the list with a single non-fallback backend
tests/test_wallet.py Coverage for the backend-fallback retry loop and broadcast idempotency (previously untested)
tests/test_tools.py Coverage for tx-status backend fallback and the new URL parsing

Flow

sequenceDiagram
    participant Client as MCP client
    participant Wallet as WalletManager.send()
    participant Airavata
    participant Blockstream
    participant LiquidNet as liquid.network

    Client->>Wallet: send(...)
    Wallet->>Airavata: broadcast(tx)
    Airavata-->>Wallet: timeout (15s)
    Wallet->>Blockstream: broadcast(tx)
    alt already relayed by Airavata
        Blockstream-->>Wallet: HTTP 400 "already in block chain"
        Wallet-->>Client: txid (treated as success)
    else still unreachable
        Blockstream-->>Wallet: connection reset
        Wallet->>LiquidNet: broadcast(tx)
        LiquidNet-->>Wallet: 200 OK
        Wallet-->>Client: txid
    end
Loading

lw_tx_status follows the same ordered fallback (Airavata → Blockstream → liquid.network on mainnet), so a status read reflects whatever backend a send actually went through.

Code review of the Airavata switch found the fallback rarely triggered in
practice: the transient-error matcher missed LWK's real failure shapes (a
reachable backend returning a non-JSON error page, Electrum DNS failures),
there was no request timeout (a blackholed host blocked ~75s before even
trying), and testnet had no fallback at all. A retried broadcast could also
surface "already in block chain" as a hard failure for a tx that was already
on-chain.

- Recognize LWK's actual transient error shapes (structural 5xx check,
  JsonFrom parse errors, Electrum DNS failures); drop dead HTTP-status string
  matches that LWK never produces.
- Add an explicit Esplora request timeout so a dead primary fails fast.
- Add liquid.network as a further fallback on both networks (testnet had
  none before).
- Scale Esplora concurrency down for public backends; keep it high only for
  our own electrs.
- Treat "already in block chain" / already-known errors on a retried
  broadcast as success, returning the known txid instead of raising.
- Build fallback clients lazily inside the retry loop so one bad entry can't
  mask the primary's real error or block later ones.
- lw_tx_status now walks the same backend list as the wallet (previously
  hardcoded to blockstream.info), so status reads reflect whatever backend a
  send actually went through; accepts liquid.network explorer URLs as input.
@andycreed0x
andycreed0x marked this pull request as ready for review September 23, 2026 18:20
Compress block comments and docstrings in wallet.py/tools.py down to 1-3
lines each. Durable rationale that had no other home (backend order,
concurrency, timeout, tx-status 404-retry) moved into docs/CONFIG.md's
"Liquid chain backends" section; implementation-detail comments (lwk error
shapes, Elements RPC codes) were compressed in place per this repo's own
convention of keeping third-party protocol semantics in code, not docs.

@TomasCast TomasCast left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, left 2 small comments found by cursor that did make sense to me.

Comment thread src/aqua/tools.py Outdated
Comment on lines +540 to +543
detail = "; ".join(failures)
if not_found:
raise _EsploraNotFound(detail)
raise ValueError(f"Could not reach any Liquid Esplora backend ({detail})")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

CONFIG.md says a 404 is reported only when every backend answered 404. The code reports not-found if any backend answered 404, even when others failed.

Scenario: send goes out through Airavata, the user checks status immediately, Airavata is down/timed out, and Blockstream has not seen the tx yet → Transaction not found for a tx that exists.

test_mixed_404_and_unreachable_reports_not_found_with_detail locks the bug in.

Raise _EsploraNotFound only when not_found covers every backend and failures is empty.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good catch, fixed

Comment thread src/aqua/wallet.py Outdated
Comment on lines +75 to +79
if isinstance(exc, lwk.LwkError.EsploraHttpError) and exc.status >= 500:
# The backend (or the proxy in front of it) failed, not the request.
return True
msg = str(exc).lower()
return any(marker in msg for marker in _TRANSIENT_MARKERS)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lw_tx_status treats HTTP 429 as a fallback (tests/test_tools.py covers it). LWK only treats status >= 500 as transient. A rate-limited Airavata makes send/sync fail without trying Blockstream / liquid.network.

Maybe it would be better to treat 429 as transient, same as in tools.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed, fixed

@coelhogonzalo coelhogonzalo changed the title Use Airabata Use Airavata Sep 23, 2026
… transient in LWK fallback

- lw_tx_status: a 404 while another backend was unreachable now raises
  'Could not reach any Liquid Esplora backend' instead of a false
  'Transaction not found' (matches docs/CONFIG.md).
- _is_transient_backend_error: HTTP 429 falls back to the next backend,
  same as lw_tx_status already did.
@andycreed0x
andycreed0x merged commit 67451a9 into develop Sep 29, 2026
1 check passed
@andycreed0x
andycreed0x deleted the default-liquid-node branch September 29, 2026 16:13
andycreed0x added a commit that referenced this pull request Oct 1, 2026
…'url' argument (#138)

* 🐛 fix: lightning_refund chain helpers after the Airavata backend switch

get_block_height, get_transaction_hex and broadcast_raw_tx (added in #134)
still called _get_client(network), whose signature #131 changed to
(network, url), so every refund raised TypeError.

Route them through the backend fallback, reuse the idempotent _broadcast,
and read tx hex over HTTP for Esplora backends: lwk's EsploraClient has no
get_tx, and every default Liquid backend is Esplora.

Closes #137

* 🐛 fix: get_transaction_hex Electrum misses and non-hex 200 bodies

Electrum: lwk's ElectrumClient.get_tx never returns None; a missing tx
raises LwkError ("missing transaction", probed live on Blockstream's
Liquid Electrum). The old None check was dead, so a miss escaped as a
raw LwkError instead of the "Transaction ... not found" ValueError.
Recognize the miss and treat it like an Esplora 404. The test now raises
what lwk really raises instead of returning None.

Esplora: a 200 whose body is not tx hex (a proxy HTML page, a JSON
error, an empty or non-UTF-8 body) was returned as the lockup hex and
failed later inside wally.tx_from_hex, without trying the next backend.
Validate the body, record the backend as failed and fall back.
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