// Copyright (c) 2018-2022 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using interfaces::Chain; using interfaces::FoundBlock; using interfaces::Handler; using interfaces::MakeSignalHandler; using interfaces::Wallet; using interfaces::WalletAddress; using interfaces::WalletBalances; using interfaces::WalletLoader; using interfaces::WalletMigrationResult; using interfaces::WalletOrderForm; using interfaces::WalletTx; using interfaces::WalletTxOut; using interfaces::WalletTxStatus; using interfaces::WalletValueMap; namespace wallet { // All members of the classes in this namespace are intentionally public, as the // classes themselves are private. namespace { //! Construct wallet tx struct. WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx) { LOCK(wallet.cs_wallet); WalletTx result; result.tx = wtx.tx; result.txin_is_mine.reserve(wtx.tx->vin.size()); for (const auto& txin : wtx.tx->vin) { result.txin_is_mine.emplace_back(InputIsMine(wallet, txin)); } result.txout_is_mine.reserve(wtx.tx->vout.size()); result.txout_address.reserve(wtx.tx->vout.size()); result.txout_address_is_mine.reserve(wtx.tx->vout.size()); for (const auto& txout : wtx.tx->vout) { result.txout_is_mine.emplace_back(wallet.IsMine(txout)); result.txout_is_change.push_back(OutputIsChange(wallet, txout)); result.txout_address.emplace_back(); result.txout_address_is_mine.emplace_back(ExtractDestination(txout.scriptPubKey, result.txout_address.back()) ? wallet.IsMine(result.txout_address.back()) : ISMINE_NO); } result.credit = CachedTxGetCredit(wallet, wtx, ISMINE_ALL); result.debit = CachedTxGetDebit(wallet, wtx, ISMINE_ALL); result.change = CachedTxGetChange(wallet, wtx); result.time = wtx.GetTxTime(); result.value_map = wtx.mapValue; result.is_coinbase = wtx.IsCoinBase(); return result; } //! Construct wallet tx status struct. WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { AssertLockHeld(wallet.cs_wallet); WalletTxStatus result; result.block_height = wtx.state() ? wtx.state()->confirmed_block_height : wtx.state() ? wtx.state()->conflicting_block_height : std::numeric_limits::max(); result.blocks_to_maturity = wallet.GetTxBlocksToMaturity(wtx); result.depth_in_main_chain = wallet.GetTxDepthInMainChain(wtx); result.time_received = wtx.nTimeReceived; result.lock_time = wtx.tx->nLockTime; result.is_trusted = CachedTxIsTrusted(wallet, wtx); result.is_abandoned = wtx.isAbandoned(); result.is_coinbase = wtx.IsCoinBase(); result.is_in_main_chain = wtx.isConfirmed(); return result; } //! Construct wallet TxOut struct. WalletTxOut MakeWalletTxOut(const CWallet& wallet, const CWalletTx& wtx, int n, int depth) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { WalletTxOut result; result.txout = wtx.tx->vout[n]; result.time = wtx.GetTxTime(); result.depth_in_main_chain = depth; result.is_spent = wallet.IsSpent(COutPoint(wtx.GetHash(), n)); return result; } WalletTxOut MakeWalletTxOut(const CWallet& wallet, const COutput& output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet) { WalletTxOut result; result.txout = output.txout; result.time = output.time; result.depth_in_main_chain = output.depth; result.is_spent = wallet.IsSpent(output.outpoint); return result; } class WalletImpl : public Wallet { public: explicit WalletImpl(WalletContext& context, const std::shared_ptr& wallet) : m_context(context), m_wallet(wallet) {} bool encryptWallet(const SecureString& wallet_passphrase) override { return m_wallet->EncryptWallet(wallet_passphrase); } bool isCrypted() override { return m_wallet->IsCrypted(); } bool lock() override { return m_wallet->Lock(); } bool unlock(const SecureString& wallet_passphrase) override { return m_wallet->Unlock(wallet_passphrase); } bool isLocked() override { return m_wallet->IsLocked(); } bool changeWalletPassphrase(const SecureString& old_wallet_passphrase, const SecureString& new_wallet_passphrase) override { return m_wallet->ChangeWalletPassphrase(old_wallet_passphrase, new_wallet_passphrase); } void abortRescan() override { m_wallet->AbortRescan(); } bool backupWallet(const std::string& filename) override { return m_wallet->BackupWallet(filename); } std::string getWalletName() override { return m_wallet->GetName(); } util::Result getNewDestination(const OutputType type, const std::string& label) override { LOCK(m_wallet->cs_wallet); return m_wallet->GetNewDestination(type, label); } bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) override { std::unique_ptr provider = m_wallet->GetSolvingProvider(script); if (provider) { return provider->GetPubKey(address, pub_key); } return false; } SigningResult signMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) override { return m_wallet->SignMessage(message, pkhash, str_sig); } bool isSpendable(const CTxDestination& dest) override { LOCK(m_wallet->cs_wallet); return m_wallet->IsMine(dest) & ISMINE_SPENDABLE; } bool haveWatchOnly() override { auto spk_man = m_wallet->GetLegacyScriptPubKeyMan(); if (spk_man) { return spk_man->HaveWatchOnly(); } return false; }; bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::optional& purpose) override { return m_wallet->SetAddressBook(dest, name, purpose); } bool delAddressBook(const CTxDestination& dest) override { return m_wallet->DelAddressBook(dest); } bool getAddress(const CTxDestination& dest, std::string* name, isminetype* is_mine, AddressPurpose* purpose) override { LOCK(m_wallet->cs_wallet); const auto& entry = m_wallet->FindAddressBookEntry(dest, /*allow_change=*/false); if (!entry) return false; // addr not found if (name) { *name = entry->GetLabel(); } std::optional dest_is_mine; if (is_mine || purpose) { dest_is_mine = m_wallet->IsMine(dest); } if (is_mine) { *is_mine = *dest_is_mine; } if (purpose) { // In very old wallets, address purpose may not be recorded so we derive it from IsMine *purpose = entry->purpose.value_or(*dest_is_mine ? AddressPurpose::RECEIVE : AddressPurpose::SEND); } return true; } std::vector getAddresses() override { LOCK(m_wallet->cs_wallet); std::vector result; m_wallet->ForEachAddrBookEntry([&](const CTxDestination& dest, const std::string& label, bool is_change, const std::optional& purpose) EXCLUSIVE_LOCKS_REQUIRED(m_wallet->cs_wallet) { if (is_change) return; isminetype is_mine = m_wallet->IsMine(dest); // In very old wallets, address purpose may not be recorded so we derive it from IsMine result.emplace_back(dest, is_mine, purpose.value_or(is_mine ? AddressPurpose::RECEIVE : AddressPurpose::SEND), label); }); return result; } std::vector getAddressReceiveRequests() override { LOCK(m_wallet->cs_wallet); return m_wallet->GetAddressReceiveRequests(); } bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) override { // Note: The setAddressReceiveRequest interface used by the GUI to store // receive requests is a little awkward and could be improved in the // future: // // - The same method is used to save requests and erase them, but // having separate methods could be clearer and prevent bugs. // // - Request ids are passed as strings even though they are generated as // integers. // // - Multiple requests can be stored for the same address, but it might // be better to only allow one request or only keep the current one. LOCK(m_wallet->cs_wallet); WalletBatch batch{m_wallet->GetDatabase()}; return value.empty() ? m_wallet->EraseAddressReceiveRequest(batch, dest, id) : m_wallet->SetAddressReceiveRequest(batch, dest, id, value); } util::Result displayAddress(const CTxDestination& dest) override { LOCK(m_wallet->cs_wallet); return m_wallet->DisplayAddress(dest); } bool lockCoin(const COutPoint& output, const bool write_to_db) override { LOCK(m_wallet->cs_wallet); std::unique_ptr batch = write_to_db ? std::make_unique(m_wallet->GetDatabase()) : nullptr; return m_wallet->LockCoin(output, batch.get()); } bool unlockCoin(const COutPoint& output) override { LOCK(m_wallet->cs_wallet); std::unique_ptr batch = std::make_unique(m_wallet->GetDatabase()); return m_wallet->UnlockCoin(output, batch.get()); } bool isLockedCoin(const COutPoint& output) override { LOCK(m_wallet->cs_wallet); return m_wallet->IsLockedCoin(output); } void listLockedCoins(std::vector& outputs) override { LOCK(m_wallet->cs_wallet); return m_wallet->ListLockedCoins(outputs); } util::Result createTransaction(const std::vector& recipients, const CCoinControl& coin_control, bool sign, int& change_pos, CAmount& fee) override { LOCK(m_wallet->cs_wallet); auto res = CreateTransaction(*m_wallet, recipients, change_pos == -1 ? std::nullopt : std::make_optional(change_pos), coin_control, sign); if (!res) return util::Error{util::ErrorString(res)}; const auto& txr = *res; fee = txr.fee; change_pos = txr.change_pos ? int(*txr.change_pos) : -1; return txr.tx; } void commitTransaction(CTransactionRef tx, WalletValueMap value_map, WalletOrderForm order_form) override { LOCK(m_wallet->cs_wallet); m_wallet->CommitTransaction(std::move(tx), std::move(value_map), std::move(order_form)); } bool transactionCanBeAbandoned(const uint256& txid) override { return m_wallet->TransactionCanBeAbandoned(txid); } bool abandonTransaction(const uint256& txid) override { LOCK(m_wallet->cs_wallet); return m_wallet->AbandonTransaction(txid); } bool transactionCanBeBumped(const uint256& txid) override { return feebumper::TransactionCanBeBumped(*m_wallet.get(), txid); } bool createBumpTransaction(const uint256& txid, const CCoinControl& coin_control, std::vector& errors, CAmount& old_fee, CAmount& new_fee, CMutableTransaction& mtx) override { std::vector outputs; // just an empty list of new recipients for now return feebumper::CreateRateBumpTransaction(*m_wallet.get(), txid, coin_control, errors, old_fee, new_fee, mtx, /* require_mine= */ true, outputs) == feebumper::Result::OK; } bool signBumpTransaction(CMutableTransaction& mtx) override { return feebumper::SignTransaction(*m_wallet.get(), mtx); } bool commitBumpTransaction(const uint256& txid, CMutableTransaction&& mtx, std::vector& errors, uint256& bumped_txid) override { return feebumper::CommitTransaction(*m_wallet.get(), txid, std::move(mtx), errors, bumped_txid) == feebumper::Result::OK; } CTransactionRef getTx(const uint256& txid) override { LOCK(m_wallet->cs_wallet); auto mi = m_wallet->mapWallet.find(txid); if (mi != m_wallet->mapWallet.end()) { return mi->second.tx; } return {}; } WalletTx getWalletTx(const uint256& txid) override { LOCK(m_wallet->cs_wallet); auto mi = m_wallet->mapWallet.find(txid); if (mi != m_wallet->mapWallet.end()) { return MakeWalletTx(*m_wallet, mi->second); } return {}; } std::set getWalletTxs() override { LOCK(m_wallet->cs_wallet); std::set result; for (const auto& entry : m_wallet->mapWallet) { result.emplace(MakeWalletTx(*m_wallet, entry.second)); } return result; } bool tryGetTxStatus(const uint256& txid, interfaces::WalletTxStatus& tx_status, int& num_blocks, int64_t& block_time) override { TRY_LOCK(m_wallet->cs_wallet, locked_wallet); if (!locked_wallet) { return false; } auto mi = m_wallet->mapWallet.find(txid); if (mi == m_wallet->mapWallet.end()) { return false; } num_blocks = m_wallet->GetLastBlockHeight(); block_time = -1; CHECK_NONFATAL(m_wallet->chain().findBlock(m_wallet->GetLastBlockHash(), FoundBlock().time(block_time))); tx_status = MakeWalletTxStatus(*m_wallet, mi->second); return true; } WalletTx getWalletTxDetails(const uint256& txid, WalletTxStatus& tx_status, WalletOrderForm& order_form, bool& in_mempool, int& num_blocks) override { LOCK(m_wallet->cs_wallet); auto mi = m_wallet->mapWallet.find(txid); if (mi != m_wallet->mapWallet.end()) { num_blocks = m_wallet->GetLastBlockHeight(); in_mempool = mi->second.InMempool(); order_form = mi->second.vOrderForm; tx_status = MakeWalletTxStatus(*m_wallet, mi->second); return MakeWalletTx(*m_wallet, mi->second); } return {}; } TransactionError fillPSBT(int sighash_type, bool sign, bool bip32derivs, size_t* n_signed, PartiallySignedTransaction& psbtx, bool& complete) override { return m_wallet->FillPSBT(psbtx, complete, sighash_type, sign, bip32derivs, n_signed); } WalletBalances getBalances() override { const auto bal = GetBalance(*m_wallet); WalletBalances result; result.balance = bal.m_mine_trusted; result.unconfirmed_balance = bal.m_mine_untrusted_pending; result.immature_balance = bal.m_mine_immature; result.have_watch_only = haveWatchOnly(); if (result.have_watch_only) { result.watch_only_balance = bal.m_watchonly_trusted; result.unconfirmed_watch_only_balance = bal.m_watchonly_untrusted_pending; result.immature_watch_only_balance = bal.m_watchonly_immature; } return result; } bool tryGetBalances(WalletBalances& balances, uint256& block_hash) override { TRY_LOCK(m_wallet->cs_wallet, locked_wallet); if (!locked_wallet) { return false; } block_hash = m_wallet->GetLastBlockHash(); balances = getBalances(); return true; } CAmount getBalance() override { return GetBalance(*m_wallet).m_mine_trusted; } CAmount getAvailableBalance(const CCoinControl& coin_control) override { LOCK(m_wallet->cs_wallet); CAmount total_amount = 0; // Fetch selected coins total amount if (coin_control.HasSelected()) { FastRandomContext rng{}; CoinSelectionParams params(rng); // Note: for now, swallow any error. if (auto res = FetchSelectedInputs(*m_wallet, coin_control, params)) { total_amount += res->total_amount; } } // And fetch the wallet available coins if (coin_control.m_allow_other_inputs) { total_amount += AvailableCoins(*m_wallet, &coin_control).GetTotalAmount(); } return total_amount; } isminetype txinIsMine(const CTxIn& txin) override { LOCK(m_wallet->cs_wallet); return InputIsMine(*m_wallet, txin); } isminetype txoutIsMine(const CTxOut& txout) override { LOCK(m_wallet->cs_wallet); return m_wallet->IsMine(txout); } CAmount getDebit(const CTxIn& txin, isminefilter filter) override { LOCK(m_wallet->cs_wallet); return m_wallet->GetDebit(txin, filter); } CAmount getCredit(const CTxOut& txout, isminefilter filter) override { LOCK(m_wallet->cs_wallet); return OutputGetCredit(*m_wallet, txout, filter); } CoinsList listCoins() override { LOCK(m_wallet->cs_wallet); CoinsList result; for (const auto& entry : ListCoins(*m_wallet)) { auto& group = result[entry.first]; for (const auto& coin : entry.second) { group.emplace_back(coin.outpoint, MakeWalletTxOut(*m_wallet, coin)); } } return result; } std::vector getCoins(const std::vector& outputs) override { LOCK(m_wallet->cs_wallet); std::vector result; result.reserve(outputs.size()); for (const auto& output : outputs) { result.emplace_back(); auto it = m_wallet->mapWallet.find(output.hash); if (it != m_wallet->mapWallet.end()) { int depth = m_wallet->GetTxDepthInMainChain(it->second); if (depth >= 0) { result.back() = MakeWalletTxOut(*m_wallet, it->second, output.n, depth); } } } return result; } CAmount getRequiredFee(unsigned int tx_bytes) override { return GetRequiredFee(*m_wallet, tx_bytes); } CAmount getMinimumFee(unsigned int tx_bytes, const CCoinControl& coin_control, int* returned_target, FeeReason* reason) override { FeeCalculation fee_calc; CAmount result; result = GetMinimumFee(*m_wallet, tx_bytes, coin_control, &fee_calc); if (returned_target) *returned_target = fee_calc.returnedTarget; if (reason) *reason = fee_calc.reason; return result; } unsigned int getConfirmTarget() override { return m_wallet->m_confirm_target; } bool hdEnabled() override { return m_wallet->IsHDEnabled(); } bool canGetAddresses() override { return m_wallet->CanGetAddresses(); } bool hasExternalSigner() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER); } bool privateKeysDisabled() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); } bool taprootEnabled() override { if (m_wallet->IsLegacy()) return false; auto spk_man = m_wallet->GetScriptPubKeyMan(OutputType::BECH32M, /*internal=*/false); return spk_man != nullptr; } OutputType getDefaultAddressType() override { return m_wallet->m_default_address_type; } CAmount getDefaultMaxTxFee() override { return m_wallet->m_default_max_tx_fee; } void remove() override { RemoveWallet(m_context, m_wallet, /*load_on_start=*/false); } bool isLegacy() override { return m_wallet->IsLegacy(); } std::unique_ptr handleUnload(UnloadFn fn) override { return MakeSignalHandler(m_wallet->NotifyUnload.connect(fn)); } std::unique_ptr handleShowProgress(ShowProgressFn fn) override { return MakeSignalHandler(m_wallet->ShowProgress.connect(fn)); } std::unique_ptr handleStatusChanged(StatusChangedFn fn) override { return MakeSignalHandler(m_wallet->NotifyStatusChanged.connect([fn](CWallet*) { fn(); })); } std::unique_ptr handleAddressBookChanged(AddressBookChangedFn fn) override { return MakeSignalHandler(m_wallet->NotifyAddressBookChanged.connect( [fn](const CTxDestination& address, const std::string& label, bool is_mine, AddressPurpose purpose, ChangeType status) { fn(address, label, is_mine, purpose, status); })); } std::unique_ptr handleTransactionChanged(TransactionChangedFn fn) override { return MakeSignalHandler(m_wallet->NotifyTransactionChanged.connect( [fn](const uint256& txid, ChangeType status) { fn(txid, status); })); } std::unique_ptr handleWatchOnlyChanged(WatchOnlyChangedFn fn) override { return MakeSignalHandler(m_wallet->NotifyWatchonlyChanged.connect(fn)); } std::unique_ptr handleCanGetAddressesChanged(CanGetAddressesChangedFn fn) override { return MakeSignalHandler(m_wallet->NotifyCanGetAddressesChanged.connect(fn)); } CWallet* wallet() override { return m_wallet.get(); } WalletContext& m_context; std::shared_ptr m_wallet; }; class WalletLoaderImpl : public WalletLoader { public: WalletLoaderImpl(Chain& chain, ArgsManager& args) { m_context.chain = &chain; m_context.args = &args; } ~WalletLoaderImpl() override { UnloadWallets(m_context); } //! ChainClient methods void registerRpcs() override { for (const CRPCCommand& command : GetWalletRPCCommands()) { m_rpc_commands.emplace_back(command.category, command.name, [this, &command](const JSONRPCRequest& request, UniValue& result, bool last_handler) { JSONRPCRequest wallet_request = request; wallet_request.context = &m_context; return command.actor(wallet_request, result, last_handler); }, command.argNames, command.unique_id); m_rpc_handlers.emplace_back(m_context.chain->handleRpc(m_rpc_commands.back())); } } bool verify() override { return VerifyWallets(m_context); } bool load() override { return LoadWallets(m_context); } void start(CScheduler& scheduler) override { m_context.scheduler = &scheduler; return StartWallets(m_context); } void flush() override { return FlushWallets(m_context); } void stop() override { return StopWallets(m_context); } void setMockTime(int64_t time) override { return SetMockTime(time); } void schedulerMockForward(std::chrono::seconds delta) override { Assert(m_context.scheduler)->MockForward(delta); } //! WalletLoader methods util::Result> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector& warnings) override { DatabaseOptions options; DatabaseStatus status; ReadDatabaseArgs(*m_context.args, options); options.require_create = true; options.create_flags = wallet_creation_flags; options.create_passphrase = passphrase; bilingual_str error; std::unique_ptr wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))}; if (wallet) { return wallet; } else { return util::Error{error}; } } util::Result> loadWallet(const std::string& name, std::vector& warnings) override { DatabaseOptions options; DatabaseStatus status; ReadDatabaseArgs(*m_context.args, options); options.require_existing = true; bilingual_str error; std::unique_ptr wallet{MakeWallet(m_context, LoadWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))}; if (wallet) { return wallet; } else { return util::Error{error}; } } util::Result> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector& warnings) override { DatabaseStatus status; bilingual_str error; std::unique_ptr wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))}; if (wallet) { return wallet; } else { return util::Error{error}; } } util::Result migrateWallet(const std::string& name, const SecureString& passphrase) override { auto res = wallet::MigrateLegacyToDescriptor(name, passphrase, m_context); if (!res) return util::Error{util::ErrorString(res)}; WalletMigrationResult out{ .wallet = MakeWallet(m_context, res->wallet), .watchonly_wallet_name = res->watchonly_wallet ? std::make_optional(res->watchonly_wallet->GetName()) : std::nullopt, .solvables_wallet_name = res->solvables_wallet ? std::make_optional(res->solvables_wallet->GetName()) : std::nullopt, .backup_path = res->backup_path, }; return out; } std::string getWalletDir() override { return fs::PathToString(GetWalletDir()); } std::vector listWalletDir() override { std::vector paths; for (auto& path : ListDatabases(GetWalletDir())) { paths.push_back(fs::PathToString(path)); } return paths; } std::vector> getWallets() override { std::vector> wallets; for (const auto& wallet : GetWallets(m_context)) { wallets.emplace_back(MakeWallet(m_context, wallet)); } return wallets; } std::unique_ptr handleLoadWallet(LoadWalletFn fn) override { return HandleLoadWallet(m_context, std::move(fn)); } WalletContext* context() override { return &m_context; } WalletContext m_context; const std::vector m_wallet_filenames; std::vector> m_rpc_handlers; std::list m_rpc_commands; }; } // namespace } // namespace wallet namespace interfaces { std::unique_ptr MakeWallet(wallet::WalletContext& context, const std::shared_ptr& wallet) { return wallet ? std::make_unique(context, wallet) : nullptr; } std::unique_ptr MakeWalletLoader(Chain& chain, ArgsManager& args) { return std::make_unique(chain, args); } } // namespace interfaces