Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 55 additions & 5 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,42 @@ class Wallet
//! Initiate a rescan. Returns status indicating success, failure, abort, or already rescanning.
virtual wallet::RescanStatus startRescan(bool from_genesis) = 0;

//! Rescan, on the calling thread, the blocks a key born at from_time
//! (UNIX time in seconds) may be paid in, as importdescriptors does.
//! Returns as startRescan() does. Only a completed rescanFromTime() or
//! startRescan() settles owed rescans: those it covers, except those
//! recorded after it started and those of an import that failed (see
//! ensureFriendshipReceivingKeychain()). Other rescans, such as
//! rescanblockchain's or one at wallet load, settle none. After a write
//! to the wallet database has failed since the wallet loaded, what a
//! rescan finds may not be saved, so the rescans it settles are owed
//! again once the wallet is reloaded.
//! FAILURE settles nothing and has two causes: a block it had to read has
//! no data (rescanUnavailableFrom() names the first block without data,
//! and the blocks this node has were scanned), or the chain changed
//! during the scan (rescanUnavailableFrom() is nullopt; run it again).
//! With a block filter index, a block whose filter does not match the
//! wallet is not read, so a rescan can complete and settle even while
//! rescanUnavailableFrom() names a height.
virtual wallet::RescanStatus rescanFromTime(int64_t from_time) = 0;

//! Abort a rescan.
virtual void abortRescan() = 0;

//! The height startRescan(false) starts at, when this node has pruned
//! blocks it would read; nullopt when it has them all.
virtual std::optional<int> rescanPrunedFrom() = 0;
//! The first height, up to the chain's tip, of the blocks
//! rescanFromTime(from_time) scans that this node has no block data
//! for, as on a pruned node or below an assumeutxo snapshot; nullopt
//! when it has them all. Such a rescan reads the blocks it has and
//! fails, unless block filters show none of the others pays the wallet.
virtual std::optional<int> rescanUnavailableFrom(int64_t from_time) = 0;

//! The time, in seconds, the rescan this wallet owes starts from, for
//! receiving chains it was given after it had scanned past their birth;
//! nullopt when it owes none. Run it with rescanFromTime(). It stays
//! owed while that rescan fails for want of the blocks
//! rescanUnavailableFrom() names. The rescan an import that failed owes
//! is left out until the chain is imported again or the wallet reloaded.
virtual std::optional<int64_t> owedRescanFrom() = 0;

//! Lock masternode collaterals
virtual void autoLockMasternodeCollaterals() = 0;
Expand Down Expand Up @@ -171,12 +201,32 @@ class Wallet
virtual wallet::PlatformKeyResult<uint32_t> platformAccountReference(const wallet::IdentityAuthKey& key,
const wallet::CompactXpub& compact_xpub) = 0;

//! Our DIP-15 receiving chain, as ensureFriendshipReceivingKeychain()
//! returns it, without importing it, and the birth of the one the wallet
//! holds. request.birth_time is not used.
virtual wallet::PlatformKeyResult<wallet::FriendshipReceivingChain> getFriendshipReceivingXpub(
const wallet::FriendshipKeychainRequest& request) = 0;

//! Ensure our private DIP-15 receiving chain is imported as a ranged
//! descriptor and return the corresponding public chain. Derivation,
//! descriptor update and result publication are one wallet-locked action.
//! descriptor born no later than request.birth_time (in seconds) and
//! return the corresponding public chain. Derivation, descriptor update
//! and result publication are one wallet-locked action. A chain the
//! wallet holds whole, born no later, is left as it is. Importing the
//! chain, or moving its birth earlier, first records a rescan owed from
//! that birth (see owedRescanFrom()). If the import then fails
//! (IMPORT_ERROR), the chain may lack some of its scripts until it is
//! imported again, which owes a new rescan; until then no rescan settles
//! the one recorded, so a reload still owes it. The failed birth may not
//! have been saved: ask again with that birth or an earlier one.
virtual wallet::PlatformKeyResult<wallet::FriendshipXpub> ensureFriendshipReceivingKeychain(
const wallet::FriendshipKeychainRequest& request) = 0;

//! Every credit output of this wallet's asset lock transactions that pays
//! one of keys, derived from the transactions the wallet holds. Deriving
//! the keys needs an unlocked wallet.
virtual wallet::PlatformKeyResult<std::vector<wallet::AssetLockFunding>> listAssetLockFundings(
const std::vector<wallet::AssetLockFundingKey>& keys) = 0;

//! Return whether wallet has private key.
virtual bool isSpendable(const CScript& script) = 0;
virtual bool isSpendable(const CTxDestination& dest) = 0;
Expand Down
3 changes: 3 additions & 0 deletions src/wallet/db.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ class WalletDatabase
virtual std::string Format() = 0;

std::atomic<unsigned int> nUpdateCounter;
//! Whether a WalletBatch failed to write a record since this database was
//! opened, so the wallet may hold what it did not save.
std::atomic<bool> m_write_failed{false};
unsigned int nLastSeen{0};
unsigned int nLastFlushed{0};
int64_t nLastWalletUpdate{0};
Expand Down
124 changes: 97 additions & 27 deletions src/wallet/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,49 +188,109 @@ class WalletImpl : public Wallet
{
return m_wallet->ChangeWalletPassphrase(old_wallet_passphrase, new_wallet_passphrase);
}
//! Where a rescan from the birth of the wallet's first key starts.
int rescanStartHeight()
{
int rescan_height{0};
std::optional<int64_t> time_first_key;
for (auto spk_man : m_wallet->GetAllScriptPubKeyMans()) {
int64_t time = spk_man->GetTimeFirstKey();
if (!time_first_key || time < *time_first_key) time_first_key = time;
}
if (time_first_key) {
m_wallet->chain().findFirstBlockWithTimeAndHeight(*time_first_key - TIMESTAMP_WINDOW, rescan_height,
FoundBlock().height(rescan_height));
//! The block a rescan of the blocks a key born at `time` may be paid in
//! starts from, and its height, as importdescriptors does; nullopt when
//! no block is that recent.
std::optional<std::pair<uint256, int>> rescanStart(int64_t time)
{
uint256 hash;
int height{0};
if (!m_wallet->chain().findFirstBlockWithTimeAndHeight(time - TIMESTAMP_WINDOW, /*min_height=*/0,
FoundBlock().hash(hash).height(height))) {
return std::nullopt;
}
return rescan_height;
return std::make_pair(hash, height);
}
wallet::RescanStatus startRescan(bool from_genesis) override
//! Scan from start and, once complete, settle the owed rescans up to
//! owed_sequence that it covers from covered_from.
wallet::RescanStatus rescanFrom(const WalletRescanReserver& reserver, const std::pair<uint256, int>& start,
uint64_t owed_sequence, int64_t covered_from)
{
const int rescan_height{from_genesis ? 0 : rescanStartHeight()};

WalletRescanReserver reserver(*m_wallet);
if (!reserver.reserve()) {
return wallet::RescanStatus::BUSY;
}
switch (m_wallet->ScanForWalletTransactions(m_wallet->chain().getBlockHash(rescan_height), rescan_height, /*max_height=*/std::nullopt,
reserver, /*fUpdate=*/true, /*save_progress=*/false).status) {
switch (m_wallet->ScanForWalletTransactions(start.first, start.second, /*max_height=*/std::nullopt, reserver,
/*fUpdate=*/true, /*save_progress=*/false).status) {
case CWallet::ScanResult::FAILURE:
return wallet::RescanStatus::FAILURE;
case CWallet::ScanResult::SUCCESS:
WITH_LOCK(m_wallet->cs_wallet, m_wallet->SettleOwedRescans(owed_sequence, covered_from));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
return wallet::RescanStatus::SUCCESS;
Comment on lines 213 to 215

@thepastaclaw thepastaclaw Nov 7, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

✅ Resolved at e1c75d64; see the replies below.

🔴 Blocking: Make transaction persistence retry-safe before settling owed rescans

A successful retry can reach this settlement with the recovered payment still only in memory. AddToWallet() inserts a newly discovered payment into mapWallet, wtxOrdered and setWalletUTXO before WriteTx() (wallet.cpp:1120–1132,1193–1196). If that write fails, AddToWalletIfInvolvingMe() throws without rolling those insertions back. After the caller catches the error and storage recovers, rescanning the same confirmed, unspent payment sets neither fInsertedNew nor fUpdated: its confirmation state is unchanged, and AddWalletUTXOs(..., false) finds the output already present. WriteTx() is therefore skipped, the retry returns SUCCESS, and settlement deletes the durable recovery obligation. Flush()/Close() do not rewrite mapWallet. On reload, a wallet whose saved locator already passed the historical payment loses it with nothing owed; ensuring the same complete receiving chain at the same birth is also a no-op. Although the transaction insertion behavior predates this PR, the new durable-recovery contract depends on it. Make failed transaction writes retry-safe in the transaction-storage owner, by rolling back the unpublished state or retaining and reliably retrying failed writes. Add a regression that fails the payment's TX-record write, retries a covering scan in the same session, and reloads to verify that settlement cannot discard recovery before the payment is persisted.

source: gpt-6.1-sol (phase2-reviewer: general)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed, this blocks the PR. The fix goes into D-04's settlement rather than into AddToWallet(), and it covers every failed write, not only the transaction's.

The bug. Your trace holds.

  • AddToWallet() puts a new payment into mapWallet, wtxOrdered and setWalletUTXO before WriteTx(). When that write fails, AddToWalletIfInvolvingMe() throws, and nothing is rolled back.
  • A covering rescan retried once writes recover finds the payment already there, with the same confirmed state. It writes nothing and returns SUCCESS, and settlement erases the one record that would have made the reloaded wallet rescan for it.

Why not fix it in AddToWallet().

  • Nothing to backport. Bitcoin Core has the same order on master (emplace, then WriteFullTx(), no rollback), and no PR changes it. wallet: Handle or explicitly ignore WalletBatch write failures bitcoin/bitcoin#35998 adds more early returns after the emplace, and wallet: Replace mapWallet and wtxOrdered with a boost::multi_index bitcoin/bitcoin#35716 keeps the order.

  • A rollback is a large change of its own. It would have to undo much more than the map entry:

    • AddToSpends() unlocks the spent coins, erasing their records, and copies metadata onto conflicting transactions;
    • the ProTx and dust coin locks are written;
    • nOrderPosNext and the wallet birth move.

    Doing that in a function every wallet backport touches is a separate change.

  • A retry marker would close only this path. A marker that makes a retry write the transaction again would close this trace but not the class. On the same scan path, TopUp() ignores a failed write of a chain's grown range. A covering rescan can then find and save payments in the grown part and settle, and after a reload those payments are outside the saved range with nothing owed.

  • It is the fourth instance. This is the fourth round in which an inherited write that publishes before it saves meets D-04's contract: orphan records, the cache, the key, now transactions. So the contract itself now guards against it.

The fix (D-04, 0a35172). D-04 erases an owed-rescan record only while no write to the wallet's database has failed since the wallet loaded.

  • The signal. WalletBatch::WriteIC(), which every ordinary wallet record update relevant to settlement goes through, sets WalletDatabase::m_write_failed when a write returns false. Only settlement reads it. The load-time version write, storage migration, dump/salvage and database-copy paths bypass WriteIC(), and a commit failure or a rolled-back write is not latched; none of those is followed by a same-session settlement today.
  • What settlement does then. A rescan that covers a record still settles it in memory, so owedRescanFrom() stops reporting it, and a caller that rescans until nothing is owed does not loop. But the record stays saved, so the reloaded wallet owes the rescan again. That rescan starts from memory rebuilt from disk, so it saves what it finds.
  • The window is since load, not since the record was written. An earlier importdescriptors or rescanblockchain rescan can leave a payment in memory only, before the ensure that owes the record.
  • The cost. After a failed write, each record settled in that session costs one more rescan after the next reload.
  • What is not counted.
    • Failed erases: the database then holds more than memory, never less.
    • Failed commits: today only EncryptWallet, which asserts, and migration, which reloads the wallet, can hit one.

Tests (friendship_rescan_owed_until_covered).

  • A new row in the plain/encrypted first-import loop:
    1. The wallet ensures the chain.
    2. With writes failing, rescanFromTime(birth) throws "DB error adding transaction to wallet, write failed".
    3. Writes recover. The same session's rescan returns SUCCESS, owes nothing and holds the payment.
    4. The reload still owes from birth and lacks the payment.
    5. Its rescan finds the payment and settles, and a further reload owes nothing and signs a spend of it.
  • The rule for a write that is not a transaction. Earlier in the case, m_wallet fails to write an owed record. The rescans it settles after that now stay saved, and a reload owes them until a rescan there settles them.
  • The key-write row. Its reload now owes the rescan again, because the key write failed in that session, and it still signs a spend of the payment. It does not ensure before signing, so reverting the key fix still fails there. This supersedes the "owes nothing" reload expectation in my earlier reply on the key-write finding (feat(wallet): wallet-owned rescans for DashPay receiving chains and asset-lock funding discovery #8068 (comment)): the key and payment are saved and sign, but after a failed write in the preceding session the reloaded wallet owes the rescan again.

Each reversion was run against the final test:

Reverted Result
the settlement check 7 failures, all owedRescanFrom() checks: in each variant, the new row's reload owes nothing while lacking the payment (your scenario, two checks), and the key-write row's reload owes nothing; and m_wallet's settled reload owes nothing
the flag set in WriteIC() the same 7
the flag set only when AddToWallet()'s WriteTx() fails, instead of in WriteIC() 3 failures. The new row passes, but the rows whose failed write is the owed record or the key fail. This is why the flag is in WriteIC().
the key fix from bitcoin#35752 (the first commit) still fails the key-write row's signing check, in both variants

Runs, on Linux (gcc 15, depends, --enable-debug --enable-werror):

  • platformkeys_tests, wallet_tests, walletdb_tests, wallet_crypto_tests, walletload_tests and scriptpubkeyman_tests pass at each of the three commits.
  • The full test_dash (all 173 suite files) passes at the head.
  • wallet_basic, wallet_abandonconflict, wallet_listtransactions, wallet_importdescriptors, wallet_import_rescan, wallet_reorgsrestore and wallet_backup pass, every wallet-type variant of each, 11 runs in all.

🤖 Posted autonomously by Claude on behalf of pasta.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Resolved (re-reviewed at e1c75d64): Your database-wide failed-write latch closes the reported settlement loss without requiring an AddToWallet rollback: settlement removes covered work only from memory while retaining its durable record after a write failure. The plaintext and encrypted regression cases verify that reload still owes recovery when the payment was held only in memory, then persist it and verify signing.

case CWallet::ScanResult::USER_ABORT:
return wallet::RescanStatus::USER_ABORT;
}
Assume(false); // unreachable
return wallet::RescanStatus::FAILURE; // fallback for release builds
}
wallet::RescanStatus startRescan(bool from_genesis) override
{
WalletRescanReserver reserver(*m_wallet);
if (!reserver.reserve()) {
return wallet::RescanStatus::BUSY;
}
uint64_t owed;
std::optional<int64_t> time_first_key;
{
LOCK(m_wallet->cs_wallet);
owed = m_wallet->GetOwedRescanSequence();
if (!from_genesis) {
for (auto spk_man : m_wallet->GetAllScriptPubKeyMans()) {
int64_t time = spk_man->GetTimeFirstKey();
if (!time_first_key || time < *time_first_key) time_first_key = time;
}
}
}
std::optional<std::pair<uint256, int>> start;
if (time_first_key) start = rescanStart(*time_first_key);
if (!start) start.emplace(m_wallet->chain().getBlockHash(0), 0);
return rescanFrom(reserver, *start, owed, time_first_key.value_or(0));
}
wallet::RescanStatus rescanFromTime(int64_t from_time) override
{
from_time = std::max<int64_t>(from_time, 0);
WalletRescanReserver reserver(*m_wallet);
if (!reserver.reserve()) {
return wallet::RescanStatus::BUSY;
}
// Taken before looking for blocks: a chain imported later may be paid
// in one that arrives meanwhile.
const uint64_t owed{WITH_LOCK(m_wallet->cs_wallet, return m_wallet->GetOwedRescanSequence())};
const auto start{rescanStart(from_time)};
if (!start) {
LOCK(m_wallet->cs_wallet);
m_wallet->chain().requestMempoolTransactions(*m_wallet);
m_wallet->SettleOwedRescans(owed, from_time);
return wallet::RescanStatus::SUCCESS;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
Comment on lines +256 to +261

@thepastaclaw thepastaclaw Nov 6, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

✅ Resolved at cd435e0b; see the replies below.

🔴 Blocking: Replay existing mempool payments before settling an empty-range rescan

When the active chain's maximum timestamp is older than from_time minus TIMESTAMP_WINDOW, this branch clears the obligation without replaying the mempool. A payment already present before the receiving chain was imported was ignored by its original notification because the wallet did not yet own its script. Unlike the normal ScanForWalletTransactions path, which calls requestMempoolTransactions when max_height is unset, this path leaves that payment absent while reporting successful recovery. Repeating the same import creates no new obligation. Replay the mempool before settlement, and extend the empty-block-range test with a payment present before import.

Suggested change
if (!start) {
WITH_LOCK(m_wallet->cs_wallet, m_wallet->SettleOwedRescans(owed, from_time));
return wallet::RescanStatus::SUCCESS;
}
if (!start) {
WITH_LOCK(m_wallet->cs_wallet, m_wallet->chain().requestMempoolTransactions(*m_wallet));
WITH_LOCK(m_wallet->cs_wallet, m_wallet->SettleOwedRescans(owed, from_time));
return wallet::RescanStatus::SUCCESS;
}

source: muse-spark-1.3-contributor (phase1-reviewer: general); gpt-6.1-sol (phase2-reviewer: general, dash-core-commit-history)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Resolved (re-reviewed at cd435e0b): Your no-block path now replays existing mempool transactions synchronously before settling obligations. The new pre-import mempool-payment test directly covers the missing recovery step.

return rescanFrom(reserver, *start, owed, from_time);
}
void abortRescan() override { m_wallet->AbortRescan(); }
std::optional<int> rescanPrunedFrom() override
std::optional<int> rescanUnavailableFrom(int64_t from_time) override
{
const auto start{rescanStart(std::max<int64_t>(from_time, 0))};
if (!start) return std::nullopt;
// The rescan reads up to the chain's tip, which the wallet may not have reached.
const CBlockLocator locator{m_wallet->chain().getTipLocator()};
int tip_height{0};
if (locator.IsNull() || !m_wallet->chain().findBlock(locator.vHave.front(), FoundBlock().height(tip_height))) {
return std::nullopt;
}
const uint256& tip{locator.vHave.front()};
if (m_wallet->chain().hasBlocks(tip, start->second)) return std::nullopt;
// Every block from start up to the first one without data has it.
int first_missing{start->second};
int last{tip_height};
while (first_missing < last) {
const int mid{first_missing + (last - first_missing) / 2};
if (m_wallet->chain().hasBlocks(tip, start->second, mid)) {
first_missing = mid + 1;
} else {
last = mid;
}
}
return first_missing;
}
std::optional<int64_t> owedRescanFrom() override
{
if (!m_wallet->chain().havePruned()) return std::nullopt;
LOCK(m_wallet->cs_wallet);
const int rescan_height{rescanStartHeight()};
if (m_wallet->chain().hasBlocks(m_wallet->GetLastBlockHash(), rescan_height)) return std::nullopt;
return rescan_height;
return m_wallet->GetOwedRescanFrom();
}
void autoLockMasternodeCollaterals() override { m_wallet->AutoLockMasternodeCollaterals(); }
bool backupWallet(const std::string& filename) override { return m_wallet->BackupWallet(filename); }
Expand Down Expand Up @@ -281,11 +341,21 @@ class WalletImpl : public Wallet
{
return m_wallet->PlatformAccountReference(key, compact_xpub);
}
wallet::PlatformKeyResult<wallet::FriendshipReceivingChain> getFriendshipReceivingXpub(
const wallet::FriendshipKeychainRequest& request) override
{
return m_wallet->GetFriendshipReceivingXpub(request);
}
wallet::PlatformKeyResult<wallet::FriendshipXpub> ensureFriendshipReceivingKeychain(
const wallet::FriendshipKeychainRequest& request) override
{
return m_wallet->EnsureFriendshipReceivingKeychain(request);
}
wallet::PlatformKeyResult<std::vector<wallet::AssetLockFunding>> listAssetLockFundings(
const std::vector<wallet::AssetLockFundingKey>& keys) override
{
return m_wallet->ListAssetLockFundings(keys);
}
bool isSpendable(const CScript& script) override
{
LOCK(m_wallet->cs_wallet);
Expand Down
30 changes: 29 additions & 1 deletion src/wallet/platformtypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#ifndef BITCOIN_WALLET_PLATFORMTYPES_H
#define BITCOIN_WALLET_PLATFORMTYPES_H

#include <consensus/amount.h>
#include <pubkey.h>
#include <script/standard.h>
#include <support/allocators/secure.h>
Expand All @@ -13,6 +14,7 @@
#include <array>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <variant>

namespace wallet {
Expand Down Expand Up @@ -59,11 +61,29 @@ using PlatformKeyRequest = std::variant<IdentityAuthKey, RegistrationFundingKey,
//! the wallet's seed, so the credits it funds can be recovered.
using AssetLockFundingKey = std::variant<RegistrationFundingKey, TopupFundingKey, InvitationFundingKey>;

enum class AssetLockFundingStatus : uint8_t {
ACTIVE, //!< Not abandoned, and no conflicting transaction is mined.
INACTIVE, //!< Abandoned and not mined. It can still be mined, so it stays resumable.
CONFLICTED, //!< A transaction spending the same coins is mined.
};

//! A credit output of a wallet asset lock that pays one of the wallet's
//! funding keys.
struct AssetLockFunding {
uint256 txid;
uint32_t credit_index{0}; //!< Index into the payload's credit outputs.
AssetLockFundingKey key;
CAmount amount{0};
AssetLockFundingStatus status{AssetLockFundingStatus::ACTIVE};
};

struct FriendshipKeychainRequest {
uint32_t account{0};
uint256 local_id;
uint256 remote_id;
int64_t birth_time{0}; //!< 0 means unknown/genesis.
//! UNIX time in seconds, as a descriptor's creation time (Platform's
//! $createdAt is in milliseconds); 0 means unknown, i.e. genesis.
int64_t birth_time{0};
};

struct FriendshipXpub {
Expand All @@ -73,6 +93,14 @@ struct FriendshipXpub {
std::array<uint8_t, 4> parent_fingerprint{};
};

//! Our DIP-15 receiving chain for one friendship.
struct FriendshipReceivingChain {
FriendshipXpub xpub;
//! Birth of the chain the wallet holds, in seconds; nullopt when it does
//! not hold it.
std::optional<int64_t> birth_time;
};

//! DIP-15 compact xpub: parentFingerprint(4) || chainCode(32) || pubKey(33).
//! It is the contactRequest encryptedPublicKey plaintext and the
//! accountReference MAC input, with no version, depth or child number.
Expand Down
11 changes: 8 additions & 3 deletions src/wallet/scriptpubkeyman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2497,14 +2497,19 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
return false;
}

m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, crypted_secret);
if (!batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret, crypted_mnemonic, crypted_mnemonic_passphrase)) {
return false;
}
m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, std::move(crypted_secret));
m_crypted_mnemonics[pubkey.GetID()] = make_pair(crypted_mnemonic, crypted_mnemonic_passphrase);
return batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret, crypted_mnemonic, crypted_mnemonic_passphrase);
} else {
if (!batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey(), mnemonic, mnemonic_passphrase)) {
return false;
}
m_map_keys[pubkey.GetID()] = key;
m_mnemonics[pubkey.GetID()] = make_pair(mnemonic, mnemonic_passphrase);
return batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey(), mnemonic, mnemonic_passphrase);
}
return true;
}

bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, const SecureString& secure_mnemonic, const SecureString& secure_mnemonic_passphrase, PathDerivationType type)
Expand Down
Loading
Loading