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
2 changes: 0 additions & 2 deletions src/Makefile.test.include
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,6 @@ FUZZ_WALLET_SRC += \
endif # USE_SQLITE

BITCOIN_TEST_SUITE += \
wallet/test/util.cpp \
wallet/test/util.h \
wallet/test/wallet_test_fixture.cpp \
wallet/test/wallet_test_fixture.h \
wallet/test/init_test_fixture.cpp \
Expand Down
12 changes: 10 additions & 2 deletions src/Makefile.test_util.include
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,14 @@ TEST_UTIL_H = \
test/util/str.h \
test/util/transaction_utils.h \
test/util/txmempool.h \
test/util/wallet.h \
test/util/validation.h \
test/util/xoroshiro128plusplus.h

if ENABLE_WALLET
TEST_UTIL_H += \
wallet/test/util.h
endif # ENABLE_WALLET

libtest_util_a_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BOOST_CPPFLAGS)
libtest_util_a_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS)
libtest_util_a_SOURCES = \
Expand All @@ -44,5 +48,9 @@ libtest_util_a_SOURCES = \
test/util/transaction_utils.cpp \
test/util/txmempool.cpp \
test/util/validation.cpp \
test/util/wallet.cpp \
$(TEST_UTIL_H)

if ENABLE_WALLET
libtest_util_a_SOURCES += \
wallet/test/util.cpp
endif # ENABLE_WALLET
1 change: 0 additions & 1 deletion src/bench/block_assemble.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include <test/util/mining.h>
#include <test/util/script.h>
#include <test/util/setup_common.h>
#include <test/util/wallet.h>
#include <txmempool.h>
#include <validation.h>

Expand Down
10 changes: 3 additions & 7 deletions src/bench/wallet_balance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,14 @@
#include <node/context.h>
#include <test/util/mining.h>
#include <test/util/setup_common.h>
#include <test/util/wallet.h>
#include <wallet/test/util.h>
#include <validationinterface.h>
#include <wallet/receive.h>
#include <wallet/wallet.h>

#include <optional>

using wallet::CreateMockWalletDatabase;
using wallet::CWallet;
using wallet::DBErrors;
using wallet::WALLET_FLAG_DESCRIPTORS;

namespace wallet {
static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const bool add_mine, const uint32_t epoch_iters)
{
const auto test_setup = MakeNoLogFileContext<const TestingSetup>();
Expand All @@ -29,7 +25,6 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b
LOCK(wallet.cs_wallet);
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet.SetupDescriptorScriptPubKeyMans("", "");
if (wallet.LoadWallet() != DBErrors::LOAD_OK) assert(false);
}
auto handler = test_setup->m_node.chain->handleNotifications({&wallet, [](CWallet*) {}});

Expand Down Expand Up @@ -59,3 +54,4 @@ BENCHMARK(WalletBalanceDirty, benchmark::PriorityLevel::HIGH);
BENCHMARK(WalletBalanceClean, benchmark::PriorityLevel::HIGH);
BENCHMARK(WalletBalanceMine, benchmark::PriorityLevel::HIGH);
BENCHMARK(WalletBalanceWatch, benchmark::PriorityLevel::HIGH);
} // namespace wallet
39 changes: 7 additions & 32 deletions src/bench/wallet_loading.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#include <node/context.h>
#include <test/util/mining.h>
#include <test/util/setup_common.h>
#include <test/util/wallet.h>
#include <wallet/test/util.h>
#include <util/translation.h>
#include <validationinterface.h>
#include <wallet/context.h>
Expand All @@ -16,33 +16,7 @@

#include <optional>

using wallet::CWallet;
using wallet::DatabaseFormat;
using wallet::DatabaseOptions;
using wallet::TxStateInactive;
using wallet::WALLET_FLAG_DESCRIPTORS;
using wallet::WalletContext;
using wallet::WalletDatabase;

static std::shared_ptr<CWallet> BenchLoadWallet(std::unique_ptr<WalletDatabase> database, WalletContext& context, DatabaseOptions& options)
{
bilingual_str error;
std::vector<bilingual_str> warnings;
auto wallet = CWallet::Create(context, "", std::move(database), options.create_flags, error, warnings);
NotifyWalletLoaded(context, wallet);
if (context.chain) {
wallet->postInitProcess();
}
return wallet;
}

static void BenchUnloadWallet(std::shared_ptr<CWallet>&& wallet)
{
SyncWithValidationInterfaceQueue();
wallet->m_chain_notifications_handler.reset();
UnloadWallet(std::move(wallet));
}

namespace wallet {
static void AddTx(CWallet& wallet)
{
CMutableTransaction mtx;
Expand Down Expand Up @@ -95,7 +69,7 @@ static void WalletLoading(benchmark::Bench& bench, bool legacy_wallet)
options.require_format = DatabaseFormat::SQLITE;
}
auto database = CreateMockWalletDatabase(options);
auto wallet = BenchLoadWallet(std::move(database), context, options);
auto wallet = TestLoadWallet(std::move(database), context, options.create_flags);

// Generate a bunch of transactions and addresses to put into the wallet
for (int i = 0; i < 1000; ++i) {
Expand All @@ -105,14 +79,14 @@ static void WalletLoading(benchmark::Bench& bench, bool legacy_wallet)
database = DuplicateMockDatabase(wallet->GetDatabase(), options);

// reload the wallet for the actual benchmark
BenchUnloadWallet(std::move(wallet));
TestUnloadWallet(std::move(wallet));

bench.epochs(5).run([&] {
wallet = BenchLoadWallet(std::move(database), context, options);
wallet = TestLoadWallet(std::move(database), context, options.create_flags);

// Cleanup
database = DuplicateMockDatabase(wallet->GetDatabase(), options);
BenchUnloadWallet(std::move(wallet));
TestUnloadWallet(std::move(wallet));
Comment on lines 71 to +89

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🔴 Blocking: Missing prerequisite: bitcoin#26715

Upstream bitcoin#27666 starts after bitcoin#26715. Its pre-state already uses CreateMockableWalletDatabase, keeps DuplicateMockDatabase in wallet/test/util.cpp, and gives BenchLoadWallet a uint64_t create_flags parameter. Dash base instead retains CreateMockWalletDatabase(options), a benchmark-local cursor-copying DuplicateMockDatabase, and a DatabaseOptions& load helper. This backport moves the load/unload helpers while preserving that older database path, which is a clean workaround but does not supply the upstream starting state. bitcoin#26715 introduced the missing MockableDatabase content and is not an ancestor of ecf23823; no explicit Dash exclusion is documented. This is soft because the adapted benchmark and helpers still build and operate using Dash's older database implementation.


Policy gate (backport-prereq-restore): For full upstream backport PRs, a missing prerequisite is blocking unless the finding is explicitly allowlisted (e.g. intentional_exclusion: true or a matching entry in policy_overrides). The agent's original evidence above is the basis for this block; either backport the prerequisite or annotate the intentional exclusion in the PR description.

source: ['codex-backport-reviewer']

@thepastaclaw thepastaclaw Jul 11, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Resolved by documenting intentional_exclusion: bitcoin#26715 in the PR body. The PR now explicitly records the retained Dash CreateMockWalletDatabase / benchmark-local duplication path; the exact-head backport gate confirmed the adaptation is coherent and no tree change is required.

});
}

Expand All @@ -125,3 +99,4 @@ BENCHMARK(WalletLoadingLegacy, benchmark::PriorityLevel::HIGH);
static void WalletLoadingDescriptors(benchmark::Bench& bench) { WalletLoading(bench, /*legacy_wallet=*/false); }
BENCHMARK(WalletLoadingDescriptors, benchmark::PriorityLevel::HIGH);
#endif
} // namespace wallet
3 changes: 3 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "walletprocesspsbt", 1, "sign" },
{ "walletprocesspsbt", 3, "bip32derivs" },
{ "walletprocesspsbt", 4, "finalize" },
{ "descriptorprocesspsbt", 1, "descriptors"},
{ "descriptorprocesspsbt", 3, "bip32derivs" },
{ "descriptorprocesspsbt", 4, "finalize" },
{ "createpsbt", 0, "inputs" },
{ "createpsbt", 1, "outputs" },
{ "createpsbt", 2, "locktime" },
Expand Down
92 changes: 86 additions & 6 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,9 @@ static std::vector<RPCArg> CreateTxDoc()
};
}

// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const CoreContext& context, const HidingSigningProvider& provider)
// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors.
// Optionally, sign the inputs that we can using information from the descriptors.
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const CoreContext& context, const HidingSigningProvider& provider, int sighash_type, bool finalize)
{
// Unserialize the transactions
PartiallySignedTransaction psbtx;
Expand Down Expand Up @@ -304,9 +305,10 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const Cor
}

// Update script/keypath information using descriptor data.
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures
// we don't actually care about those here, in fact.
SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, /*sighash=*/1);
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures.
// We only actually care about those if our signing provider doesn't hide private
// information, as is the case with `descriptorprocesspsbt`
SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, sighash_type, /*out_sigdata=*/nullptr, finalize);
Comment thread
thepastaclaw marked this conversation as resolved.
}

