Skip to content

Rebalancer permanently misses an on-chain receive when its Confirmed update lands one sync cycle late #98

Description

@hash-money

Summary

needs_onchain_rebalance (orange-sdk/src/rebalancer.rs @ 5a540f66) selects sweep-trigger payments with a timestamp window: latest_update_timestamp > onchain_sync_time (lines 163 and 211), while onchain_sync_time is unconditionally advanced to the node's latest_onchain_wallet_sync_timestamp on every tick that observes a new sync (line 173). If a payment's Confirmed update is first visible on a tick where the stored onchain_sync_time has already advanced past that payment's latest_update_timestamp, the payment fails the window on that tick — and every later tick, because the stored time only grows. The wallet then loops "Detected onchain sync with balance updates, but no new onchain payments found" (line 261) with a spendable balance above rebalance_min that is never swept.

Reproduction / evidence (regtest, bitcoind v29 chain source)

While hardening our integration battery we hit this as a ~1-in-4 flake on "fund on-chain → auto-sweep opens LSP channel", and isolated it:

  • Fund a fresh wallet on-chain, confirm the deposit, then stop producing blocks. If the race is lost, the wallet logs the line-261 warning on every subsequent sync and never rebalances.
  • The miss is permanent: neither restarting the wallet nor sending a fresh unrelated on-chain nudge recovers the original payment (the new payment can trigger its own sweep of the whole spendable balance, but only if its update wins the race).
  • Continuous block production masks the bug: each new confirmation refreshes latest_update_timestamp, giving the payment repeated chances to pass the window. That's why it looks like a rare flake on regtest and would look like sporadic "stuck on-chain funds" in the field — e.g. a mobile app backgrounded/offline across the confirmation, syncing after the update timestamp is already behind the stored sync time.

Why this matters

The on-chain→Lightning sweep is the load-bearing behavior of the two-tier model: a missed sweep leaves user funds resting on-chain indefinitely with no user-visible error and no recovery path short of another deposit.

Suggested fix shape

Eligibility should be state-based rather than timestamp-window-based: a payment that is Inbound + Succeeded + Onchain Confirmed and not already promoted is a valid trigger regardless of when its update landed. The dedupe needed for that already exists in this function — the can_mark_as_trigger check against tx_metadata (lines 222-226) — so the latest_update_timestamp > onchain_sync_time clause at line 211 could be dropped (or kept only as a fast-path) with the metadata check preventing re-promotion. The event-emission filter at line 163 needs the same treatment or a parallel dedupe to avoid duplicate OnchainPaymentReceived events.

Happy to submit a PR along those lines from our fork if that's welcome.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions