upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBitflash <btf01@proton.me>2026-08-01 14:48:27 -0300
committerGitHub <noreply@github.com>2026-08-01 14:48:27 -0300
commitb65c19e559e2fec511f40a4d5481bcf6a35c311c (patch)
treee54c0c58d44c46befae58924b1df913816b569e9
parentae81fa37123730982c4e054d7433c9d0c944a373 (diff)
wallet: walk the chain for coins a key owns but the wallet never saw (#88)
A transaction only entered the wallet when its block arrived over the network, so a key added afterwards was invisible: coins on the chain, private key in wallet.dat, balance zero. /importwallet has always ended that way -- it writes the keys and the money never shows up, which looks exactly like a failed import. ScanForWalletTransactions walks the main chain and hands every transaction that IsMine() to AddToWalletIfMine, the same two primitives the live path uses. Wired into /importwallet, and offered as /rescan. It is also the half that any recovery-phrase feature has to stand on: deriving keys from twelve words is easy, and without this a restore restores nothing.
-rw-r--r--src/main.cpp49
-rw-r--r--src/main.h5
-rw-r--r--src/main_gui.cpp26
3 files changed, 80 insertions, 0 deletions
diff --git a/src/main.cpp b/src/main.cpp
index b027cef..68ae44c 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -305,6 +305,55 @@ bool AddToWalletIfMine(const CTransaction& tx, const CBlock* pblock)
305 return true; 305 return true;
306} 306}
307 307
308int ScanForWalletTransactions(CBlockIndex* pindexStart)
309{
310 // Walk the chain looking for transactions that belong to keys this wallet
311 // holds now, whatever it held when those blocks arrived.
312 //
313 // Nothing in this tree could do that. A key added after the fact -- from
314 // /importwallet, or from a backup restored onto a node that had since
315 // moved on -- was simply invisible: the coins were on the chain, the
316 // private key was in wallet.dat, and the balance did not show them,
317 // because a transaction only ever entered the wallet at the moment its
318 // block arrived over the network.
319 //
320 // Bitcoin grew the same function for the same reason. It is also what any
321 // recovery-phrase feature has to stand on: deriving the keys is the easy
322 // half, and without this the other half does not exist.
323 int nFound = 0;
324 int nScanned = 0;
325 CBlockIndex* pindex = pindexStart;
326 CRITICAL_BLOCK(cs_main)
327 {
328 while (pindex)
329 {
330 CBlock block;
331 if (block.ReadFromDisk(pindex, true))
332 {
333 foreach(CTransaction& tx, block.vtx)
334 {
335 // Ask before handing it over: AddToWalletIfMine returns
336 // true for a transaction that is none of our business, so
337 // its return value cannot be counted.
338 if (tx.IsMine())
339 {
340 if (AddToWalletIfMine(tx, &block))
341 nFound++;
342 }
343 }
344 }
345 nScanned++;
346 if ((nScanned % 500) == 0)
347 printf("ScanForWalletTransactions() : %d blocks scanned, %d transaction(s) found\n",
348 nScanned, nFound);
349 pindex = pindex->pnext;
350 }
351 }
352 printf("ScanForWalletTransactions() : %d block(s) scanned, %d transaction(s) added or updated\n",
353 nScanned, nFound);
354 return nFound;
355}
356
308bool EraseFromWallet(uint256 hash) 357bool EraseFromWallet(uint256 hash)
309{ 358{
310 CRITICAL_BLOCK(cs_mapWallet) 359 CRITICAL_BLOCK(cs_mapWallet)
diff --git a/src/main.h b/src/main.h
index 1d9efac..8d9b7e6 100644
--- a/src/main.h
+++ b/src/main.h
@@ -152,6 +152,11 @@ void TopUpKeyPool();
152vector<unsigned char> GetKeyFromPool(); 152vector<unsigned char> GetKeyFromPool();
153bool AddToWallet(const CWalletTx& wtxIn); 153bool AddToWallet(const CWalletTx& wtxIn);
154void ReacceptWalletTransactions(); 154void ReacceptWalletTransactions();
155// Walk the chain from pindexStart forward, adding every transaction that pays
156// a key this wallet holds. Returns how many were added or updated. Needed by
157// anything that puts a key into the wallet after the fact -- an import, a
158// restored backup, and later a recovery phrase.
159int ScanForWalletTransactions(CBlockIndex* pindexStart);
155// Returns how many wallet transactions the chain corrected. 160// Returns how many wallet transactions the chain corrected.
156int RescanSpentFlags(); 161int RescanSpentFlags();
157void RelayWalletTransactions(); 162void RelayWalletTransactions();
diff --git a/src/main_gui.cpp b/src/main_gui.cpp
index 5c87fe0..c9e27a9 100644
--- a/src/main_gui.cpp
+++ b/src/main_gui.cpp
@@ -76,6 +76,8 @@ static void PrintUsage()
76 printf(" /dumpwallet=FILE (export private keys as text -- readable by\n"); 76 printf(" /dumpwallet=FILE (export private keys as text -- readable by\n");
77 printf(" anyone, so guard it like cash)\n"); 77 printf(" anyone, so guard it like cash)\n");
78 printf(" /importwallet=FILE (load keys from such a file back in)\n"); 78 printf(" /importwallet=FILE (load keys from such a file back in)\n");
79 printf(" /rescan (walk the chain for coins this wallet owns\n");
80 printf(" but never recorded, then exit)\n");
79 printf("\n"); 81 printf("\n");
80 printf("Each option also accepts '-' instead of '/'.\n"); 82 printf("Each option also accepts '-' instead of '/'.\n");
81} 83}
@@ -307,13 +309,37 @@ int main(int argc, char* argv[])
307 int nAdded = 0, nSkipped = 0; 309 int nAdded = 0, nSkipped = 0;
308 bool fOk = ImportWallet(strImport, nAdded, nSkipped); 310 bool fOk = ImportWallet(strImport, nAdded, nSkipped);
309 if (fOk) 311 if (fOk)
312 {
310 printf("Imported %d key(s); %d were already here.\n", nAdded, nSkipped); 313 printf("Imported %d key(s); %d were already here.\n", nAdded, nSkipped);
314 // Importing a key without looking for its coins leaves the user
315 // holding a wallet that says zero about money that is on the
316 // chain. That was the old behaviour and it looks exactly like the
317 // import having failed.
318 if (nAdded > 0)
319 {
320 printf("Looking through the chain for coins belonging to the imported key(s)...\n");
321 int nFound = ScanForWalletTransactions(pindexGenesisBlock);
322 printf("Found %d transaction(s). Start the node normally to see the balance.\n", nFound);
323 }
324 }
311 else 325 else
312 fprintf(stderr, "Import failed.\n"); 326 fprintf(stderr, "Import failed.\n");
313 DBFlush(true); 327 DBFlush(true);
314 return fOk ? 0 : 1; 328 return fOk ? 0 : 1;
315 } 329 }
316 330
331 // /rescan -- walk the chain and pick up anything the wallet's keys own but
332 // the wallet never recorded. Cheap to offer and the only recourse when a
333 // balance is wrong for this reason.
334 if (arg(argc,argv,"/rescan") || arg(argc,argv,"-rescan"))
335 {
336 printf("Rescanning the chain for this wallet's transactions...\n");
337 int nFound = ScanForWalletTransactions(pindexGenesisBlock);
338 printf("Rescan done: %d transaction(s) added or updated.\n", nFound);
339 DBFlush(true);
340 return 0;
341 }
342
317 ReacceptWalletTransactions(); 343 ReacceptWalletTransactions();
318 344
319 if (!StartNode(strErrors)) { fprintf(stderr,"StartNode: %s\n",strErrors.c_str()); return 1; } 345 if (!StartNode(strErrors)) { fprintf(stderr,"StartNode: %s\n",strErrors.c_str()); return 1; }