Skip to content

fix(payout): fail-closed on empty post-broadcast tx ids (Lightning, Cardano, BTC, Monero, Zano)#4204

Open
TaprootFreak wants to merge 2 commits into
developfrom
fix/payout-empty-txid-fail-closed
Open

fix(payout): fail-closed on empty post-broadcast tx ids (Lightning, Cardano, BTC, Monero, Zano)#4204
TaprootFreak wants to merge 2 commits into
developfrom
fix/payout-empty-txid-fail-closed

Conversation

@TaprootFreak

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #4181. Closes residual double-payout paths where a broadcast can succeed but an empty/missing transaction identifier is treated as a pre-broadcast failure, causing handleBroadcastError to roll the order back to PREPARATION_CONFIRMED so the payout cron rebroadcasts.

Closes #4194.

Lightning (all address types)

  • After the LND send call, a missing, empty, or unparseable payment_hash now surfaces as TxBroadcastErrorPayoutBroadcastException (fail-closed).
  • Invoice payouts (LN_URL / LND_HUB) retry by fetching a new invoice, so LND payment_hash dedup does not protect a re-broadcast — the previous keysend-only guard was insufficient.
  • In-band payment_error (e.g. no route) remains a plain Error so invoice paths can still self-heal when the payment was provably not routed.
  • Replaced the unsafe unit test that asserted empty invoice hashes were safe to return unchanged.

Cardano

  • Empty/missing txSubmit result throws TxBroadcastError inside the broadcast boundary.

Same residual class (Bitcoin / Monero / Zano)

  • sendMany empty txid, Monero empty tx_hash, Zano empty tx_hash now throw TxBroadcastError instead of returning empty values that later roll back for rebroadcast.

Tests

  • Client/service unit tests for empty-id fail-closed on all touched chains.
  • Strategy regression: Lightning + Cardano empty-id PayoutBroadcastException leaves PAYOUT_DESIGNATED, no rollback, no second broadcast.

Test plan

  • Focused jest suite (Lightning/Cardano/BTC/Monero/Zano + designate-before-broadcast) green
  • npm run type-check green
  • eslint on changed files green
  • CI Build and test on the PR

…ardano, BTC, Monero, Zano)

Follow-up to #4181 / closes residual double-payout class from #4194.

After the on-chain send is reached, a missing/empty/unparseable transaction
identifier is ambiguous (funds may already have moved). Returning a plain
error caused handleBroadcastError to roll the order back to
PREPARATION_CONFIRMED so the cron could broadcast again.

- Lightning: map empty/missing payment_hash after LND send to TxBroadcastError
  for LN_URL, LND_HUB and LN_NID. Invoice retries fetch a NEW invoice, so
  payment_hash dedup does not protect a re-broadcast. In-band payment_error
  stays a plain Error (self-heal). PayoutLightningService fail-closes empty
  ids for all address types.
- Cardano: empty txSubmit result -> TxBroadcastError.
- Bitcoin sendMany / Monero / Zano: empty txid/tx_hash -> TxBroadcastError.
- Replace the unsafe invoice-dedup unit test; add strategy regressions that
  prove PAYOUT_DESIGNATED is kept with no rebroadcast.
CI format:check failed on the empty-txid regression test added for
the fail-closed sendMany guard.
@TaprootFreak

Copy link
Copy Markdown
Collaborator Author

Ready for review after 1 review pass (0 defects).

What landed before the review pass:

Verified: type-check, lint, focused payout/client tests, and CI (Build and test + CodeQL + review bot) green; commits GPG-signed (verified=true). No migration.

@TaprootFreak TaprootFreak marked this pull request as ready for review July 14, 2026 12:02
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.

fix(payout): fail closed on missing Lightning and Cardano transaction IDs

1 participant