// Update script/keypath information using descriptor data.
Expand Down Expand Up @@ -1799,7 +1801,9 @@ static RPCHelpMan utxoupdatepsbt()
const PartiallySignedTransaction& psbtx = ProcessPSBT(
request.params[0].get_str(),
request.context,
HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false));
HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false),
/*sighash_type=*/SIGHASH_ALL,
/*finalize=*/false);

CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
Expand Down Expand Up @@ -1996,6 +2000,81 @@ static RPCHelpMan analyzepsbt()
};
}

RPCHelpMan descriptorprocesspsbt()
{
return RPCHelpMan{"descriptorprocesspsbt",
"\nUpdate all legacy inputs in a PSBT with information from output descriptors, txindex or the mempool. \n"
"Then, sign the inputs we are able to with information from the output descriptors. ",
Comment thread
thepastaclaw marked this conversation as resolved.
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"},
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of either strings or objects", {
{"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with an output descriptor and extra information", {
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
{"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "Up to what index HD chains should be explored (either end or [begin,end])"},
}},
}},
{"sighashtype", RPCArg::Type::STR, RPCArg::Default{"ALL"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n"
" \"ALL\"\n"
" \"NONE\"\n"
" \"SINGLE\"\n"
" \"ALL|ANYONECANPAY\"\n"
" \"NONE|ANYONECANPAY\"\n"
" \"SINGLE|ANYONECANPAY\""},
{"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},
{"finalize", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also finalize inputs if possible"},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "psbt", "The base64-encoded partially signed transaction"},
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
}
},
RPCExamples{
HelpExampleCli("descriptorprocesspsbt", "\"psbt\" \"[\\\"descriptor1\\\", \\\"descriptor2\\\"]\"") +
HelpExampleCli("descriptorprocesspsbt", "\"psbt\" \"[{\\\"desc\\\":\\\"mydescriptor\\\", \\\"range\\\":21}]\"")
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
// Add descriptor information to a signing provider
FlatSigningProvider provider;

auto descs = request.params[1].get_array();
for (size_t i = 0; i < descs.size(); ++i) {
EvalDescriptorStringOrObject(descs[i], provider, /*expand_priv=*/true);
}

int sighash_type = ParseSighashString(request.params[2]);
bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool();
bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool();

const PartiallySignedTransaction& psbtx = ProcessPSBT(
request.params[0].get_str(),
request.context,
HidingSigningProvider(&provider, /*hide_secret=*/false, !bip32derivs),
sighash_type,
finalize);

// Check whether or not all of the inputs are now signed
bool complete = true;
for (const auto& input : psbtx.inputs) {
complete &= PSBTInputSigned(input);
}

CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;

UniValue result(UniValue::VOBJ);

result.pushKV("psbt", EncodeBase64(ssTx));
result.pushKV("complete", complete);

return result;
},
};
}

void RegisterRawTransactionRPCCommands(CRPCTable& t)
{
static const CRPCCommand commands[]{
Expand All @@ -2015,6 +2094,7 @@ void RegisterRawTransactionRPCCommands(CRPCTable& t)
{"rawtransactions", &createpsbt},
{"rawtransactions", &converttopsbt},
{"rawtransactions", &utxoupdatepsbt},
{"rawtransactions", &descriptorprocesspsbt},
{"rawtransactions", &joinpsbts},
{"rawtransactions", &analyzepsbt},
};
Expand Down
5 changes: 4 additions & 1 deletion src/rpc/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1027,7 +1027,7 @@ UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_s
}
}

std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider)
std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider, const bool expand_priv)
{
std::string desc_str;
std::pair<int64_t, int64_t> range = {0, 1000};
Expand Down Expand Up @@ -1060,6 +1060,9 @@ std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, Fl
if (!desc->Expand(i, provider, scripts, provider)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str));
}
if (expand_priv) {
desc->ExpandPrivate(/*pos=*/i, provider, /*out=*/provider);
}
std::move(scripts.begin(), scripts.end(), std::back_inserter(ret));
}
return ret;
Expand Down
3 changes: 1 addition & 2 deletions src/rpc/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -148,8 +148,7 @@ enum class OuterType {
NONE, // Only set on first recursion
};
/** Evaluate a descriptor given as a string, or as a {"desc":...,"range":...} object, with default range of 1000. */
std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider);

std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider, const bool expand_priv = false);
struct RPCArg {
enum class Type {
OBJ,
Expand Down
1 change: 1 addition & 0 deletions src/test/fuzz/rpc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
"decoderawtransaction",
"decodescript",
"deriveaddresses",
"descriptorprocesspsbt",
"disconnectnode",
"echo",
"echojson",
Expand Down
44 changes: 0 additions & 44 deletions src/test/util/wallet.cpp

This file was deleted.

Loading