Skip to content

backport: Bitcoin Core v0.26 backports (batch 1)#7237

Draft
thepastaclaw wants to merge 15 commits intodashpay:developfrom
thepastaclaw:backport-0.26-batch-1-staging
Draft

backport: Bitcoin Core v0.26 backports (batch 1)#7237
thepastaclaw wants to merge 15 commits intodashpay:developfrom
thepastaclaw:backport-0.26-batch-1-staging

Conversation

@thepastaclaw
Copy link
Copy Markdown

Batch 1 of Bitcoin Core v0.26 backports.

13 commits:


Automated backport via PastaClaw backport pipeline.
Each commit was individually cherry-picked, build-tested, reviewer-verified, and CI-validated on the fork before batching.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 20, 2026

⚠️ Potential Merge Conflicts Detected

This PR has potential conflicts with the following open PRs:

Please coordinate with the authors of these PRs to avoid merge conflicts.

thepastaclaw and others added 5 commits March 19, 2026 20:29
… txindex

This backports Bitcoin PR bitcoin#25939 (commits a5b4883, 6e9f8bb) which:
- Extracts PSBT updating logic from utxoupdatepsbt into ProcessPSBT
- Makes utxoupdatepsbt look for full transactions in the txindex and
  mempool before falling back to the UTXO set
- Moves RemoveUnnecessaryTransactions from wallet to psbt module

Dash adaptations:
- Use CoreContext instead of std::any for context parameter
- RemoveUnnecessaryTransactions is a no-op (Dash has no segwit)
- UTXO set fallback for witness_utxo removed (Dash has no segwit)
- Test expectations adapted for non-segwit addresses

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@thepastaclaw thepastaclaw force-pushed the backport-0.26-batch-1-staging branch from 6d79e83 to 5e41969 Compare March 20, 2026 01:31
Copy link
Copy Markdown
Author

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

This batch backport of 13 Bitcoin Core v0.26 PRs is clean and well-adapted for Dash. All SegWit removal, naming, and constructor-signature adaptations are correct. Three minor cleanup items remain from the bitcoin#25939 PSBT commit (dead code), the bitcoin#26720 fuzz commit (missing explicit include), and the bitcoin#26933 packages commit (misleading test function).

Reviewed commit: 5e41969

🟡 2 suggestion(s) | 💬 1 nitpick(s)

2 additional findings

🟡 suggestion: `test_submit_cpfp` no longer exercises CPFP

test/functional/rpc_packages.py (lines 296-326)

Upstream removed this function (CPFP testing moved to mempool_limit.py where the mempool min fee is elevated). The Dash version retains it but sets tx_poor to pay at relayfee (line 302), which is sufficient for individual acceptance — no child fee bumping is needed. tx_rich gets in via prioritisetransaction, not CPFP. The function name is misleading since no CPFP is actually exercised. Real CPFP coverage exists in mempool_limit.py (line 87). Consider removing this function to match upstream, or renaming it to reflect what it actually tests (multi-parent package submission with prioritization).

💬 nitpick: Missing explicit `#include ` for `MAX_STANDARD_TX_SIZE`

src/wallet/test/fuzz/coinselection.cpp (lines 5-6)

The fuzz test uses MAX_STANDARD_TX_SIZE (defined in policy/policy.h) but does not include that header directly. It compiles today via the transitive chain test/fuzz/util.htxmempool.hpolicy/packages.hpolicy/policy.h. Upstream explicitly adds the include. Adding it here would match upstream and protect against future include-chain changes, but this is a minor hygiene issue rather than a bug.

💡 Suggested change
#include <policy/feerate.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/rpc/rawtransaction.cpp`:
- [SUGGESTION] lines 236-265: Dead `coins` map left behind after SegWit fallback removal
  The `coins` map (line 239) is declared, entries are inserted at line 264 for inputs not found in txindex/mempool, but the map is never read. Upstream uses it in a `FindCoins` + `IsSegWitOutput` block that populates `witness_utxo` — that block was correctly removed for Dash (no SegWit), but the map declaration, insertion, and stale comment (lines 236-238 referencing "segwit inputs") were left behind. The explanatory comment at lines 268-270 is good and should stay; the dead `coins` variable and its stale comment should be removed.

In `test/functional/rpc_packages.py`:
- [SUGGESTION] lines 296-326: `test_submit_cpfp` no longer exercises CPFP
  Upstream removed this function (CPFP testing moved to `mempool_limit.py` where the mempool min fee is elevated). The Dash version retains it but sets `tx_poor` to pay at `relayfee` (line 302), which is sufficient for individual acceptance — no child fee bumping is needed. `tx_rich` gets in via `prioritisetransaction`, not CPFP. The function name is misleading since no CPFP is actually exercised. Real CPFP coverage exists in `mempool_limit.py` (line 87). Consider removing this function to match upstream, or renaming it to reflect what it actually tests (multi-parent package submission with prioritization).

