@@ -164,6 +164,7 @@ bool RemoveWallet(WalletContext& context, const std::shared_ptr<CWallet>& wallet
164164
165165 interfaces::Chain& chain = wallet->chain ();
166166 std::string name = wallet->GetName ();
167+ WITH_LOCK (wallet->cs_wallet , wallet->WriteBestBlock ());
167168
168169 // Unregister with the validation interface which also drops shared pointers.
169170 wallet->m_chain_notifications_handler .reset ();
@@ -652,15 +653,20 @@ bool CWallet::ChangeWalletPassphrase(const SecureString& strOldWalletPassphrase,
652653 return false ;
653654}
654655
655- void CWallet::chainStateFlushed (ChainstateRole role, const CBlockLocator& loc )
656+ void CWallet::SetLastBlockProcessedInMem ( int block_height, uint256 block_hash )
656657{
657- // Don't update the best block until the chain is attached so that in case of a shutdown,
658- // the rescan will be restarted at next startup.
659- if (m_attaching_chain || role == ChainstateRole::BACKGROUND) {
660- return ;
661- }
662- WalletBatch batch (GetDatabase ());
663- batch.WriteBestBlock (loc);
658+ AssertLockHeld (cs_wallet);
659+
660+ m_last_block_processed = block_hash;
661+ m_last_block_processed_height = block_height;
662+ }
663+
664+ void CWallet::SetLastBlockProcessed (int block_height, uint256 block_hash)
665+ {
666+ AssertLockHeld (cs_wallet);
667+
668+ SetLastBlockProcessedInMem (block_height, block_hash);
669+ WriteBestBlock ();
664670}
665671
666672void CWallet::SetMinVersion (enum WalletFeature nVersion, WalletBatch* batch_in)
@@ -1377,15 +1383,16 @@ void CWallet::RecursiveUpdateTxState(WalletBatch* batch, const Txid& tx_hash, co
13771383 }
13781384}
13791385
1380- void CWallet::SyncTransaction (const CTransactionRef& ptx, const SyncTxState& state, bool update_tx, bool rescanning_old_block)
1386+ bool CWallet::SyncTransaction (const CTransactionRef& ptx, const SyncTxState& state, bool update_tx, bool rescanning_old_block)
13811387{
13821388 if (!AddToWalletIfInvolvingMe (ptx, state, update_tx, rescanning_old_block))
1383- return ; // Not one of ours
1389+ return false ; // Not one of ours
13841390
13851391 // If a transaction changes 'conflicted' state, that changes the balance
13861392 // available of the outputs it spends. So force those to be
13871393 // recomputed, also:
13881394 MarkInputsDirty (ptx);
1395+ return true ;
13891396}
13901397
13911398void CWallet::transactionAddedToMempool (const CTransactionRef& tx) {
@@ -1472,18 +1479,25 @@ void CWallet::blockConnected(ChainstateRole role, const interfaces::BlockInfo& b
14721479 assert (block.data );
14731480 LOCK (cs_wallet);
14741481
1475- m_last_block_processed_height = block.height ;
1476- m_last_block_processed = block.hash ;
1482+ // Update the best block in memory first. This will set the best block's height, which is
1483+ // needed by MarkConflicted.
1484+ SetLastBlockProcessedInMem (block.height , block.hash );
14771485
14781486 // No need to scan block if it was created before the wallet birthday.
14791487 // Uses chain max time and twice the grace period to adjust time for block time variability.
14801488 if (block.chain_time_max < m_birth_time.load () - (TIMESTAMP_WINDOW * 2 )) return ;
14811489
14821490 // Scan block
1491+ bool wallet_updated = false ;
14831492 for (size_t index = 0 ; index < block.data ->vtx .size (); index++) {
1484- SyncTransaction (block.data ->vtx [index], TxStateConfirmed{block.hash , block.height , static_cast <int >(index)});
1493+ wallet_updated |= SyncTransaction (block.data ->vtx [index], TxStateConfirmed{block.hash , block.height , static_cast <int >(index)});
14851494 transactionRemovedFromMempool (block.data ->vtx [index], MemPoolRemovalReason::BLOCK);
14861495 }
1496+
1497+ // Update on disk if this block resulted in us updating a tx, or periodically every 144 blocks (~1 day)
1498+ if (wallet_updated || block.height % 144 == 0 ) {
1499+ WriteBestBlock ();
1500+ }
14871501}
14881502
14891503void CWallet::blockDisconnected (const interfaces::BlockInfo& block)
@@ -1495,9 +1509,6 @@ void CWallet::blockDisconnected(const interfaces::BlockInfo& block)
14951509 // be unconfirmed, whether or not the transaction is added back to the mempool.
14961510 // User may have to call abandontransaction again. It may be addressed in the
14971511 // future with a stickier abandoned state or even removing abandontransaction call.
1498- m_last_block_processed_height = block.height - 1 ;
1499- m_last_block_processed = *Assert (block.prev_hash );
1500-
15011512 int disconnect_height = block.height ;
15021513
15031514 for (size_t index = 0 ; index < block.data ->vtx .size (); index++) {
@@ -1531,6 +1542,9 @@ void CWallet::blockDisconnected(const interfaces::BlockInfo& block)
15311542 }
15321543 }
15331544 }
1545+
1546+ // Update the best block
1547+ SetLastBlockProcessed (block.height - 1 , *Assert (block.prev_hash ));
15341548}
15351549
15361550void CWallet::updatedBlockTip ()
@@ -2885,6 +2899,8 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
28852899 !walletInstance->IsWalletFlagSet (WALLET_FLAG_BLANK_WALLET);
28862900 if (fFirstRun )
28872901 {
2902+ LOCK (walletInstance->cs_wallet );
2903+
28882904 // ensure this wallet.dat can only be opened by clients supporting HD with chain split and expects no default key
28892905 walletInstance->SetMinVersion (FEATURE_LATEST);
28902906
@@ -2894,7 +2910,6 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
28942910 assert (walletInstance->IsWalletFlagSet (WALLET_FLAG_DESCRIPTORS));
28952911
28962912 if ((wallet_creation_flags & WALLET_FLAG_EXTERNAL_SIGNER) || !(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
2897- LOCK (walletInstance->cs_wallet );
28982913 if (walletInstance->IsWalletFlagSet (WALLET_FLAG_DESCRIPTORS)) {
28992914 walletInstance->SetupDescriptorScriptPubKeyMans ();
29002915 // SetupDescriptorScriptPubKeyMans already calls SetupGeneration for us so we don't need to call SetupGeneration separately
@@ -2910,7 +2925,10 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
29102925 }
29112926
29122927 if (chain) {
2913- walletInstance->chainStateFlushed (ChainstateRole::NORMAL, chain->getTipLocator ());
2928+ std::optional<int > tip_height = chain->getHeight ();
2929+ if (tip_height) {
2930+ walletInstance->SetLastBlockProcessed (*tip_height, chain->getBlockHash (*tip_height));
2931+ }
29142932 }
29152933 } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) {
29162934 // Make it impossible to disable private keys after creation
@@ -3112,11 +3130,6 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
31123130 // be pending on the validation-side until lock release. Blocks that are connected while the
31133131 // rescan is ongoing will not be processed in the rescan but with the block connected notifications,
31143132 // so the wallet will only be completeley synced after the notifications delivery.
3115- // chainStateFlushed notifications are ignored until the rescan is finished
3116- // so that in case of a shutdown event, the rescan will be repeated at the next start.
3117- // This is temporary until rescan and notifications delivery are unified under same
3118- // interface.
3119- walletInstance->m_attaching_chain = true ; // ignores chainStateFlushed notifications
31203133 walletInstance->m_chain_notifications_handler = walletInstance->chain ().handleNotifications (walletInstance);
31213134
31223135 // If rescan_required = true, rescan_height remains equal to 0
@@ -3193,15 +3206,21 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
31933206
31943207 {
31953208 WalletRescanReserver reserver (*walletInstance);
3196- if (!reserver.reserve () || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions (chain.getBlockHash (rescan_height), rescan_height, /* max_height=*/ {}, reserver, /* fUpdate=*/ true , /* save_progress=*/ true ).status )) {
3209+ if (!reserver.reserve ()) {
3210+ error = _ (" Failed to acquire rescan reserver during wallet initialization" );
3211+ return false ;
3212+ }
3213+ ScanResult scan_res = walletInstance->ScanForWalletTransactions (chain.getBlockHash (rescan_height), rescan_height, /* max_height=*/ {}, reserver, /* fUpdate=*/ true , /* save_progress=*/ true );
3214+ if (ScanResult::SUCCESS != scan_res.status ) {
31973215 error = _ (" Failed to rescan the wallet during initialization" );
31983216 return false ;
31993217 }
3218+ // Set and update the best block record
3219+ // Set last block scanned as the last block processed as it may be different in case the case of a reorg.
3220+ // Also save the best block locator because rescanning only updates it intermittently.
3221+ walletInstance->SetLastBlockProcessed (*scan_res.last_scanned_height , scan_res.last_scanned_block );
32003222 }
3201- walletInstance->m_attaching_chain = false ;
3202- walletInstance->chainStateFlushed (ChainstateRole::NORMAL, chain.getTipLocator ());
32033223 }
3204- walletInstance->m_attaching_chain = false ;
32053224
32063225 return true ;
32073226}
@@ -3261,14 +3280,7 @@ void CWallet::postInitProcess()
32613280
32623281bool CWallet::BackupWallet (const std::string& strDest) const
32633282{
3264- if (m_chain) {
3265- CBlockLocator loc;
3266- WITH_LOCK (cs_wallet, chain ().findBlock (m_last_block_processed, FoundBlock ().locator (loc)));
3267- if (!loc.IsNull ()) {
3268- WalletBatch batch (GetDatabase ());
3269- batch.WriteBestBlock (loc);
3270- }
3271- }
3283+ WITH_LOCK (cs_wallet, WriteBestBlock ());
32723284 return GetDatabase ().Backup (strDest);
32733285}
32743286
@@ -4197,11 +4209,6 @@ util::Result<MigrationResult> MigrateLegacyToDescriptor(const std::string& walle
41974209 return util::Error{_ (" Error: This wallet is already a descriptor wallet" )};
41984210 }
41994211
4200- // Flush chain state before unloading wallet
4201- CBlockLocator locator;
4202- WITH_LOCK (wallet->cs_wallet , context.chain ->findBlock (wallet->GetLastBlockHash (), FoundBlock ().locator (locator)));
4203- if (!locator.IsNull ()) wallet->chainStateFlushed (ChainstateRole::NORMAL, locator);
4204-
42054212 if (!RemoveWallet (context, wallet, /* load_on_start=*/ std::nullopt , warnings)) {
42064213 return util::Error{_ (" Unable to unload the wallet before migrating" )};
42074214 }
@@ -4450,4 +4457,17 @@ std::optional<CKey> CWallet::GetKey(const CKeyID& keyid) const
44504457 }
44514458 return std::nullopt ;
44524459}
4460+
4461+ void CWallet::WriteBestBlock () const
4462+ {
4463+ AssertLockHeld (cs_wallet);
4464+
4465+ if (!m_last_block_processed.IsNull ()) {
4466+ CBlockLocator loc;
4467+ chain ().findBlock (m_last_block_processed, FoundBlock ().locator (loc));
4468+
4469+ WalletBatch batch (GetDatabase ());
4470+ batch.WriteBestBlock (loc);
4471+ }
4472+ }
44534473} // namespace wallet
0 commit comments