Skip to content

Commit 06e1e43

Browse files
03ad3ec Merge bitcoin#29115: [doc]: add doxygen comment describing what `CheckPackageLimits` returns (Konstantin Akimov) 0bc9aea Merge bitcoin#27199: test: fix race condition in encrypted wallet rescan tests (Konstantin Akimov) bbb5992 Merge bitcoin#29260: refactor: remove CTxMemPool::queryHashes() (Konstantin Akimov) b42203e partial Merge bitcoin#28863: wallet, mempool: propagete `checkChainLimits` error message to wallet (Konstantin Akimov) 13def7a partial Merge bitcoin#28602: descriptors: Disallow hybrid and uncompressed keys when inferring (Konstantin Akimov) 48a3b6c partial Merge bitcoin#28067: descriptors: do not return top-level only funcs as sub descriptors (Konstantin Akimov) 32155d6 Merge bitcoin#27622: Fee estimation: avoid serving stale fee estimate (Konstantin Akimov) 1898d8f partial Merge bitcoin#27279: Add "warnings", deprecate "warning" in {create,load,unload,restore}wallet (Konstantin Akimov) 6c7d92b Merge bitcoin#26347: wallet: ensure the wallet is unlocked when needed for rescanning (Konstantin Akimov) 9a55401 fix: encrypt the HD chain in sethdseed on encrypted wallets (Konstantin Akimov) Pull request description: ## Issue being fixed or feature implemented Release of Dash Core v24 is soon, the merging PRs with breaking changes will be blocked awhile, until branch for v25 won't be split with v24 and usually it takes months. ## What was done? This PR contains multiple backports with breaking changes from Bitcoin Core v25-v27, part II [wallets] ## How Has This Been Tested? Run unit & functional tests ## Breaking Changes Wallet ------ - In the createwallet, loadwallet, unloadwallet, and restorewallet RPCs, the "warning" string field is deprecated in favor of a "warnings" field that returns a JSON array of strings to better handle multiple warning messages and for consistency with other wallet RPCs. The "warning" field will be fully removed from these RPCs in v25. It can be temporarily re-enabled during the deprecation period by launching dashd with the configuration option `-deprecatedrpc=walletwarningfield`. - Wallet passphrases are needed to top up the keypool of encrypted wallets during a rescan. The following RPCs need the passphrase when rescanning: - `importdescriptors` - `rescanblockchain` - Now the wallet `CreateTransactionInternal` will have access to the package limit error string whenever its hit. `Transaction has too long of a mempool chain` is replaced to `too many unconfirmed ancestors [limit: XX]` - Changes in bitcoin#28067 [including / not including top level descriptor] is not actually breaking because `migratewallet` had not been released yet, but if it will be included in the other release it will be considered as breaking - Changes in bitcoin#28602 [Disallow hybrid and uncompressed keys when inferring] is not actually breaking because `migratewallet` had not been release yet, but if it will be included in the other release it will be considered as breaking Fee estimation ------- - If `fee_estimates.dat` has not been updated for 60 hours or more, it is considered stale and will not be read during initialization ## Checklist: - [x] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: thepastaclaw: <!-- thepastaclaw-review v1 --> <!-- thepastaclaw-review-phase v1 phase=final sha=03ad3ec0e7014cdece953a90eaa0abdd64b9a99b policy=c7608b44bdc3b803 --> ## ⚠️ DEGRADED — Final validation — Phase 1 + Phase 2 > **⚠️ DEGRADED review.** The primary review models were unavailable (`gpt-6-astra` unavailable: All credentials for model gpt-6-astra are cooling down (last error: usage_limit_reached: The usage limit has been reache), so this review ran on stand-in models: `gpt-5.6-luna` → `muse-spark-1.3-contributor`, `gpt-5.6-sol` → `muse-spark-1.3-contributor`, `gpt-5.6-terra` → `muse-spark-1.3-contributor`, `gpt-6-astra` → `muse-spark-1.3-contributor`. Both review phases and the independent verifiers still ran, but on weaker models, with Phase 1 capped at `high` effort. Treat the verdict as provisional; a full-strength re-review will run on the next push once the primary models are back. Backport bundle is sound: all nine upstream merges land with correct Dash adaptations and declared partials carry explicit BACKPORT NOTEs. No merge-resolution errors, consensus risks, or undeclared omissions remain after first-principles verification. <details> <summary>Review provenance</summary> Source: reviewer 1: `glm-5.3-flash` (agent: `phase1-reviewer`, role: `general`); reviewer 2: `glm-5.3-flash` (agent: `phase1-reviewer`, role: `backport-reviewer`); reviewer 3: `glm-5.3-flash` (agent: `phase1-reviewer`, role: `dash-core-commit-history`); reviewer 4: `muse-spark-1.3-contributor` (standing in for `gpt-6-astra`) (agent: `phase2-reviewer`, role: `general`); reviewer 5: `muse-spark-1.3-contributor` (standing in for `gpt-6-astra`) (agent: `phase2-reviewer`, role: `backport-reviewer`); reviewer 6: `muse-spark-1.3-contributor` (standing in for `gpt-6-astra`) (agent: `phase2-reviewer`, role: `dash-core-commit-history`); final verifier: `muse-spark-1.3-contributor` (standing in for `gpt-6-astra`) (agent: `astra-verifier`, role: `final-verifier`) - **Degraded mode**: `gpt-6-astra` unavailable: All credentials for model gpt-6-astra are cooling down (last error: usage_limit_reached: The usage limit has been reache (detected by probe, since 2026-09-20T20:45:34Z); stand-ins `gpt-5.6-luna` → `muse-spark-1.3-contributor`, `gpt-5.6-sol` → `muse-spark-1.3-contributor`, `gpt-5.6-terra` → `muse-spark-1.3-contributor`, `gpt-6-astra` → `muse-spark-1.3-contributor`; Phase 1 effort capped at `high` - Triage: `normal` by `muse-spark-1.3-contributor` (standing in for `gpt-6-astra`) (effort low) — Collection of wallet RPC, fee estimation, descriptor, and mempool backports with mostly plumbing and test updates and no consensus, coin-selection, crypto, or migration logic change. - Phase 1 reviewers: `glm-5.3-flash` — general (completed, effort high); agent `phase1-reviewer`, `glm-5.3-flash` — backport-reviewer (completed, effort high); agent `phase1-reviewer`, `glm-5.3-flash` — dash-core-commit-history (completed, effort high); agent `phase1-reviewer` - Phase 1 model: `glm-5.3-flash` — zai quota: 5h 89% left, weekly 89% left; passed over `gemini-3.8-flash-high` (lane failed) - Fresh verifier: `muse-spark-1.3-contributor` (standing in for `gpt-6-astra`) — final-verifier; agent `astra-verifier` - Phase 2 reviewers: `muse-spark-1.3-contributor` (standing in for `gpt-6-astra`) — general (completed, effort high); agent `phase2-reviewer`, `muse-spark-1.3-contributor` (standing in for `gpt-6-astra`) — backport-reviewer (completed, effort high); agent `phase2-reviewer`, `muse-spark-1.3-contributor` (standing in for `gpt-6-astra`) — dash-core-commit-history (completed, effort high); agent `phase2-reviewer` </details> Tree-SHA512: 5d8aa732bd7eaac19d0ab963d3b47215e6017c4aa6979f92112c274a0297f2e8dda44454a1011fc8629c52bab5810aa84cc3529c6376d8fcd6cd08412267bfec
2 parents 008f2c2 + 03ad3ec commit 06e1e43

33 files changed

Lines changed: 633 additions & 100 deletions

‎doc/release-notes-27279.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Wallet
2+
------
3+
4+
- In the createwallet, loadwallet, unloadwallet, and restorewallet RPCs, the
5+
"warning" string field is deprecated in favor of a "warnings" field that
6+
returns a JSON array of strings to better handle multiple warning messages and
7+
for consistency with other wallet RPCs. The "warning" field will be fully
8+
removed from these RPCs in v25. It can be temporarily re-enabled during the
9+
deprecation period by launching dashd with the configuration option
10+
`-deprecatedrpc=walletwarningfield`. (dash#7711)

‎src/init.cpp‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
#include <util/system.h>
8989
#include <util/thread.h>
9090
#include <util/threadnames.h>
91+
#include <util/time.h>
9192
#include <util/translation.h>
9293
#include <validation.h>
9394
#include <validationinterface.h>
@@ -834,6 +835,7 @@ void SetupServerArgs(ArgsManager& argsman)
834835
argsman.AddArg("-acceptnonstdtxn", strprintf("Relay and mine \"non-standard\" transactions (test networks only; default: %u)", DEFAULT_ACCEPT_NON_STD_TXN), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
835836
argsman.AddArg("-dustrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define dust, the value of an output such that it will cost more than its value in fees at this fee rate to spend it. (default: %s)", CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
836837
argsman.AddArg("-incrementalrelayfee=<amt>", strprintf("Fee rate (in %s/kB) used to define cost of relay, used for mempool limiting. (default: %s)", CURRENCY_UNIT, FormatMoney(DEFAULT_INCREMENTAL_RELAY_FEE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::NODE_RELAY);
838+
argsman.AddArg("-acceptstalefeeestimates", strprintf("Read fee estimates even if they are stale (%sdefault: %u) fee estimates are considered stale if they are %s hours old", "regtest only; ", DEFAULT_ACCEPT_STALE_FEE_ESTIMATES, Ticks<std::chrono::hours>(MAX_FILE_AGE)), ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST);
837839
argsman.AddArg("-bytespersigop", strprintf("Equivalent bytes per sigop in transactions for relay and mining (default: %u)", DEFAULT_BYTES_PER_SIGOP), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
838840
argsman.AddArg("-datacarrier", strprintf("Relay and mine data carrier transactions (default: %u)", DEFAULT_ACCEPT_DATACARRIER), ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
839841
argsman.AddArg("-datacarriersize",
@@ -1681,7 +1683,17 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
16811683
assert(!node.fee_estimator);
16821684
// Don't initialize fee estimation with old data if we don't relay transactions,
16831685
// as they would never get updated.
1684-
if (!ignores_incoming_txs) node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(args));
1686+
if (!ignores_incoming_txs) {
1687+
bool read_stale_estimates = args.GetBoolArg("-acceptstalefeeestimates", DEFAULT_ACCEPT_STALE_FEE_ESTIMATES);
1688+
if (read_stale_estimates && (chainparams.NetworkIDString() != CBaseChainParams::REGTEST)) {
1689+
return InitError(strprintf(_("acceptstalefeeestimates is not supported on %s chain."), chainparams.NetworkIDString()));
1690+
}
1691+
node.fee_estimator = std::make_unique<CBlockPolicyEstimator>(FeeestPath(args), read_stale_estimates);
1692+
1693+
// Flush estimates to disk periodically
1694+
CBlockPolicyEstimator* fee_estimator = node.fee_estimator.get();
1695+
node.scheduler->scheduleEvery([fee_estimator] { fee_estimator->FlushFeeEstimates(); }, FEE_FLUSH_INTERVAL);
1696+
}
16851697

16861698
assert(!node.mn_metaman);
16871699
node.mn_metaman = std::make_unique<CMasternodeMetaMan>();

‎src/interfaces/chain.h‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <blockfilter.h>
99
#include <primitives/transaction.h> // For CTransactionRef
1010
#include <util/settings.h> // For util::SettingsValue
11+
#include <util/result.h>
1112

1213
#include <functional>
1314
#include <memory>
@@ -242,7 +243,7 @@ class Chain
242243
virtual void getPackageLimits(unsigned int& limit_ancestor_count, unsigned int& limit_descendant_count) = 0;
243244

244245
//! Check if transaction will pass the mempool's chain limits.
245-
virtual bool checkChainLimits(const CTransactionRef& tx) = 0;
246+
virtual util::Result<void> checkChainLimits(const CTransactionRef& tx) = 0;
246247

247248
//! Estimate smart fee.
248249
virtual CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc = nullptr) = 0;

‎src/node/interfaces.cpp‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
#include <uint256.h>
6565
#include <util/check.h>
6666
#include <util/system.h>
67+
#include <util/result.h>
6768
#include <util/translation.h>
6869
#include <validation.h>
6970
#include <validationinterface.h>
@@ -1530,14 +1531,16 @@ class ChainImpl : public Chain
15301531
limit_ancestor_count = limits.ancestor_count;
15311532
limit_descendant_count = limits.descendant_count;
15321533
}
1533-
bool checkChainLimits(const CTransactionRef& tx) override
1534+
util::Result<void> checkChainLimits(const CTransactionRef& tx) override
15341535
{
1535-
if (!m_node.mempool) return true;
1536+
if (!m_node.mempool) return {};
15361537
LockPoints lp;
15371538
CTxMemPoolEntry entry(tx, 0, 0, 0, false, 0, lp);
15381539
const CTxMemPool::Limits& limits{m_node.mempool->m_limits};
15391540
LOCK(m_node.mempool->cs);
1540-
return m_node.mempool->CalculateMemPoolAncestors(entry, limits).has_value();
1541+
auto ancestors{m_node.mempool->CalculateMemPoolAncestors(entry, limits)};
1542+
if (!ancestors) return util::Error{util::ErrorString(ancestors)};
1543+
return {};
15411544
}
15421545
CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override
15431546
{

‎src/policy/fees.cpp‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
#include <algorithm>
2626
#include <cassert>
27+
#include <chrono>
2728
#include <cmath>
2829
#include <cstddef>
2930
#include <cstdint>
@@ -540,7 +541,7 @@ bool CBlockPolicyEstimator::_removeTx(const uint256& hash, bool inBlock)
540541
}
541542
}
542543

543-
CBlockPolicyEstimator::CBlockPolicyEstimator(const fs::path& estimation_filepath)
544+
CBlockPolicyEstimator::CBlockPolicyEstimator(const fs::path& estimation_filepath, const bool read_stale_estimates)
544545
: m_estimation_filepath{estimation_filepath}
545546
{
546547
static_assert(MIN_BUCKET_FEERATE > 0, "Min feerate must be nonzero");
@@ -558,9 +559,22 @@ CBlockPolicyEstimator::CBlockPolicyEstimator(const fs::path& estimation_filepath
558559
shortStats = std::make_unique<TxConfirmStats>(buckets, bucketMap, SHORT_BLOCK_PERIODS, SHORT_DECAY, SHORT_SCALE);
559560
longStats = std::make_unique<TxConfirmStats>(buckets, bucketMap, LONG_BLOCK_PERIODS, LONG_DECAY, LONG_SCALE);
560561

561-
// If the fee estimation file is present, read recorded estimations
562562
AutoFile est_file{fsbridge::fopen(m_estimation_filepath, "rb")};
563-
if (est_file.IsNull() || !Read(est_file)) {
563+
564+
// Whenever the fee estimation file is not present return early
565+
if (est_file.IsNull()) {
566+
LogPrintf("%s is not found. Continue anyway.\n", fs::PathToString(m_estimation_filepath));
567+
return;
568+
}
569+
570+
std::chrono::hours file_age = GetFeeEstimatorFileAge();
571+
// fee estimate file must not be too old to avoid wrong fee estimates.
572+
if (file_age > MAX_FILE_AGE && !read_stale_estimates) {
573+
LogPrintf("Fee estimation file %s too old (age=%lld > %lld hours) and will not be used to avoid serving stale estimates.\n", fs::PathToString(m_estimation_filepath), Ticks<std::chrono::hours>(file_age), Ticks<std::chrono::hours>(MAX_FILE_AGE));
574+
return;
575+
}
576+
577+
if (!Read(est_file)) {
564578
LogPrintf("Failed to read fee estimates from %s. Continue anyway.\n", fs::PathToString(m_estimation_filepath));
565579
}
566580
}
@@ -915,10 +929,16 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation
915929

916930
void CBlockPolicyEstimator::Flush() {
917931
FlushUnconfirmed();
932+
FlushFeeEstimates();
933+
}
918934

935+
void CBlockPolicyEstimator::FlushFeeEstimates()
936+
{
919937
AutoFile est_file{fsbridge::fopen(m_estimation_filepath, "wb")};
920938
if (est_file.IsNull() || !Write(est_file)) {
921939
LogPrintf("Failed to write fee estimates to %s. Continue anyway.\n", fs::PathToString(m_estimation_filepath));
940+
} else {
941+
LogPrintf("Flushed fee estimates to %s.\n", fs::PathToString(m_estimation_filepath.filename()));
922942
}
923943
}
924944

@@ -1022,3 +1042,10 @@ void CBlockPolicyEstimator::FlushUnconfirmed()
10221042
const auto endclear{SteadyClock::now()};
10231043
LogPrint(BCLog::ESTIMATEFEE, "Recorded %u unconfirmed txs from mempool in %gs\n", num_entries, Ticks<SecondsDouble>(endclear - startclear));
10241044
}
1045+
1046+
std::chrono::hours CBlockPolicyEstimator::GetFeeEstimatorFileAge()
1047+
{
1048+
auto file_time = std::filesystem::last_write_time(m_estimation_filepath);
1049+
auto now = std::filesystem::file_time_type::clock::now();
1050+
return std::chrono::duration_cast<std::chrono::hours>(now - file_time);
1051+
}

‎src/policy/fees.h‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,25 @@
1414
#include <util/fs.h>
1515

1616
#include <array>
17+
#include <chrono>
1718
#include <map>
1819
#include <memory>
1920
#include <set>
2021
#include <string>
2122
#include <vector>
2223

24+
25+
// How often to flush fee estimates to fee_estimates.dat.
26+
static constexpr std::chrono::hours FEE_FLUSH_INTERVAL{1};
27+
28+
/** fee_estimates.dat that are more than 60 hours (2.5 days) will not be read,
29+
* as the estimates in the file are stale.
30+
*/
31+
static constexpr std::chrono::hours MAX_FILE_AGE{60};
32+
33+
// Whether we allow importing a fee_estimates file older than MAX_FILE_AGE.
34+
static constexpr bool DEFAULT_ACCEPT_STALE_FEE_ESTIMATES{false};
35+
2336
class AutoFile;
2437
class CTxMemPoolEntry;
2538
class TxConfirmStats;
@@ -183,7 +196,7 @@ class CBlockPolicyEstimator
183196
const fs::path m_estimation_filepath;
184197
public:
185198
/** Create new BlockPolicyEstimator and initialize stats tracking classes with default values */
186-
CBlockPolicyEstimator(const fs::path& estimation_filepath);
199+
CBlockPolicyEstimator(const fs::path& estimation_filepath, const bool read_stale_estimates);
187200
~CBlockPolicyEstimator();
188201

189202
/** Process all the transactions that have been included in a block */
@@ -239,6 +252,13 @@ class CBlockPolicyEstimator
239252
void Flush()
240253
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
241254

255+
/** Record current fee estimations. */
256+
void FlushFeeEstimates()
257+
EXCLUSIVE_LOCKS_REQUIRED(!m_cs_fee_estimator);
258+
259+
/** Calculates the age of the file, since last modified */
260+
std::chrono::hours GetFeeEstimatorFileAge();
261+
242262
private:
243263
mutable Mutex m_cs_fee_estimator;
244264

‎src/rpc/mempool.cpp‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -342,17 +342,15 @@ UniValue MempoolToJSON(const CTxMemPool& pool, const llmq::CInstantSendManager*
342342
}
343343
return o;
344344
} else {
345+
UniValue a(UniValue::VARR);
345346
uint64_t mempool_sequence;
346-
std::vector<uint256> vtxid;
347347
{
348348
LOCK(pool.cs);
349-
pool.queryHashes(vtxid);
349+
for (const CTxMemPoolEntry& e : pool.mapTx) {
350+
a.push_back(e.GetTx().GetHash().ToString());
351+
}
350352
mempool_sequence = pool.GetSequence();
351353
}
352-
UniValue a(UniValue::VARR);
353-
for (const uint256& hash : vtxid)
354-
a.push_back(hash.ToString());
355-
356354
if (!include_mempool_sequence) {
357355
return a;
358356
} else {

‎src/rpc/util.cpp‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,3 +1187,25 @@ std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, Fl
11871187
}
11881188
return ret;
11891189
}
1190+
/** Convert a vector of bilingual strings to a UniValue::VARR containing their original untranslated values. */
1191+
[[nodiscard]] static UniValue BilingualStringsToUniValue(const std::vector<bilingual_str>& bilingual_strings)
1192+
{
1193+
CHECK_NONFATAL(!bilingual_strings.empty());
1194+
UniValue result{UniValue::VARR};
1195+
for (const auto& s : bilingual_strings) {
1196+
result.push_back(s.original);
1197+
}
1198+
return result;
1199+
}
1200+
1201+
void PushWarnings(const UniValue& warnings, UniValue& obj)
1202+
{
1203+
if (warnings.empty()) return;
1204+
obj.pushKV("warnings", warnings);
1205+
}
1206+
1207+
void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj)
1208+
{
1209+
if (warnings.empty()) return;
1210+
obj.pushKV("warnings", BilingualStringsToUniValue(warnings));
1211+
}

‎src/rpc/util.h‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,4 +456,13 @@ class RPCHelpMan
456456
RPCErrorCode RPCErrorFromTransactionError(TransactionError terr);
457457
UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_string = "");
458458

459+
/**
460+
* Push warning messages to an RPC "warnings" field as a JSON array of strings.
461+
*
462+
* @param[in] warnings Warning messages to push.
463+
* @param[out] obj UniValue object to push the warnings array object to.
464+
*/
465+
void PushWarnings(const UniValue& warnings, UniValue& obj);
466+
void PushWarnings(const std::vector<bilingual_str>& warnings, UniValue& obj);
467+
459468
#endif // BITCOIN_RPC_UTIL_H

‎src/script/descriptor.cpp‎

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,8 +1128,16 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
11281128
return nullptr;
11291129
}
11301130

1131-
std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
1131+
std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext ctx, const SigningProvider& provider)
11321132
{
1133+
// Key cannot be hybrid
1134+
if (!pubkey.IsValidNonHybrid()) {
1135+
return nullptr;
1136+
}
1137+
// Uncompressed is only allowed in TOP and P2SH contexts
1138+
if (ctx != ParseScriptContext::TOP && ctx != ParseScriptContext::P2SH && !pubkey.IsCompressed()) {
1139+
return nullptr;
1140+
}
11331141
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey);
11341142
KeyOriginInfo info;
11351143
if (provider.GetKeyOrigin(pubkey.GetID(), info)) {
@@ -1145,25 +1153,33 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
11451153

11461154
if (txntype == TxoutType::PUBKEY) {
11471155
CPubKey pubkey(data[0]);
1148-
if (pubkey.IsValidNonHybrid()) {
1149-
return std::make_unique<PKDescriptor>(InferPubkey(pubkey, ctx, provider));
1156+
if (auto pubkey_provider = InferPubkey(pubkey, ctx, provider)) {
1157+
return std::make_unique<PKDescriptor>(std::move(pubkey_provider));
11501158
}
11511159
}
11521160
if (txntype == TxoutType::PUBKEYHASH) {
11531161
uint160 hash(data[0]);
11541162
CKeyID keyid(hash);
11551163
CPubKey pubkey;
11561164
if (provider.GetPubKey(keyid, pubkey)) {
1157-
return std::make_unique<PKHDescriptor>(InferPubkey(pubkey, ctx, provider));
1165+
if (auto pubkey_provider = InferPubkey(pubkey, ctx, provider)) {
1166+
return std::make_unique<PKHDescriptor>(std::move(pubkey_provider));
1167+
}
11581168
}
11591169
}
11601170
if (txntype == TxoutType::MULTISIG) {
1171+
bool ok = true;
11611172
std::vector<std::unique_ptr<PubkeyProvider>> providers;
11621173
for (size_t i = 1; i + 1 < data.size(); ++i) {
11631174
CPubKey pubkey(data[i]);
1164-
providers.push_back(InferPubkey(pubkey, ctx, provider));
1175+
if (auto pubkey_provider = InferPubkey(pubkey, ctx, provider)) {
1176+
providers.push_back(std::move(pubkey_provider));
1177+
} else {
1178+
ok = false;
1179+
break;
1180+
}
11651181
}
1166-
return std::make_unique<MultisigDescriptor>((int)data[0][0], std::move(providers));
1182+
if (ok) return std::make_unique<MultisigDescriptor>((int)data[0][0], std::move(providers));
11671183
}
11681184
if (txntype == TxoutType::SCRIPTHASH && ctx == ParseScriptContext::TOP) {
11691185
uint160 hash(data[0]);
@@ -1175,6 +1191,10 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
11751191
}
11761192
}
11771193

1194+
// The following descriptors are all top-level only descriptors.
1195+
// So if we are not at the top level, return early.
1196+
if (ctx != ParseScriptContext::TOP) return nullptr;
1197+
11781198
CTxDestination dest;
11791199
if (ExtractDestination(script, dest)) {
11801200
if (GetScriptForDestination(dest) == script) {

0 commit comments

Comments
 (0)