Comment on lines +236 to +265
// If we can't find the corresponding full transaction for all of our inputs,
// this will be used to find just the utxos for the segwit inputs for which
// the full transaction isn't found
std::map<COutPoint, Coin> coins;

// Fetch previous transactions:
// First, look in the txindex and the mempool
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInput& psbt_input = psbtx.inputs.at(i);
const CTxIn& tx_in = psbtx.tx->vin.at(i);

// The `non_witness_utxo` is the whole previous transaction
if (psbt_input.non_witness_utxo) continue;

CTransactionRef tx;

// Look in the txindex
if (g_txindex) {
uint256 block_hash;
g_txindex->FindTx(tx_in.prevout.hash, block_hash, tx);
}
// If we still don't have it look in the mempool
if (!tx) {
tx = node.mempool->get(tx_in.prevout.hash);
}
if (tx) {
psbt_input.non_witness_utxo = tx;
} else {
coins[tx_in.prevout]; // Create empty map entry keyed by prevout
}
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.

🟡 Suggestion: Dead coins map left behind after SegWit fallback removal

The coins map (line 239) is declared, entries are inserted at line 264 for inputs not found in txindex/mempool, but the map is never read. Upstream uses it in a FindCoins + IsSegWitOutput block that populates witness_utxo — that block was correctly removed for Dash (no SegWit), but the map declaration, insertion, and stale comment (lines 236-238 referencing "segwit inputs") were left behind. The explanatory comment at lines 268-270 is good and should stay; the dead coins variable and its stale comment should be removed.

source: ['claude']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/rpc/rawtransaction.cpp`:
- [SUGGESTION] lines 236-265: Dead `coins` map left behind after SegWit fallback removal
  The `coins` map (line 239) is declared, entries are inserted at line 264 for inputs not found in txindex/mempool, but the map is never read. Upstream uses it in a `FindCoins` + `IsSegWitOutput` block that populates `witness_utxo` — that block was correctly removed for Dash (no SegWit), but the map declaration, insertion, and stale comment (lines 236-238 referencing "segwit inputs") were left behind. The explanatory comment at lines 268-270 is good and should stay; the dead `coins` variable and its stale comment should be removed.

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.

Fixed in thepastaclaw/dash@f984d20b5d (branch fix/review-854-cleanup). Removed the dead coins map declaration, its insertion at line 264, and the stale SegWit comment. The explanatory comment about Dash not supporting the SegWit fallback is preserved.

Copy link
Copy Markdown
Author

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Clean incremental backport of bitcoin#17860 (fuzz: BIP 30, CVE-2018-17144). All Dash adaptations — cs_main locking, GetBlockSubsidy(CBlockIndex*), BlockMerkleRoot instead of RegenerateCommitments — are correctly applied. The MineBlock API refactoring (CTxIn→COutPoint) and LoadVerifyActivateChainstate extraction are properly wired to all callers. One minor locking inconsistency in the fuzz test.

Reviewed commit: 93a2516

🟡 1 suggestion(s)

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/test/fuzz/utxo_total_supply.cpp`:
- [SUGGESTION] line 114: ActiveTip() called without cs_main lock
  `GetBlockSubsidy(chainman.ActiveTip(), ...)` is called without holding `::cs_main` at lines 114 and 146. Upstream uses `GetBlockSubsidy(ActiveHeight(), ...)` which goes through the lock-acquiring `ActiveHeight()` lambda defined at line 35. The Dash adaptation correctly changed the argument from `int` to `CBlockIndex*` (matching Dash's `GetBlockSubsidy` signature) but dropped the locking wrapper. Single-threaded fuzz tests won't race in practice, but adding a parallel `ActiveTip()` lambda would match the locking discipline already used 20 lines above and prevent thread-safety annotations from flagging this if they're tightened later.

}
current_block->hashMerkleRoot = BlockMerkleRoot(*current_block);
assert(!MineBlock(node, current_block).IsNull());
circulation += GetBlockSubsidy(chainman.ActiveTip(), Params().GetConsensus());
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.

🟡 Suggestion: ActiveTip() called without cs_main lock

