Skip to content

silent payments: file-backed wallet store - #60

Merged
rustaceanrob merged 2 commits into
kernel-node:masterfrom
pzafonte:sp-wallet-store
Jun 1, 2026
Merged

silent payments: file-backed wallet store#60
rustaceanrob merged 2 commits into
kernel-node:masterfrom
pzafonte:sp-wallet-store

Conversation

@pzafonte

@pzafonte pzafonte commented May 27, 2026

Copy link
Copy Markdown
Contributor

Updated design:

Wallet gains save and load methods through FileExt. Each save writes the whole wallet to a binary file. To
avoid leaving a half-written file if the node crashes during a save, the save writes to a temporary file first and then renames it over the real one. WalletStore is type that holds the wallet file's path and the network the file should be for. It calls the wallet's save and load methods.

When the node starts, it loads the wallet. The scan thread saves the wallet through the store after every
block it processes. If a save fails, the node shuts down through FatalShutdown so it cannot keep scanning past data that never reached disk.

I have the sp-wallet-store-backup branch on my fork for reference.

@rustaceanrob

rustaceanrob commented May 28, 2026

Copy link
Copy Markdown
Contributor

Although we will probably want to move this way at some point in the future, given how simple our use case is, this might be overkill (thanks for demonstrating regardless). The way it integrates with the node and wallet appears awkward to me. I believe you had a PR that was more in line with what I'm about to recommend, so hopefully there's some code already present for these changes

  1. I prefer we use our file extension trait and just implement save and load for Wallet. This means we will re-write the coins to the file on every save, but since this occurs on a separate thread and the file won't be big enough for this to have a significant impact until someone has hundreds of thousands of coins, this seems fine to me. It will greatly simplify the logic for our "database" and doesn't require changes to the scan and disonnect functions.
  2. No new WalletState struct should have to be introduced. We can pass the file to the scanning thread and save each time there is a change using the extension trait. I can't imagine a user that would import keys and not want the result saved, so I think we should just store found coins on disk for all users. That should make the "integration" part nearly trivial. I'm fine with file I/O failures being fatal.

@pzafonte

Copy link
Copy Markdown
Contributor Author

Got it. I didn't have a clear sense of scale so I was making trade-offs that went beyond what we actually need. The separation between wallet code and file code, and the cost of rewriting the file, both matter less than I assumed. Easy enough to redo using the save/load pattern from the keys file PR.

@pzafonte

Copy link
Copy Markdown
Contributor Author

Pushed the rework. Updated the PR description to reflect the changes.

Comment thread crates/wallet/src/silentpayments/wallet_store.rs Outdated
Comment thread crates/wallet/src/silentpayments/wallet_store.rs Outdated
Comment thread src/bin/node.rs Outdated
pzafonte added 2 commits May 31, 2026 13:55
The wallet keeps scan progress and found coins only in memory, so
a node restart re-scans from genesis. Add a `wallet_store` module
that persists the wallet to disk.

`Wallet` implements the file extension trait. Save serializes the
whole wallet to a small binary file. Load reads it back. Each
save writes to a `.tmp` file, flushes it, then renames it over
the wallet file so a crash mid-write leaves the previous file
intact.

`WalletStore` pairs the wallet file path with the configured
network. Save and load delegate to `Wallet::save` / `Wallet::load`.
Wire the wallet save/load through the node binary so wallet state
actually survives restarts.

At startup, the node builds a `WalletStore` from
`<datadir>/wallet.bin` and the configured network. If the file
exists, it is loaded through the store. Otherwise a fresh wallet
is created. Any load failure exits with an error rather than
scanning into the wrong file.

The scan thread saves the wallet through the store after every
block it processes. If a save fails, the thread escalates
through `FatalShutdown` so the node goes down before the next
block can advance `scan_height` past data that never reached
disk.
@rustaceanrob

Copy link
Copy Markdown
Contributor

Is this ready for review?

@pzafonte

Copy link
Copy Markdown
Contributor Author

Is this ready for review?

I believe so, the only major design I was stuck on was wallet updates and that's clarified now. I did some end to end tests locally and it appears to work.

@pzafonte
pzafonte marked this pull request as ready for review May 31, 2026 19:19

@rustaceanrob rustaceanrob left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sent a payment and did a few startup/shutdowns. Works as expected.

@rustaceanrob
rustaceanrob merged commit ed8088e into kernel-node:master Jun 1, 2026
1 check passed
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.

2 participants