pos: descriptor wallet staking with Taproot and SegWit kernel support#400
Merged
reddink merged 11 commits intoJul 16, 2026
Merged
Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 125 |
| Duplication | 5 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Enable Taproot outputs to participate in Proof-of-Stake by supporting WITNESS_V1_TAPROOT as a valid kernel type. This implementation: - Adds WITNESS_V1_TAPROOT to allowed output types for staking - Preserves Taproot script in coinstake output (key-path spending) - Maintains privacy by not converting to legacy output types - Naturally rejects script-path outputs requiring coordination Only single-key Taproot outputs (key-path) can stake autonomously. Multi-party script-path outputs will fail to sign, which is correct behavior for PoS that requires autonomous block generation. This change is required for Taproot soft fork activation and enables Taproot adopters to participate in network security through staking. (cherry picked from commit 0a6c55d62c055949a4695c7c7e965007a63efd5d)
Update CreateCoinStake to support both legacy and descriptor wallets: - PUBKEYHASH/WITNESS_V0_KEYHASH: Iterate all ScriptPubKeyMans to find keys, using CanProvide to check ownership and appropriate GetKey method based on wallet type - PUBKEY: Add key ownership verification by extracting keyid from pubkey and checking all ScriptPubKeyMans - WITNESS_V1_TAPROOT: Add key ownership verification using CanProvide on all ScriptPubKeyMans before preserving the taproot output - Signing: Replace legacy SignSignature with wallet's SignTransaction which handles both wallet types internally This enables PoS staking with descriptor wallets. (cherry picked from commit 286a856cbce4ed4e2b4c23d2204ab2b4ff135cf6)
Update SignBlock to support both legacy and descriptor wallets by iterating all ScriptPubKeyMans to find the signing key. For descriptor wallets, the coinstake output uses P2PK format but the wallet internally tracks P2PKH scripts. Construct a P2PKH script from the keyid to query the descriptor wallet's GetKey method. This enables PoS block signing with descriptor wallets. (cherry picked from commit feb5c41789c50bea54f48e3af0f08e8ebc07cdcd)
- Add DescriptorGetKey unit test to verify DescriptorScriptPubKeyMan can retrieve private keys for owned scripts and fails appropriately for unknown scripts - Add safety check in importaddress test utility to assert when called with a descriptor wallet, since legacy import is not supported (cherry picked from commit 1237f6a1717cecdcb1e2222a043858e8b322094f)
Add wallet_descriptor_staking.py to verify end-to-end PoS staking works with descriptor wallets: - Creates a descriptor wallet - Generates PoW blocks to fund the wallet - Generates PoS blocks using the descriptor wallet for staking - Verifies mature balance and block generation Uses ReddCoin's regtest COINBASE_MATURITY of 60 blocks. (cherry picked from commit e96ddbc5fe32b9620126f936fd4f2c004646ac77)
Verify that descriptor and legacy wallets can both stake PoS blocks and that blocks produced by each wallet type are accepted by the other. The test creates a 2-node network (descriptor wallet on node0, legacy wallet on node1), generates PoW/PoS blocks, transfers coins between nodes, and confirms cross-validation of staked blocks. Includes a UTXO exhaustion test: the descriptor wallet stakes 100+ PoS blocks from only 89 original coinbase UTXOs, verifying that P2PK coinstake outputs are correctly recognized and recycled by the descriptor wallet's IsMine/GetSigningProvider P2PK fallback. Key technical details: - Connects nodes before setting mocktime to avoid InactivityCheck disconnections (nTimeConnected uses real time, InactivityCheck uses mocktime) - Uses -peertimeout=999999999 to prevent P2P timeout disconnections during large mocktime advances - Splits balance into 40 additional UTXOs at separate descriptor indices to bridge the 61-block coinstake maturity gap - Sends 10x1,000,000 RDD to node1 at separate addresses to prevent coinstake combine from grouping them (cherry picked from commit 1b24914eb28e13d3f153dee01056a6bcb86a9fd8)
Add documentation for wallet_descriptor_staking.py and feature_descriptor_legacy_staking.py covering test structure, P2PK fallback mechanics, UTXO exhaustion validation, and cross-wallet interoperability. (cherry picked from commit bf9febef89beaa07b6634f876179ba6c0cf274a1)
(cherry picked from commit 6aa1f5861d4cc0faa025aa6cbb59c58f7fa1eee4)
The staking code selects wallet UTXOs for coinstake without checking whether SegWit is active. If a witness UTXO (P2WPKH, P2WSH, taproot) is selected before activation, the resulting block contains witness data and gets rejected with "unexpected witness data" during ContextualCheckBlock. Add a check at the start of the coin iteration loop in CreateCoinStake to skip witness output types when DeploymentActiveAfter returns false for DEPLOYMENT_SEGWIT. This prevents invalid block creation during the BIP9 STARTED/LOCKED_IN phases. (cherry picked from commit 2b85b7b8f7e05ac5d2b7597347ab0b394c6ff6d1)
CreateCoinStake can select a taproot (WITNESS_V1_TAPROOT) UTXO as a stake kernel and preserves the taproot scriptPubKey as the coinstake output, but SignBlock and CheckBlockSignature only handled P2PK coinstake outputs, so a taproot-staked block could never be signed or validated (SignBlock returned 'Failed to sign PoS block'). Add a BIP340 Schnorr path. DescriptorScriptPubKeyMan::SignBlockSchnorr resolves the taproot output key to its internal key and merkle root via the descriptor signing provider and signs the block hash with the taproot tweak applied, so the 64-byte signature verifies against the output key. SignBlock dispatches taproot coinstake outputs to it; CheckBlockSignature verifies them with XOnlyPubKey::VerifySchnorr. Purely additive: no taproot-coinstake block could exist before (none could be signed), so validation of existing blocks is unchanged. Only descriptor wallets hold taproot keys, and taproot descriptors are not enabled by default.
The existing descriptor staking tests stake only from legacy (P2PKH) addresses. Add wallet_descriptor_staking_witness.py covering the witness kernel paths in CreateCoinStake: Phase 1 (SegWit inactive): a P2WPKH UTXO must be skipped as a stake kernel so the coinstake never embeds witness data rejected as unexpected-witness, while the wallet keeps staking from legacy UTXOs. Phase 2 (SegWit + Taproot active): drive BIP9 activation, then stake from wallets funded only with P2WPKH or (imported tr() descriptor) taproot UTXOs, exercising the WITNESS_V0_KEYHASH and WITNESS_V1_TAPROOT kernel branches and the taproot block-signing path. Witness UTXOs are buried deeply before staking since freshly-confirmed coins are not yet stakeable; success is asserted by a consumed kernel UTXO.
reddink
force-pushed
the
pr/descriptor-staking-rebased
branch
from
July 16, 2026 04:04
2c4e87f to
814a0fc
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Enables descriptor wallets to participate in Proof-of-Stake. Previously only
legacy wallets could produce coinstakes and sign PoS blocks; this PR extends the
staking path (
CreateCoinStake,SignBlock,CheckBlockSignature) to work withdescriptor wallets and adds support for Taproot key-path and P2WPKH stake
kernels, with the safety gating and test coverage to match.
The change is purely additive and backward compatible: no coinstake output
type that this PR handles could be produced or accepted before, so validation of
all existing blocks is unchanged, and legacy-wallet staking behaves exactly as it
did. It remains interoperable with v4.22.9 followers.
What's included
Descriptor wallet staking (source)
CreateCoinStakenow iterates allScriptPubKeyMans and usesCanProvide/the wallet-appropriate
GetKeyto locate keys, coveringPUBKEYHASH,WITNESS_V0_KEYHASH,PUBKEY, andWITNESS_V1_TAPROOTkernels. Signing isrouted through the wallet's
SignTransaction, which handles both wallet types.SignBlocksupports descriptor wallets: the coinstake output is P2PK while thedescriptor wallet tracks P2PKH internally, so it reconstructs the P2PKH script
from the keyid to resolve the signing key (plus a P2WPKH key-lookup path).
Taproot key-path staking
WITNESS_V1_TAPROOTas a valid kernel type and preserves the taprootscriptPubKey in the coinstake output (key-path spend, no downgrade to a legacy
type, privacy preserved). Script-path outputs naturally fail to sign, which is
correct for autonomous PoS.
(
DescriptorScriptPubKeyMan::SignBlockSchnorr+XOnlyPubKey::VerifySchnorr)so taproot-staked blocks can actually be signed and validated. Only descriptor
wallets hold taproot keys, and taproot descriptors are off by default.
SegWit-activation safety
CreateCoinStakeskips witness UTXOs (P2WPKH/P2WSH/taproot) as stake kernelswhile
DEPLOYMENT_SEGWITis not yet active, preventing blocks with unexpectedwitness data from being created during BIP9 STARTED/LOCKED_IN.
Testing
wallet_descriptor_staking.py— end-to-end PoS staking with a descriptor wallet.feature_descriptor_legacy_staking.py— descriptor⇄legacy cross-validation, plusa UTXO-exhaustion case proving the descriptor wallet recognises and recycles P2PK
coinstake outputs (100+ PoS blocks from 89 coinbase UTXOs).
wallet_descriptor_staking_witness.py— P2WPKH-skip before SegWit, then P2WPKHand
tr()taproot kernel staking once SegWit/Taproot are active.scriptpubkeyman_tests.cpp—DescriptorGetKeyunit coverage, plus animportaddressguard that asserts on descriptor wallets.Documentation
docs/wallet_descriptor_staking.mdanddocs/feature_descriptor_legacy_staking.mdcover test structure, the P2PK fallback mechanics, UTXO-exhaustion validation, and
cross-wallet interoperability.
Compatibility
Additive only — existing block validation is unchanged and legacy staking is
unaffected. Compatible with v4.22.9 followers.
Commits (11)
feat: add Taproot key-path staking supportpos: Add descriptor wallet support for coinstake creationpos: Add descriptor wallet support for block signingtest: Add descriptor wallet tests and safety checkstest: Add functional test for descriptor wallet stakingtest: Add descriptor-legacy wallet staking interop testdocs: Add descriptor wallet staking test documentationfix: Support P2WPKH key lookup in SignBlock for descriptor walletspos: Skip witness UTXOs for staking before SegWit activationpos: implement taproot key-path block signing for descriptor stakingtest: cover P2WPKH and taproot kernel staking for descriptor walletsNotes
Rebased onto the current
develop.developalready registerswallet_descriptor_staking.pyandfeature_descriptor_legacy_staking.py(theylanded via the test-framework / test-adaptations merges), so the branch's original
"Register descriptor staking tests in test runner" commit became redundant and was
dropped during the rebase (12 → 11 commits). The only
test_runner.pychange vsdevelopnow is registering the newwallet_descriptor_staking_witness.py.