GetBlockSubsidy(chainman.ActiveTip(), ...) is called without holding ::cs_main at lines 114 and 146. Upstream uses GetBlockSubsidy(ActiveHeight(), ...) which goes through the lock-acquiring ActiveHeight() lambda defined at line 35. The Dash adaptation correctly changed the argument from int to CBlockIndex* (matching Dash's GetBlockSubsidy signature) but dropped the locking wrapper. Single-threaded fuzz tests won't race in practice, but adding a parallel ActiveTip() lambda would match the locking discipline already used 20 lines above and prevent thread-safety annotations from flagging this if they're tightened later.

source: ['claude']

🤖 Fix this with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/test/fuzz/utxo_total_supply.cpp`:
- [SUGGESTION] line 114: ActiveTip() called without cs_main lock
  `GetBlockSubsidy(chainman.ActiveTip(), ...)` is called without holding `::cs_main` at lines 114 and 146. Upstream uses `GetBlockSubsidy(ActiveHeight(), ...)` which goes through the lock-acquiring `ActiveHeight()` lambda defined at line 35. The Dash adaptation correctly changed the argument from `int` to `CBlockIndex*` (matching Dash's `GetBlockSubsidy` signature) but dropped the locking wrapper. Single-threaded fuzz tests won't race in practice, but adding a parallel `ActiveTip()` lambda would match the locking discipline already used 20 lines above and prevent thread-safety annotations from flagging this if they're tightened later.

Copy link
Copy Markdown
Author

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

All findings validated against the source code. The Codex mixed-marker round-trip concern is upstream Bitcoin Core bitcoin#26076 behavior faithfully backported — not a Dash-specific regression. The only real Dash-specific observation is a minor format specifier inconsistency in CoinJoin detection code that has no functional impact due to tinyformat's type-safe handling. No blocking issues found. The backport is clean.

Reviewed commit: c624011

💬 1 nitpick(s)

Comment on lines +2064 to +2067
std::string match = strprintf("/%dh/%sh/4h/0h", BIP32_PURPOSE_FEATURE, Params().ExtCoinType());
// Fallback: also match old-format descriptors that used apostrophe marker
std::string match_legacy = strprintf("/%d'/%s'/4'/0'", BIP32_PURPOSE_FEATURE, Params().ExtCoinType());
bool is_cj = type != std::nullopt && (descriptor.find(match) != std::string::npos || descriptor.find(match_legacy) != std::string::npos);
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.

💬 Nitpick: Format specifier inconsistency: %s used for int ExtCoinType()

Both match and match_legacy use %s for Params().ExtCoinType() which returns int, while BIP32_PURPOSE_FEATURE (also integral) uses %d in the same format string. Elsewhere in the codebase (scriptpubkeyman.cpp:2156), ExtCoinType() uses %d. This is purely cosmetic — tinyformat is type-safe and handles it correctly — but %d would be more consistent.

💡 Suggested change
Suggested change
std::string match = strprintf("/%dh/%sh/4h/0h", BIP32_PURPOSE_FEATURE, Params().ExtCoinType());
// Fallback: also match old-format descriptors that used apostrophe marker
std::string match_legacy = strprintf("/%d'/%s'/4'/0'", BIP32_PURPOSE_FEATURE, Params().ExtCoinType());
bool is_cj = type != std::nullopt && (descriptor.find(match) != std::string::npos || descriptor.find(match_legacy) != std::string::npos);
std::string match = strprintf("/%dh/%dh/4h/0h", BIP32_PURPOSE_FEATURE, Params().ExtCoinType());
std::string match_legacy = strprintf("/%d'/%d'/4'/0'", BIP32_PURPOSE_FEATURE, Params().ExtCoinType());

source: ['claude']

Copy link
Copy Markdown
Author

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Trivial incremental: 1 commit (e495bd0) fixing flaky LLMQ signing test — increased wait_for_sigs timeouts from 15s→30s and 2s→15s, and bumped mocktime from 2s→10s to ensure recovery rotation. Pure test reliability fix, no production code changes.

Reviewed commit: e495bd0

@thepastaclaw thepastaclaw force-pushed the backport-0.26-batch-1-staging branch 3 times, most recently from 60c4a65 to 946d202 Compare March 25, 2026 14:48
thepastaclaw and others added 9 commits March 25, 2026 09:57
…s in rpc_psbt.py (#9)

* Merge bitcoin#27325: test: various `converttopsbt` check cleanups in rpc_psbt.py

afc2dd5 test: various `converttopsbt` check cleanups in rpc_psbt.py (Sebastian Falbesoner)

Pull request description:

  In the functional test rpc_psbt.py, some comments around the `converttopsbt` RPC checks are wrong or outdated and can be removed:

  > _Error could be either "TX decode failed" (segwit inputs causes
  > parsing to fail) or "Inputs must not have scriptSigs and
  > scriptWitnesses"_

  Decoding a valid TX with at least one input always succeeds with the [heuristic](https://github.com/bitcoin/bitcoin/blob/e352f5ab6b60ec1cc549997275e945238508cdee/src/core_read.cpp#L126), i.e. this comment is not right and we can assert for the error string "Inputs must not have scriptSigs and scriptWitnesses" on the calls below.

  > _We must set iswitness=True because the serialized transaction has
  > inputs and is therefore a witness transaction_

  This is also unneeded (and confusing, w.r.t. "is therefore a witness transaction"?), for a TX with one input there is no need to set the `iswitness` parameter. For sake of completeness, we still keep one variant where iswitness is explicitly set to true.

  Lastly, there is a superflous `converttopsbt` call on the raw tx which is the same as just [about ~10 lines above](https://github.com/bitcoin/bitcoin/blob/master/test/functional/rpc_psbt.py#L393-L397), so it can be removed.

ACKs for top commit:
  ismaelsadeeq:
    tested ACK afc2dd5
  achow101:
    ACK afc2dd5

Tree-SHA512: 467efefdb3f61efdb79145044b90fc8dc2f0c425f078117a99112b0074e7d4a32c34e464f665fbf8de70d06caaa5d4ad5908c1d75d2e7607eccb0837480afab3

* fix: remove iswitness test case — Dash converttopsbt does not support iswitness parameter

---------

Co-authored-by: Andrew Chow <github@achow101.com>
Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>
…ck exceeds blockfile size (#5)

* Merge bitcoin#27191: blockstorage: Adjust fastprune limit if block exceeds blockfile size

8f14fc8 test: cover fastprune with excessive block size (Matthew Zipkin)
271c23e blockstorage: Adjust fastprune limit if block exceeds blockfile size (Martin Zumsande)

Pull request description:

  The debug-only `-fastprune` option used in several tests is not always safe to use:
  If a `-fastprune` node receives a block larger than the maximum blockfile size of `64kb` bad things happen: The while loop in `BlockManager::FindBlockPos` never terminates, and the node runs oom because memory for `m_blockfile_info` is allocated in each iteration of the loop.
  The same would happen if a naive user used `-fastprune` on anything other than regtest (so this can be tested by syncing on signet for example, the first block that crashes the node is at height 2232).

  Change the approach by raising the blockfile size to the size of the block, if that block otherwise wouldn't fit (idea by TheCharlatan).

ACKs for top commit:
  ryanofsky:
    Code review ACK 8f14fc8. Added new assert, test, and comment since last review
  TheCharlatan:
    ACK 8f14fc8
  pinheadmz:
    ACK 8f14fc8

Tree-SHA512: df2fea30613ef9d40ebbc2416eacb574f6d7d96847db5c33dda22a29a2c61a8db831aa9552734ea4477e097f253dbcb6dcb1395d43d2a090cc0588c9ce66eac3

* fix: adapt feature_fastprune.py for Dash (no SegWit witness support)

Bitcoin's test uses a SegWit witness annex to create an oversized block.
Dash has no SegWit, so instead create a 64 KiB+ tx via MiniWallet's
target_weight with an OP_RETURN padding output (using -datacarriersize
to allow the large data carrier). Mine it with generateblock to bypass
mempool standardness rules.

This exercises the same code path as Bitcoin's test: a block whose
serialized size exceeds the 64 KiB fastprune blockfile limit, triggering
the dynamic max_blockfile_size adjustment from bitcoin#27191.

* fix: generate blocks to MiniWallet address in feature_fastprune test

The test was generating blocks to self.nodes[0] (the node's default
address) instead of wallet (MiniWallet's address).  This meant
MiniWallet had no spendable UTXOs, causing a StopIteration when
create_self_transfer tried to find one.

Pass wallet instead of self.nodes[0] to self.generate() so that
MiniWallet.generate() sends coinbase rewards to its own address and
tracks them as spendable UTXOs.

* fix: remove absolute block count assert in feature_fastprune (Dash starts at 200)

* ci: retrigger CI (flaky llmq signing test)

---------

Co-authored-by: fanquake <fanquake@gmail.com>
Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>
…ock to measure durations (#11)

* Merge bitcoin#27405: util: Use steady clock instead of system clock to measure durations

fa83fb3 wallet: Use steady clock to calculate number of derive iterations (MarcoFalke)
fa2c099 wallet: Use steady clock to measure scanning duration (MarcoFalke)
fa97621 qt: Use steady clock to throttle GUI notifications (MarcoFalke)
fa1d804 test: Use steady clock in index tests (MarcoFalke)
fa454dc net: Use steady clock in InterruptibleRecv (MarcoFalke)

Pull request description:

  `GetTimeMillis` has multiple issues:

  * It doesn't denote the underlying clock type
  * It isn't type-safe
  * It is used incorrectly in places that should use a steady clock

  Fix all issues here.

ACKs for top commit:
  willcl-ark:
    ACK fa83fb3
  martinus:
    Code review ACK bitcoin@fa83fb3, also ran all tests. All usages of the steady_clock are just for duration measurements, so the change to a different epoch is ok.

Tree-SHA512: 5ec4fede8c7f97e2e08863c011856e8304f16ba30a68fdeb42f96a50a04961092cbe46ccf9ea6ac99ff5203c09f9e0924eb483eb38d7df0759addc85116c8a9f

* fix: add MillisecondsDouble type alias for backport compatibility

* fix: add missing util/time.h include to wallet.cpp

The backport of bitcoin#27405 introduced usage of MillisecondsDouble
and SteadyClock in wallet/wallet.cpp but didn't add the corresponding
include for util/time.h, causing build failures across all CI
platforms.

---------

Co-authored-by: fanquake <fanquake@gmail.com>
Co-authored-by: PastaClaw <thepastaclaw@users.noreply.github.com>
@thepastaclaw thepastaclaw force-pushed the backport-0.26-batch-1-staging branch 2 times, most recently from 090e3ac to 5cf8960 Compare March 25, 2026 15:00
…ion marker to h)

Adapt Dash-specific wallet code, CoinJoin descriptor detection, and functional
tests to use the 'h' hardened derivation marker introduced by bitcoin#26076.
Updates both descriptor and legacy wallet test paths.
@thepastaclaw thepastaclaw force-pushed the backport-0.26-batch-1-staging branch from 5cf8960 to 6ef4468 Compare March 25, 2026 15:09
Copy link
Copy Markdown
Author

@thepastaclaw thepastaclaw left a comment

Choose a reason for hiding this comment

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

Code Review

Clean backport of 13 Bitcoin Core v0.26 PRs. All conflict resolutions are correct and Dash adaptations (no SegWit, CoinJoin, masternode subsystems) are properly handled throughout. No blocking issues. One convergent soft prerequisite noted (bitcoin#25781 for BlockManager::Options), correctly adapted around. Two minor pre-existing code observations noted.

Reviewed commit: 6ef4468

💬 2 nitpick(s)

Comment on lines +12 to +18
/**
* An options struct for `BlockManager`, more ergonomically referred to as
* `BlockManager::Options` due to the using-declaration in `BlockManager`.
*/
struct BlockManagerOpts {
const CChainParams& chainparams;
};
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.

💬 Nitpick: Soft prerequisite: bitcoin#25781 (BlockManager globals removal)

Upstream bitcoin#27570 builds on top of bitcoin#25781 which moved pruning globals into BlockManager::Options. Since bitcoin#25781 is not backported to Dash, this PR creates blockmanager_opts.h from scratch with only chainparams (no prune_target), and keeps the existing fPruneMode/nPruneTarget globals in blockstorage.h. This is correctly handled — the Dash commit adapts cleanly by initializing m_blockman{{chainparams}} in ChainstateManager's constructor. No semantic gap, just noting the divergence for tracking purposes.

source: ['claude', 'codex']

mempoolmin_feerate = node.getmempoolinfo()["mempoolminfee"]
target_weight_each = 50000
assert_greater_than(target_weight_each * 2, node.getmempoolinfo()["maxmempool"] - node.getmempoolinfo()["bytes"])
parent_fee = (mempoolmin_feerate / 1000) * (target_weight_each // 4) - Decimal("0.00001")
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.

💬 Nitpick: Vestigial // 4 weight-to-vbyte conversion from Bitcoin

parent_fee = (mempoolmin_feerate / 1000) * (target_weight_each // 4) - Decimal("0.00001") — the // 4 converts Bitcoin weight units to vbytes, but in Dash weight == serialized size (no SegWit discount). The test still passes because the assertion checks parent is below mempoolminfee (true, just by a wider margin than Bitcoin's version). Not blocking, but the edge case is tested less precisely.

💡 Suggested change
Suggested change
parent_fee = (mempoolmin_feerate / 1000) * (target_weight_each // 4) - Decimal("0.00001")
parent_fee = (mempoolmin_feerate / 1000) * target_weight_each - Decimal("0.00001")

source: ['claude']

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant