Files
bitcoin/src/wallet/interfaces.cpp
Ava Chow 526673487c Merge bitcoin/bitcoin#34683: rpc: support a formal description of our JSON-RPC interface
ca9ffb8e12 rpc: add OpenRPC discovery alias (willcl-ark)
ef0676f400 rpc: factor getaddressinfo embedded field docs (will)
1fb6b60560 test: add functional test for getopenrpcinfo (will)
672dd42d14 rpc: add getopenrpcinfo command (will)
f5116c587f rpc: add placeholder annotation for deprecated params (will)
26c221a980 rpc: expose RPC metadata for introspection (will)
6a1a66c180 rpc: render Type::ANY in help text instead of aborting (will)
06de34a033 rpc: erase empty map entry in removeCommand (will)
d4d64ae739 rpc: add missing string_view include to server.h (will)

Pull request description:

  Fixes #29912

  This PR adds a machine-readable[ OpenRPC](https://www.open-rpc.org/) 1.4.1 specification of out JSON-RPC interface, auto-generated from existing `RPCHelpMan` metadata.

  There is currently no formal, machine-readable specification of the RPC API. As discussed in #29912, this has knock-on consequences:

  - Client libraries re-implement the API manually, leading to bugs like unit mistakes (sats vs BTC, vB vs kvB) and missing/incorrect argument types. No existing client library fully and correctly implements the API in a type-safe manner.
  - When the API changes, every downstream client must manually discover and adapt, creating downstream maintenance burden. There is no artifact they can diff between releases.
  - Implementing a new client in a new language requires reading C++ source or help text and transcribing it, which is error-prone and tedious, and represents an on-going porting cost.
  - Existing documentation is either stale or not machine-readable. The developer.bitcoin.org docs are wrong/outdated in places, and the bitcoincore.org/en/doc/ pages are rendered from help output but not in a standard schema format.
  - (new/extra) AI/LLM tooling increasingly builds on structured API specifications. A standard spec format enables AI-assisted client generation and integration without the ambiguity of parsing human-readable help text.

  This draft builds on prior art by casey and the observations by laanwj, stickies-v, kilianmh, hodlinator, and cdecker in #29912. Casey's work demonstrated that RPCHelpMan already contains all the structured information needed, which makes this feasible without duplicating any API definitions.

  This differs from Casey's branches in that it uses the OpenRPC standard rather than an ad-hoc format or raw JSON Schema.

  ### Why OpenRPC

  I seletced OpenRPC for a number of reasons:

  - It's purpose-built for JSON-RPC APIs (suggested by stickies-v,nflatrea, and kilianmh).
  - It wraps JSON schema for params/results, so consumers get both the method-level structure and the type-level schemas.
  - Unlike OpenAPI, it is not path-centric, which better fits our single-endpoint JSON-RPC model (concern raised by hodlinator).
    - Although it therefore does not cover our REST interface.
  - It's _kind of_ a standard format with (_some_) existing tooling for type generation (TypeScript, Rust, Python, Go) and client scaffolding, though maturity varies by language. More importantly though, IMO, a ~standardised format is inherently more useful than any ad-hoc one: any JSON Schema validator works, any LLM can consume it directly, and anyone can write a bespoke generator against a known schema rather than parsing help text.

  ### Approach

  `RPCHelpMan` metadata → `getopenrpcinfo` / `rpc.discover` → OpenRPC JSON

  ### Tradeoffs

  vs an ad-hoc format OpenRPC gives us interoperability with the (admittedly surprisingly limited) tooling, documentation generators, code generators, and validators, at the cost of needing x-bitcoin-* extensions for Bitcoin-specific concepts. As Casey noted after trying both approaches, JSON Schema "is probably not a great fit". OpenRPC's method-level framing on top of JSON Schema addresses the ergonomic issues while keeping the schema benefits. After testing both, I think I agree.

  Types: JSON Schema cannot natively express all Bitcoin-specific semantics. Amount result fields are represented as JSON numbers with `x-bitcoin-unit: amount`; other Bitcoin-specific distinctions remain in descriptions or `x-bitcoin-*` extensions. More structured unit metadata and stronger constraints can be added in follow-up work.

  Some RPCs return different types depending on argument values (e.g. verbosity levels). These are represented as `oneOf` in the result schema with free-text condition descriptions. This is accurate but not fully machine-parseable — a code generator cannot automatically determine which result variant corresponds to which argument value without parsing the description. I still we have enough information to satisfy humans an agents alike though.

  ### Regenerating the spec

  The functional test invokes both RPCs, verifies valid JSON, checks public and hidden RPC handling, and covers representative generated schemas. It does not compare a committed generated artifact.

  `getopenrpcinfo` omits hidden RPCs and arguments by default; `getopenrpcinfo(true)` includes them. The standard parameterless `rpc.discover` method returns the public document.

  The RPC output documents which RPCs are available for any given built binary.

  ### Discussion questions

  - Is this valuable/wanted?
  - Do we like openrpc format? (less relevant if we don't want this in this repo, as another repo could generate one or many definitions).
  - Should we cover "hidden" RPCs? They are currently hidden, but don't have to be...

  My personal thoughts are that this is very nice to have.

ACKs for top commit:
  dergoegge:
    ACK ca9ffb8e12
  achow101:
    ACK ca9ffb8e12
  sedited:
    ACK ca9ffb8e12
  w0xlt:
    ACK ca9ffb8e12

Tree-SHA512: 5bf7abdb9f119d591306884f31b9da815908e5cb5812706c5496b39162c22cd949465d7b3ff1fdce7ff5b3c1b6367c14b6d28d58dee86698c0161d4d844d84c9
2026-07-23 10:57:25 -07:00

677 lines
27 KiB
C++

// Copyright (c) 2018-present 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 <interfaces/wallet.h>
#include <common/args.h>
#include <consensus/amount.h>
#include <interfaces/chain.h>
#include <interfaces/handler.h>
#include <node/types.h>
#include <policy/fees/block_policy_estimator.h>
#include <primitives/transaction.h>
#include <rpc/server.h>
#include <scheduler.h>
#include <support/allocators/secure.h>
#include <sync.h>
#include <uint256.h>
#include <util/check.h>
#include <util/translation.h>
#include <util/ui_change_type.h>
#include <wallet/coincontrol.h>
#include <wallet/context.h>
#include <wallet/feebumper.h>
#include <wallet/fees.h>
#include <wallet/load.h>
#include <wallet/receive.h>
#include <wallet/rpc/wallet.h>
#include <wallet/spend.h>
#include <wallet/wallet.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
using common::PSBTError;
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::WalletTx;
using interfaces::WalletTxOut;
using interfaces::WalletTxStatus;
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()) :
false);
}
result.credit = CachedTxGetCredit(wallet, wtx, /*avoid_reuse=*/true);
result.debit = CachedTxGetDebit(wallet, wtx, /*avoid_reuse=*/true);
result.change = CachedTxGetChange(wallet, wtx);
result.time = wtx.GetTxTime();
result.from = wtx.m_from;
result.message = wtx.m_message;
result.comment = wtx.m_comment;
result.comment_to = wtx.m_comment_to;
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<TxStateConfirmed>() ? wtx.state<TxStateConfirmed>()->confirmed_block_height :
wtx.state<TxStateBlockConflicted>() ? wtx.state<TxStateBlockConflicted>()->conflicting_block_height :
std::numeric_limits<int>::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<CWallet>& 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->HasEncryptionKeys(); }
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<CTxDestination> 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<SigningProvider> 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);
}
bool setAddressBook(const CTxDestination& dest, const std::string& name, const std::optional<AddressPurpose>& 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,
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();
}
if (purpose) {
// In very old wallets, address purpose may not be recorded so we derive it from IsMine
*purpose = entry->purpose.value_or(m_wallet->IsMine(dest) ? AddressPurpose::RECEIVE : AddressPurpose::SEND);
}
return true;
}
std::vector<WalletAddress> getAddresses() override
{
LOCK(m_wallet->cs_wallet);
std::vector<WalletAddress> result;
m_wallet->ForEachAddrBookEntry([&](const CTxDestination& dest, const std::string& label, bool is_change, const std::optional<AddressPurpose>& purpose) EXCLUSIVE_LOCKS_REQUIRED(m_wallet->cs_wallet) {
if (is_change) return;
bool 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<std::string> 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<void> 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);
return m_wallet->LockCoin(output, write_to_db);
}
bool unlockCoin(const COutPoint& output) override
{
LOCK(m_wallet->cs_wallet);
return m_wallet->UnlockCoin(output);
}
bool isLockedCoin(const COutPoint& output) override
{
LOCK(m_wallet->cs_wallet);
return m_wallet->IsLockedCoin(output);
}
void listLockedCoins(std::vector<COutPoint>& outputs) override
{
LOCK(m_wallet->cs_wallet);
return m_wallet->ListLockedCoins(outputs);
}
util::Result<wallet::CreatedTransactionResult> createTransaction(const std::vector<CRecipient>& recipients,
const CCoinControl& coin_control,
bool sign,
std::optional<unsigned int> change_pos) override
{
LOCK(m_wallet->cs_wallet);
return CreateTransaction(*m_wallet, recipients, change_pos, coin_control, sign);
}
void commitTransaction(CTransactionRef tx, const std::vector<std::string>& messages) override
{
LOCK(m_wallet->cs_wallet);
m_wallet->CommitTransaction(std::move(tx), /*replaces_txid=*/std::nullopt, /*comment=*/std::nullopt, /*comment_to=*/std::nullopt, messages);
}
bool transactionCanBeAbandoned(const Txid& txid) override { return m_wallet->TransactionCanBeAbandoned(txid); }
bool abandonTransaction(const Txid& txid) override
{
LOCK(m_wallet->cs_wallet);
return m_wallet->AbandonTransaction(txid);
}
bool transactionCanBeBumped(const Txid& txid) override
{
return feebumper::TransactionCanBeBumped(*m_wallet.get(), txid);
}
bool createBumpTransaction(const Txid& txid,
const CCoinControl& coin_control,
std::vector<bilingual_str>& errors,
CAmount& old_fee,
CAmount& new_fee,
CMutableTransaction& mtx) override
{
std::vector<CTxOut> 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 Txid& txid,
CMutableTransaction&& mtx,
std::vector<bilingual_str>& errors,
Txid& bumped_txid) override
{
return feebumper::CommitTransaction(*m_wallet.get(), txid, std::move(mtx), errors, bumped_txid) ==
feebumper::Result::OK;
}
CTransactionRef getTx(const Txid& 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 Txid& 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<WalletTx> getWalletTxs() override
{
LOCK(m_wallet->cs_wallet);
std::set<WalletTx> result;
for (const auto& entry : m_wallet->mapWallet) {
result.emplace(MakeWalletTx(*m_wallet, entry.second));
}
return result;
}
bool tryGetTxStatus(const Txid& 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 Txid& txid,
WalletTxStatus& tx_status,
std::vector<std::string>& messages,
std::vector<std::string>& payment_requests,
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();
messages = mi->second.m_messages;
payment_requests = mi->second.m_payment_requests;
tx_status = MakeWalletTxStatus(*m_wallet, mi->second);
return MakeWalletTx(*m_wallet, mi->second);
}
return {};
}
std::optional<PSBTError> fillPSBT(const common::PSBTFillOptions& options,
size_t* n_signed,
PartiallySignedTransaction& psbtx,
bool& complete) override
{
return m_wallet->FillPSBT(psbtx, options, complete, 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.used_balance = bal.m_mine_used;
result.nonmempool_balance = bal.m_mine_nonmempool;
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->GetTotalAmount();
}
}
// And fetch the wallet available coins
if (coin_control.m_allow_other_inputs) {
total_amount += AvailableCoins(*m_wallet, &coin_control).GetTotalAmount();
}
return total_amount;
}
bool txinIsMine(const CTxIn& txin) override
{
LOCK(m_wallet->cs_wallet);
return InputIsMine(*m_wallet, txin);
}
bool txoutIsMine(const CTxOut& txout) override
{
LOCK(m_wallet->cs_wallet);
return m_wallet->IsMine(txout);
}
CAmount getDebit(const CTxIn& txin) override
{
LOCK(m_wallet->cs_wallet);
return m_wallet->GetDebit(txin);
}
CAmount getCredit(const CTxOut& txout) override
{
LOCK(m_wallet->cs_wallet);
return OutputGetCredit(*m_wallet, txout);
}
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<WalletTxOut> getCoins(const std::vector<COutPoint>& outputs) override
{
LOCK(m_wallet->cs_wallet);
std::vector<WalletTxOut> 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 {
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);
}
std::unique_ptr<Handler> handleUnload(UnloadFn fn) override
{
return MakeSignalHandler(m_wallet->NotifyUnload.connect(fn));
}
std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) override
{
return MakeSignalHandler(m_wallet->ShowProgress.connect(fn));
}
std::unique_ptr<Handler> handleStatusChanged(StatusChangedFn fn) override
{
return MakeSignalHandler(m_wallet->NotifyStatusChanged.connect([fn](CWallet*) { fn(); }));
}
std::unique_ptr<Handler> 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<Handler> handleTransactionChanged(TransactionChangedFn fn) override
{
return MakeSignalHandler(m_wallet->NotifyTransactionChanged.connect(
[fn](const Txid& txid, ChangeType status) { fn(txid, status); }));
}
std::unique_ptr<Handler> handleCanGetAddressesChanged(CanGetAddressesChangedFn fn) override
{
return MakeSignalHandler(m_wallet->NotifyCanGetAddressesChanged.connect(fn));
}
CWallet* wallet() override { return m_wallet.get(); }
WalletContext& m_context;
std::shared_ptr<CWallet> m_wallet;
};
class WalletLoaderImpl : public WalletLoader
{
public:
WalletLoaderImpl(Chain& chain, ArgsManager& args)
{
m_context.chain = &chain;
m_context.args = &args;
}
~WalletLoaderImpl() override { stop(); }
//! 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_commands.back().metadata_fn = command.metadata_fn;
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 stop() override { return UnloadWallets(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<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& 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> 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<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) override
{
DatabaseOptions options;
DatabaseStatus status;
ReadDatabaseArgs(*m_context.args, options);
options.require_existing = true;
bilingual_str error;
std::unique_ptr<Wallet> 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<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings, bool load_after_restore) override
{
DatabaseStatus status;
bilingual_str error;
std::unique_ptr<Wallet> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings, load_after_restore))};
if (!error.empty()) {
return util::Error{error};
}
return wallet;
}
util::Result<WalletMigrationResult> 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_name,
.solvables_wallet_name = res->solvables_wallet_name,
.backup_path = res->backup_path,
};
return out;
}
bool isEncrypted(const std::string& wallet_name) override
{
auto wallets{GetWallets(m_context)};
auto it = std::find_if(wallets.begin(), wallets.end(), [&](std::shared_ptr<CWallet> w){ return w->GetName() == wallet_name; });
if (it != wallets.end()) return (*it)->HasEncryptionKeys();
// Unloaded wallet, read db
DatabaseOptions options;
options.require_existing = true;
DatabaseStatus status;
bilingual_str error;
auto db = MakeWalletDatabase(wallet_name, options, status, error);
if (!db && status == wallet::DatabaseStatus::FAILED_LEGACY_DISABLED) {
options.require_format = wallet::DatabaseFormat::BERKELEY_RO;
db = MakeWalletDatabase(wallet_name, options, status, error);
}
if (!db) return false;
return WalletBatch(*db).IsEncrypted();
}
std::string getWalletDir() override
{
return fs::PathToString(GetWalletDir());
}
std::vector<std::pair<std::string, std::string>> listWalletDir() override
{
std::vector<std::pair<std::string, std::string>> paths;
for (auto& [path, format] : ListDatabases(GetWalletDir())) {
paths.emplace_back(fs::PathToString(path), format);
}
return paths;
}
std::vector<std::unique_ptr<Wallet>> getWallets() override
{
std::vector<std::unique_ptr<Wallet>> wallets;
for (const auto& wallet : GetWallets(m_context)) {
wallets.emplace_back(MakeWallet(m_context, wallet));
}
return wallets;
}
std::unique_ptr<Handler> handleLoadWallet(LoadWalletFn fn) override
{
return HandleLoadWallet(m_context, std::move(fn));
}
WalletContext* context() override { return &m_context; }
WalletContext m_context;
const std::vector<std::string> m_wallet_filenames;
std::vector<std::unique_ptr<Handler>> m_rpc_handlers;
std::list<CRPCCommand> m_rpc_commands;
};
} // namespace
} // namespace wallet
namespace interfaces {
std::unique_ptr<Wallet> MakeWallet(wallet::WalletContext& context, const std::shared_ptr<wallet::CWallet>& wallet) { return wallet ? std::make_unique<wallet::WalletImpl>(context, wallet) : nullptr; }
std::unique_ptr<WalletLoader> MakeWalletLoader(Chain& chain, ArgsManager& args)
{
return std::make_unique<wallet::WalletLoaderImpl>(chain, args);
}
} // namespace interfaces