Skip to content

feat(cycles): manual-route circular self-payment (superset of PR #3 swaps)#5

Open
tonible14012002 wants to merge 10 commits into
lamtuanvu:custom-gossip-datafrom
tonible14012002:cycles
Open

feat(cycles): manual-route circular self-payment (superset of PR #3 swaps)#5
tonible14012002 wants to merge 10 commits into
lamtuanvu:custom-gossip-datafrom
tonible14012002:cycles

Conversation

@tonible14012002

Copy link
Copy Markdown

Builds on PR #3 (native PeerSwap swaps primitives B1–B7 + bitcoind txid byte-order fix) and adds the cycles feature: the manual-route circular self-payment primitive required by the cooperative cycle-balance engine.

Same base branch as PR #3 (custom-gossip-data), so this PR contains all of PR #3's changes plus the two cycle commits — it is a superset and can supersede PR #3.

New commits on top of PR #3

  • f16ef0b feat(cycles): manual-route circular self-payment behind the cycles feature
  • 630e5e1 fix(cycles): make the rebalance loop actually receivable + pin the claim amount

What the cycles feature adds (all #[cfg(feature = "cycles")], default OFF)

  • Node::send_along_route(route, amount_msat, payment_hash, preimage) (src/lib.rs) — sends a Lightning payment along a caller-supplied Route (no pathfinding, no route hints) and registers the hash with the stateless inbound-payment verifier (create_inbound_payment_for_hash, min_value = amount) so the looped HTLC is receivable at the final hop without creating an inbound payment-store record (LDK's circular-payment guard still sees only the single Outbound record).
  • PaymentKind::Rebalance { hash, preimage } (src/payment/store.rs) — a new TLV enum variant at type 12, additive and forward/backward-compatible. Ungated so the serialization stays stable; only ever written by send_along_route.
  • PaymentClaimable fall-through + inline self-claim (src/event.rs) — the circular-payment and duplicate/spontaneous refusals gain && !info.is_rebalance(), and a Rebalance loop is claimed immediately with the record-carried preimage (belt-and-braces amount pin: never reveal the preimage for less than the recorded loop amount).

Compatibility

  • No lightning-crate version bumpChannelMonitor / KVStore / wallet persistence format unchanged.
  • No change to seed derivation, the on-chain BDK wallet descriptor, or node identity.
  • Both swaps and cycles are empty Cargo features, default OFF → the stock build is byte-for-byte unaffected.

The preimage is generated natively, held only in the fork's payment record, and never crosses FFI, is never logged, and never appears in any payload. No BOLT11 invoice is minted.

lamtuanvu and others added 10 commits July 3, 2025 21:48
The ChannelFunding confirmation target resolved to a 12-block fee tier,
which during normal mempool congestion maps to a rate low enough that
funding transactions can sit unconfirmed for hours. The channel never
reaches channel_ready and the UI is stuck on `sync`.

Lower the target to ~3 blocks (mempool's "fast" tier) so funding txs are
mined promptly. The fee is still sourced from the chain source's
recommended estimates (esplora get_fee_estimates / electrum estimatefee /
bitcoind estimatesmartfee) — only the targeted confirmation window
changes, and only for funding transactions.

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

getrawtransaction wants the txid in RPC/display (big-endian) order (Txid Display),
but serialize_hex(txid) emits internal little-endian (reversed) bytes → bitcoind
returns -5, mapped to Ok(None)=NotFound. The native swap watcher therefore never
saw a confirmed opening tx on the bitcoind backend, so the CSV/confirmation ladder
never armed and swaps wedged at AWAIT_CONFIRM/AWAIT_CLAIM_PAYMENT. Fix: pass
txid.to_string() (display order). Proven on regtest (display txid=10 confs, reversed=-5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…swaps` feature

Add the on-chain building blocks the native PeerSwap engine (modules/ldk-node in
the consumer) needs, all gated behind a new `swaps` cargo feature so the default
build is byte-for-byte unaffected:

- B1–B3 funding: create_swap_funding_tx (P2WSH HTLC opening, signed, not broadcast),
  swap_list_confirmed_utxos, swap_sign_psbt (wallet/mod.rs).
- B4 broadcast: broadcast_swap_tx over the bounded broadcast queue (tx_broadcaster.rs).
- B5 reorg-aware per-txid confirmation tracking: watch_txid + get_tx_confirmations
  → TxStatus/ChainStatus + derive_tx_status, with a swap_query_tx backed by whichever
  chain source is configured (Esplora/Electrum/Bitcoind) and FAIL-CLOSED
  (NoChainSource) when it cannot answer (chain/mod.rs, chain/electrum.rs).
- B6 feerate: estimate_onchain_feerate → source-bearing FeerateQuote so callers can
  refuse a stale/fallback estimate (fee_estimator.rs).
- B7 discovery: swap-capability custom gossip plumbing (custom_gossip.rs).
- Builder wiring (builder.rs), public surface (lib.rs).

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

Adds the cooperative cycle-balance primitive: `Node::send_along_route(route,
amount_msat, payment_hash, preimage)` sends a spontaneous payment along a
caller-supplied route back to self, recorded as `PaymentKind::Rebalance`
(TLV type 12, ungated for record compatibility). The `PaymentClaimable`
handler scopes the circular-payment and spontaneous-duplicate guards to
exclude Rebalance records and claims the looped HTLC inline with the
locally-held preimage, marking the single outbound record Succeeded —
settlement is observed by polling the payment store, no user-facing event
is emitted. Only the `Node` method is gated behind the new `cycles`
feature; the default build is unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…aim amount

send_along_route sent the final onion with RecipientOnionFields::
spontaneous_empty() and no keysend TLV, so lightning's final-hop parser
(create_recv_pending_htlc_info) failed every looped HTLC with "We
require payment_secrets" BEFORE PaymentClaimable could fire — the
patched claim path was unreachable and no cycle could ever settle
(fail-safe, but feature-dead). Fix: register the hash with the
ChannelManager's STATELESS inbound-payment verifier
(create_inbound_payment_for_hash, min_value_msat = amount) and send
secret_only(payment_secret). No payment-store record is created, so the
scoped circular guard still sees only the single Outbound Rebalance
record; the secret never leaves the onion we build, so nobody else can
construct a claimable HTLC for the hash.

event.rs: belt-and-braces amount pin in the Rebalance claim — never
claim_funds (= reveal the preimage) for less than the recorded loop
amount; fail the HTLC backwards instead.

Co-Authored-By: Claude Fable 5 <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.

3 participants