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.
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), whileonchain_sync_timeis unconditionally advanced to the node'slatest_onchain_wallet_sync_timestampon every tick that observes a new sync (line 173). If a payment'sConfirmedupdate is first visible on a tick where the storedonchain_sync_timehas already advanced past that payment'slatest_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 aboverebalance_minthat 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:
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 Confirmedand not already promoted is a valid trigger regardless of when its update landed. The dedupe needed for that already exists in this function — thecan_mark_as_triggercheck againsttx_metadata(lines 222-226) — so thelatest_update_timestamp > onchain_sync_timeclause 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 duplicateOnchainPaymentReceivedevents.Happy to submit a PR along those lines from our fork if that's welcome.