Skip to content

[draft]: Introduce SQL schema for wallet#1110

Draft
yyforyongyu wants to merge 695 commits into
interface-walletfrom
sql-wallet
Draft

[draft]: Introduce SQL schema for wallet#1110
yyforyongyu wants to merge 695 commits into
interface-walletfrom
sql-wallet

Conversation

@yyforyongyu

Copy link
Copy Markdown
Collaborator

Open this PR to track rebase conflicts.

TODO: add descriptions and link PRs and issues.

@yyforyongyu
yyforyongyu force-pushed the sql-wallet branch 2 times, most recently from c93d0a1 to 8eade91 Compare November 12, 2025 14:10
@yyforyongyu
yyforyongyu force-pushed the sql-wallet branch 3 times, most recently from 0f68bb3 to 2c7f7a1 Compare November 24, 2025 01:54
@yyforyongyu
yyforyongyu force-pushed the sql-wallet branch 2 times, most recently from 04cfa4b to d6ffdfb Compare November 28, 2025 07:24
@yyforyongyu
yyforyongyu force-pushed the sql-wallet branch 4 times, most recently from 823ddd5 to 7bd08f6 Compare December 16, 2025 00:31
@GustavoStingelin
GustavoStingelin force-pushed the sql-wallet branch 2 times, most recently from 715a0c0 to aac7047 Compare December 22, 2025 17:52
@GustavoStingelin

Copy link
Copy Markdown
Collaborator

just rebased from master

@yyforyongyu

Copy link
Copy Markdown
Collaborator Author

Pushed a rebase to fix conflicts

@coveralls

coveralls commented Jan 22, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 28857699468

Warning

No base build found for commit 78ac6ae on interface-wallet.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 55.557%

Details

  • Patch coverage: Could not be determined — this PR's diff is too large for GitHub to return (406 error at GitHub).

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 57833
Covered Lines: 32130
Line Coverage: 55.56%
Coverage Strength: 14600.06 hits per line

💛 - Coveralls

@yyforyongyu
yyforyongyu changed the base branch from master to interface-wallet January 28, 2026 07:37
@yyforyongyu
yyforyongyu force-pushed the sql-wallet branch 3 times, most recently from 9778f28 to 1a85fc7 Compare February 11, 2026 09:00
@yyforyongyu
yyforyongyu force-pushed the sql-wallet branch 3 times, most recently from ee70821 to d3c67d4 Compare February 13, 2026 02:59
Add cross-backend coverage proving RewindWallet detaches only the requested wallet and leaves another wallet's transactions and sync state unchanged.
Use the wallet-scoped RewindWallet API for manual rescans, resolving height-only requests to the canonical block hash and header before recording the new sync tip.
DBPutRewind advances the live address manager's in-memory synced tip via
SetSyncedTo before txStore.Rollback inside one walletdb update. If the
rollback fails, walletdb reverts the bucket write but the in-memory tip
stays rewound to a never-persisted fork point. Snapshot the pre-rewind tip
and restore it via RestoreSyncedTo on failure, mirroring ApplyScanBatch's
synced-tip rollback. Successful rewinds are unchanged.
Use db.Store to load active unmined transactions for rebroadcast
while preserving the legacy read path for syncers without store
wiring.
Persist filtered-block notification batches through db.Store so
transaction updates and sync-tip advancement stay atomic at the
store boundary.
Assert connected-block notifications write one transaction batch
together with the matching sync-tip update through the Store.
Cover that a bare-multisig output owned through a member pubkey
survives the Store batch path instead of being dropped.
Cover that a relevant transaction carrying both a wallet-owned and
an unrelated third-party output credits only the owned output.
Add the helpers that convert recovery scan results into Store batch
params: horizon merging, horizon flattening, per-output tx params,
and the scan-result block shape.
Write recovery scan batches through db.Store so rescans persist
tx notifications, horizons, and synced blocks through one store
operation.
Route targeted recovery scan results through the Store batch API
without advancing the wallet's synced blocks.
Read the synced tip through syncedTip so checkRollback,
advanceChainSync, and scanWithRewind use the Store's tip rather than
the legacy addrStore tip in store-backed mode.
scanHorizonParams emitted db.ScanHorizon with an empty AccountName, so
ApplyScanBatch fell back to numeric account resolution. That is unsafe
for imported and imported-xpub accounts, whose Store-facing numbers are
masked to 0 and collide with the default derived account.

Thread the *RecoveryState into storeScanBatchParams and stamp every
emitted horizon with the durable account name resolved from the recovery
state, keeping the numeric fallback only for unexpected missing names.
Add coverage proving a confirmed chain.RelevantTx notification builds an
ApplyTxBatch whose transaction carries the confirming block while
SyncedTo stays nil, so the standalone notification records the confirmed
tx without advancing the wallet sync tip.
Add store-backed reads for recovery scan account horizons: ListAccounts
for full scans and GetAccount for targeted scans, converting store
account metadata into RecoveryState horizon shape. Every imported
account is withheld from the horizons, since its number is masked to 0
and would collide with the default derived account in the by-number
recovery lookahead. The wiring of these readers into the scan-state
loader follows in a later commit.
Cover storeScanHorizons over both the full-scan ListAccounts path and
the targeted GetAccount path, including that imported accounts are
withheld from the emitted recovery horizons.
Add store-backed loading of active recovery scan addresses through
paginated ListAddresses queries, extracting one address per stored
script.
Add store-backed loading of UTXOs that recovery scans should watch
through ListOutputsToWatch, converting each store UTXO row into the
recovery scan credit shape.
Aggregate store-backed horizon, address, and watch-output reads into
loadStoreScanData and route wallet and targeted scan-data loading
through the store when store wiring is available.
The Store-backed targeted rescan resolved each AccountScope by number in
storeScanHorizons. Both Store backends mask an imported account's number
to 0, so an imported-xpub target was indistinguishable from the default
derived account 0 and could seed the wrong horizon.

Resolve the public targets into identity-aware scanTargets up front
through the legacy manager, which still keys accounts by their non-masked
number. The keyless imported-address bucket is skipped before any Store
lookup; every other target carries its durable AccountName, and the Store
horizon load prefers the name, mirroring the ScanHorizon contract and the
SQL GetHorizonAccount behavior. A number lookup is never issued for an
imported target.

The legacy walletdb path is unchanged: it still resolves by number, which
it does not mask.
Add real-kvdb-backed coverage for the targeted rescan identity fix: a
syncer wired over a real, unlocked waddrmgr-backed Store.

The keyless imported-address bucket produces no horizon and issues no
number lookup (a real by-number lookup would surface ErrAccountNotFound).
A setup with the default derived account 0 and an imported-xpub account
masked to number 0 resolves the imported target by its durable name,
classifies it as imported, and withholds it, so it is never mis-resolved
as the default derived account.
The syncer runtime helpers were routed through the transitional Store but
kept an "if s.store == nil" branch that fell back to the legacy walletdb
DB* helpers, plus two positive "if s.store != nil" scan-data conditionals
that did the same. Production newSyncer is always constructed with the
wallet's Store, so nil Store is impossible on these migrated paths and the
fallback is dead, contradictory design: a migrated runtime path must always
use the Store.

Remove the nil-store fallbacks from rewindToBlock, syncedBlockHashes,
unminedTxns, updateSyncTip, putTxNotifications, putBlockNotifications,
putSyncBatch, putTargetedBatch, syncedTip, loadTargetedScanData, and
loadWalletScanData so each helper always uses the Store, and rewrite the
"store when configured, falling back to the legacy walletdb path" comments
to describe the Store-only behavior.

The legacy DB* helpers in db_ops.go are left in place for now; they are
removed once no non-deferred call site needs them.
newSyncer kept the Store optional behind a variadic syncerStoreConfig, but
the migrated runtime paths now always read and write through the Store, so a
nil Store is impossible. Drop the variadic and take store and walletID as
mandatory positional parameters.

Update every newSyncer call site mechanically to pass the Store: production
wiring in manager.go, plus the benchmark, db_ops, and syncer test setups.
The store-backed test bodies are rewritten in the following commits.
Rewrite the rollback, initialization, run-loop, run-step, wait, and
init-chain-sync tests to drive the syncer through the Store instead of the
legacy addrStore/walletdb setup: replace setupTestDB and mockAddrStore wiring
with walletmock.Store expectations, and add the expectSyncedTip helper that
stubs the synced tip via Store.GetWallet.

Drop TestLoadScanDataLegacyFallback and TestRewindToBlockFallsBackToLegacy,
which covered the now-removed nil-store fallbacks.
Rewrite the scan-strategy, scan-batch, scan-target, advance-chain-sync,
fetch-and-filter, dispatch, scan-data loading, and handle-scan-req tests to
source horizons, addresses, watched outputs, and the synced tip from the
Store via walletmock expectations, replacing the legacy addrStore and
walletdb-backed setup.
Rewrite the handle-chain-update, process-chain-update, and broadcast
unmined-txn tests to read the synced tip and unmined transactions from the
Store via walletmock expectations, and add the serializeTx helper that
encodes a transaction for stubbing TxInfo.SerializedTx.
resolveMasterFingerprint read the master HD public key with a direct
walletdb.View against the address manager during wallet load. The master
public key is public wallet metadata, so route it through Store.GetWallet
instead, removing the last direct walletdb transaction for public-metadata
reads from wallet/manager.go.

To serve the read, add MasterHDPubKey to the waddrmgr.AddrStore interface
(already implemented on *waddrmgr.Manager) and have kvdb's GetWallet fill
WalletInfo.MasterPubKey from it, tolerating ErrNoExist for shell, watch-only,
and pre-master-key wallets. The MasterHDPubKey read is extracted into a small
readMasterPubKey helper to keep GetWallet within the complexity limit.

The DBCreateWallet/DBLoadWallet create/load path is left as-is: kvdb's
Store.CreateWallet is unimplemented and the Store does not return the legacy
waddrmgr.Manager/wtxmgr.Store the runtime needs, so that remains justified
runtime-construction scope. Passphrase, private-key, and signer flows are
unchanged (deferred to ADR 0010).

TestManagerLoadSuccess now asserts the loaded wallet's cached master
fingerprint is non-zero and matches the create-time value, locking in the
Store-routed read.
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.

4 participants