@@ -4510,4 +4510,170 @@ util::Result<std::vector<WalletDescInfo>> CWallet::ExportDescriptors(bool export
4510
4510
}
4511
4511
return wallet_descriptors;
4512
4512
}
4513
+
4514
+ util::Result<std::string> CWallet::ExportWatchOnlyWallet (const fs::path& destination, WalletContext& context) const
4515
+ {
4516
+ AssertLockHeld (cs_wallet);
4517
+
4518
+ if (destination.empty ()) {
4519
+ return util::Error{_ (" Error: Export destination cannot be empty" )};
4520
+ }
4521
+ if (fs::exists (destination)) {
4522
+ return util::Error{strprintf (_ (" Error: Export destination '%s' already exists" ), fs::PathToString (destination))};
4523
+ }
4524
+ fs::path canonical_dest = fs::canonical (destination.parent_path ());
4525
+ canonical_dest /= destination.filename ();
4526
+
4527
+ // Get the descriptors from this wallet
4528
+ util::Result<std::vector<WalletDescInfo>> exported = ExportDescriptors (/* export_private=*/ false );
4529
+ if (!exported) {
4530
+ return util::Error{util::ErrorString (exported)};
4531
+ }
4532
+
4533
+ // Setup DatabaseOptions to create a new sqlite database
4534
+ DatabaseOptions options;
4535
+ options.require_existing = false ;
4536
+ options.require_create = true ;
4537
+ options.require_format = DatabaseFormat::SQLITE;
4538
+
4539
+ // Make the wallet with the same flags as this wallet, but without private keys
4540
+ options.create_flags = GetWalletFlags () | WALLET_FLAG_DISABLE_PRIVATE_KEYS;
4541
+
4542
+ // Make the watchonly wallet
4543
+ DatabaseStatus status;
4544
+ std::vector<bilingual_str> warnings;
4545
+ std::string wallet_name = GetName () + " _watchonly" ;
4546
+ bilingual_str error;
4547
+ std::unique_ptr<WalletDatabase> database = MakeWalletDatabase (wallet_name, options, status, error);
4548
+ if (!database) {
4549
+ return util::Error{strprintf (_ (" Wallet file creation failed: %s" ), error)};
4550
+ }
4551
+ WalletContext empty_context;
4552
+ empty_context.args = context.args ;
4553
+ std::shared_ptr<CWallet> watchonly_wallet = CWallet::Create (empty_context, wallet_name, std::move (database), options.create_flags , error, warnings);
4554
+ if (!watchonly_wallet) {
4555
+ return util::Error{_ (" Error: Failed to create new watchonly wallet" )};
4556
+ }
4557
+
4558
+ {
4559
+ LOCK (watchonly_wallet->cs_wallet );
4560
+
4561
+ // Parse the descriptors and add them to the new wallet
4562
+ for (const WalletDescInfo& desc_info : *exported) {
4563
+ // Parse the descriptor
4564
+ FlatSigningProvider keys;
4565
+ std::string parse_err;
4566
+ std::vector<std::unique_ptr<Descriptor>> descs = Parse (desc_info.descriptor , keys, parse_err, /* require_checksum=*/ true );
4567
+ assert (descs.size () == 1 ); // All of our descriptors should be valid, and not multipath
4568
+
4569
+ // Get the range if there is one
4570
+ int32_t range_start = 0 ;
4571
+ int32_t range_end = 0 ;
4572
+ if (desc_info.range ) {
4573
+ range_start = desc_info.range ->first ;
4574
+ range_end = desc_info.range ->second ;
4575
+ }
4576
+
4577
+ WalletDescriptor w_desc (std::move (descs.at (0 )), desc_info.creation_time , range_start, range_end, desc_info.next_index );
4578
+
4579
+ // For descriptors that cannot self expand (i.e. needs private keys or cache), retrieve the cache
4580
+ uint256 desc_id = w_desc.id ;
4581
+ if (!w_desc.descriptor ->CanSelfExpand ()) {
4582
+ DescriptorScriptPubKeyMan* desc_spkm = dynamic_cast <DescriptorScriptPubKeyMan*>(GetScriptPubKeyMan (desc_id));
4583
+ w_desc.cache = WITH_LOCK (desc_spkm->cs_desc_man , return desc_spkm->GetWalletDescriptor ().cache );
4584
+ }
4585
+
4586
+ // Add to the watchonly wallet
4587
+ if (auto spkm_res = watchonly_wallet->AddWalletDescriptor (w_desc, keys, " " , false ); !spkm_res) {
4588
+ return util::Error{util::ErrorString (spkm_res)};
4589
+ }
4590
+
4591
+ // Set active spkms as active
4592
+ if (desc_info.active ) {
4593
+ // Determine whether this descriptor is internal
4594
+ // This is only set for active spkms
4595
+ bool internal = false ;
4596
+ if (desc_info.internal ) {
4597
+ internal = *desc_info.internal ;
4598
+ }
4599
+ watchonly_wallet->AddActiveScriptPubKeyMan (desc_id, *w_desc.descriptor ->GetOutputType (), internal);
4600
+ }
4601
+ }
4602
+
4603
+ {
4604
+ // Make a WalletBatch for the watchonly_wallet so that everything else can be written atomically
4605
+ WalletBatch watchonly_batch (watchonly_wallet->GetDatabase ());
4606
+ if (!watchonly_batch.TxnBegin ()) {
4607
+ return util::Error{strprintf (_ (" Error: database transaction cannot be executed for new watchonly wallet %s" ), watchonly_wallet->GetName ())};
4608
+ }
4609
+
4610
+ // Copy minversion
4611
+ // Don't use SetMinVersion to account for the newly created wallet having FEATURE_LATEST
4612
+ // while the source wallet doesn't.
4613
+ watchonly_wallet->LoadMinVersion (GetVersion ());
4614
+ watchonly_batch.WriteMinVersion (watchonly_wallet->GetVersion ());
4615
+
4616
+ // Copy orderPosNext
4617
+ watchonly_batch.WriteOrderPosNext (watchonly_wallet->nOrderPosNext );
4618
+
4619
+ // Write the best block locator to avoid rescanning on reload
4620
+ CBlockLocator best_block_locator;
4621
+ {
4622
+ WalletBatch local_wallet_batch (GetDatabase ());
4623
+ if (!local_wallet_batch.ReadBestBlock (best_block_locator)) {
4624
+ return util::Error{_ (" Error: Unable to read wallet's best block locator record" )};
4625
+ }
4626
+ }
4627
+ if (!watchonly_batch.WriteBestBlock (best_block_locator)) {
4628
+ return util::Error{_ (" Error: Unable to write watchonly wallet best block locator record" )};
4629
+ }
4630
+
4631
+ // Copy the transactions
4632
+ for (const auto & [txid, wtx] : mapWallet) {
4633
+ const CWalletTx& to_copy_wtx = wtx;
4634
+ if (!watchonly_wallet->LoadToWallet (txid, [&](CWalletTx& ins_wtx, bool new_tx) EXCLUSIVE_LOCKS_REQUIRED (watchonly_wallet->cs_wallet ) {
4635
+ if (!new_tx) return false ;
4636
+ ins_wtx.SetTx (to_copy_wtx.tx );
4637
+ ins_wtx.CopyFrom (to_copy_wtx);
4638
+ return true ;
4639
+ })) {
4640
+ return util::Error{strprintf (_ (" Error: Could not add tx %s to watchonly wallet" ), txid.GetHex ())};
4641
+ }
4642
+ watchonly_batch.WriteTx (watchonly_wallet->mapWallet .at (txid));
4643
+ }
4644
+
4645
+ // Copy address book
4646
+ for (const auto & [dest, entry] : m_address_book) {
4647
+ auto address{EncodeDestination (dest)};
4648
+ if (entry.purpose ) watchonly_batch.WritePurpose (address, PurposeToString (*entry.purpose ));
4649
+ if (entry.label ) watchonly_batch.WriteName (address, *entry.label );
4650
+ for (const auto & [id, request] : entry.receive_requests ) {
4651
+ watchonly_batch.WriteAddressReceiveRequest (dest, id, request);
4652
+ }
4653
+ if (entry.previously_spent ) watchonly_batch.WriteAddressPreviouslySpent (dest, true );
4654
+ }
4655
+
4656
+ // Copy locked coins that are persisted
4657
+ for (const auto & [coin, persisted] : m_locked_coins) {
4658
+ if (!persisted) continue ;
4659
+ watchonly_wallet->LockCoin (coin, &watchonly_batch);
4660
+ }
4661
+
4662
+
4663
+ if (!watchonly_batch.TxnCommit ()) {
4664
+ return util::Error{_ (" Error: cannot commit db transaction for watchonly wallet export" )};
4665
+ }
4666
+ }
4667
+
4668
+ // Make a backup of this wallet at the specified destination directory
4669
+ watchonly_wallet->BackupWallet (fs::PathToString (canonical_dest));
4670
+ }
4671
+
4672
+ // Delete the watchonly wallet now that it has been exported to the desired location
4673
+ fs::path watchonly_path = fs::PathFromString (watchonly_wallet->GetDatabase ().Filename ()).parent_path ();
4674
+ watchonly_wallet.reset ();
4675
+ fs::remove_all (watchonly_path);
4676
+
4677
+ return fs::PathToString (canonical_dest);
4678
+ }
4513
4679
} // namespace wallet
0 commit comments