Skip to content

Wallet birthday height for seed recovery on pruned nodes #818

Closed

Description

@FreeOnlineUser

When restoring an ldk-node wallet from seed, the node starts syncing from the current chain tip. Any on-chain deposits made before the restore are never seen by synchronize_listeners, so the wallet balance shows zero even though funds exist on-chain.

This affects all seed restores, but pruned nodes are hit hardest: historical blocks are gone, so there is no workaround via rescan.

There is an existing TODO acknowledging this:

// TODO: Use a proper wallet birthday once BDK supports it.

(src/builder.rs:1327)

Impact

  • On-chain balance shows 0 after seed restore
  • Cannot open Lightning channels (no visible funds)
  • Funds are safe on-chain but inaccessible through ldk-node
  • Mobile wallets (where reinstalls and data loss are common) are the primary use case

What we did

We hit this building Bitcoin Pocket Node, an Android app running bitcoind + ldk-node on GrapheneOS with pruned storage.

Our workaround overrides chain_tip_opt in the Builder when a birthday height is provided. Caller flow:

  1. User restores seed words
  2. App uses bitcoind scantxoutset to find UTXOs and their block heights (works on pruned nodes)
  3. Sets birthday to min(heights) - 10 as safety margin
  4. ldk-node syncs from there, balance appears with zero transaction fees

Working fork with the changes (~100 lines): FreeOnlineUser/ldk-node@upstream/wallet-birthday

Verified end-to-end on real hardware: seed restore, UTXO scan, birthday set, 110,628 sats recovered in seconds.

Upstream context

BDK is working on related pieces:

  • bdk#2126: Adding start_height to FilterIter::new
  • bdk#2050: Pruned node support via scantxoutset

Once BDK ships native birthday support, ldk-node can use that directly (which is what the TODO anticipates). Our fork is a pragmatic solution in the meantime.

We are also the folks working on the watchtower improvements discussed in #813.

Activity

  1. tnull commented on Mar 6, 2026

    @tnull
    Collaborator

    Note this was addressed in #769 where we added a recovery_mode flag to the builder which restores the ability to resync from genesis when importing a pre-existing wallet. Otherwise the wallet birthday is set to the current block height at first run. I guess we could add the ability to specify a specific block height instead. Would you prefer that over a simple bool flag?

  2. FreeOnlineUser commented on Mar 7, 2026

    @FreeOnlineUser
    Author

    Thanks for the pointer to #769! The recovery_mode flag helps for full nodes, but for pruned nodes resyncing from genesis isn't possible since historical blocks are gone.

    A specific block height would be ideal for our use case. The flow we use:

    1. User restores seed
    2. scantxoutset finds UTXOs and their block heights (works on pruned nodes)
    3. Set birthday to min(heights) - 10
    4. ldk-node syncs from that height forward

    With just the bool flag, a pruned node would fail trying to resync blocks it doesn't have. A height parameter lets us skip straight to where the funds are.

    So yes, the ability to specify a block height would be much preferred over the bool flag for this use case.

  3. added a commit that references this issue on Mar 10, 2026
    df8f084
  4. added a commit that references this issue on Mar 19, 2026
    172b44e
  5. tnull commented on Mar 27, 2026

    @tnull
    Collaborator

    Tagging this for 0.8 so we make sure to revisit, though given the release is approaching fast I can't promise it won't end up slipping.

  6. added this to the 0.8 milestone on Mar 27, 2026
  7. FreeOnlineUser commented on Mar 27, 2026

    @FreeOnlineUser
    Author

    Thanks for tagging it. PR #822 is rebased on current main whenever you're ready to look at it.

  8. added a commit that references this issue on Jun 3, 2026
    ae6f3f3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions