test: update and re-enable trim headers functional test
What changed, and why it matters
This commit re-enables a functional test for a feature called 'trim headers' and makes a small wallet change so that anyone-can-spend outputs (OP_TRUE) are recognized as wallet funds on custom Elements chains when a special configuration flag is set. The main purpose appears to be test maintenance, not fixing an active security vulnerability. The wallet change could affect how funds are classified, but only in a specific, non-default configuration.
Review the OP_TRUE ownership change carefully: confirm it is gated only by the non-default anyonecanspend_aremine parameter, verify it does not inadvertently mark arbitrary OP_TRUE outputs as spendable on mainnet or other production chains, and ensure the re-enabled test passes reliably in CI.
Security signals we found
Wallet ownership classification changed for OP_TRUE outputs under -anyonecanspendaremine
Functional test re-enabled after being disabled/moved to extended suite
Legacy wallet dependency removed from test infrastructure
Evidence from the diff
The commit updates test/functional/feature_trim_headers.py to work with descriptor wallets instead of legacy BDB wallets, moves it from extended tests back to the default test suite, and removes BDB-specific skips. It also changes src/wallet/wallet.cpp in CWallet::IsMine so that on chains where Params().anyonecanspend_aremine is true, an empty script that pushes OP_TRUE is treated as ISMINE_SPENDABLE. This is intended to preserve behavior for custom Elements regtest-style chains that rely on -anyonecanspendaremine. The block signing logic in the test is rewritten to sign the block hash directly with an ECKey and use combineblocksigs, avoiding the legacy-wallet-only signblock RPC.
Changed components
src/wallet/wallet.cpptest/functional/feature_trim_headers.pytest/functional/test_runner.pyInspect captured patch +29 / −19
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index c9371f5..da8cd09 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1654,6 +1654,13 @@ isminetype CWallet::IsMine(const CScript& script) const
return spkm->IsMine(script);
}
+ // ELEMENTS: OP_TRUE outputs aren't derived from any descriptor, so they never
+ // land in m_cached_spks above. Custom chains (e.g. elementsregtest) rely on
+ // -anyonecanspendaremine to treat them as wallet funds regardless of wallet type.
+ if (Params().anyonecanspend_aremine && script == CScript() << OP_TRUE) {
+ return ISMINE_SPENDABLE;
+ }
+
return ISMINE_NO;
}
diff --git a/test/functional/feature_trim_headers.py b/test/functional/feature_trim_headers.py
index 386f59c..6c3dc87 100755
--- a/test/functional/feature_trim_headers.py
+++ b/test/functional/feature_trim_headers.py
@@ -5,25 +5,20 @@ import codecs
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal
from test_framework import (
- address,
key,
)
from test_framework.messages import (
CBlock,
from_hex,
+ ser_uint256,
)
from test_framework.script import (
OP_NOP,
OP_RETURN,
- CScript
+ CScript,
+ SIGHASH_ALL,
)
-# Generate wallet import format from private key.
-def wif(pk):
- # Base58Check version for regtest WIF keys is 0xef = 239
- pk_compressed = pk + bytes([0x1])
- return address.byte_to_base58(pk_compressed, 239)
-
# The signblockscript is a Bitcoin Script k-of-n multisig script.
def make_signblockscript(num_nodes, required_signers, keys):
assert num_nodes >= required_signers
@@ -39,7 +34,6 @@ def make_signblockscript(num_nodes, required_signers, keys):
class TrimHeadersTest(BitcoinTestFramework):
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
- self.skip_if_no_bdb()
def add_options(self, parser):
self.add_wallet_options(parser)
@@ -47,13 +41,10 @@ class TrimHeadersTest(BitcoinTestFramework):
# Dynamically generate N keys to be used for block signing.
def init_keys(self, num_keys):
self.keys = []
- self.wifs = []
for i in range(num_keys):
k = key.ECKey()
k.generate()
- w = wif(k.get_bytes())
self.keys.append(k)
- self.wifs.append(w)
def set_test_params(self):
self.num_nodes = 3
@@ -92,6 +83,21 @@ class TrimHeadersTest(BitcoinTestFramework):
else:
assert_equal(self.nodes[0].getblockcount(), expected_height)
+ # The signblock RPC only works on legacy (BDB) wallets.
+ # Sign the block header hash directly with the key instead,
+ # then use combineblocksigs (a non-wallet RPC).
+ def sign_block(self, key, block):
+ block.rehash()
+ msg = ser_uint256(block.sha256)
+ sig = key.sign_ecdsa(msg)
+ if not block.m_dynafed_params.is_null():
+ sig += bytes([SIGHASH_ALL])
+
+ return [{
+ "pubkey": key.get_pubkey().get_bytes().hex(),
+ "sig": sig.hex(),
+ }]
+
def mine_block(self, make_transactions):
# alternate mining between the signing nodes
mineridx = self.nodes[0].getblockcount() % self.required_signers # assuming in sync
@@ -141,7 +147,7 @@ class TrimHeadersTest(BitcoinTestFramework):
sigs = []
for i in range(self.num_keys):
result = miner.combineblocksigs(block, sigs, self.witnessScript)
- sigs = sigs + self.nodes[i].signblock(block, self.witnessScript)
+ sigs = sigs + self.sign_block(self.keys[i], block_struct)
assert_equal(result["complete"], i >= self.required_signers)
# submitting should have no effect pre-threshhold
if i < self.required_signers:
@@ -171,7 +177,7 @@ class TrimHeadersTest(BitcoinTestFramework):
block.solve()
h = block.serialize().hex()
- sigs = node.signblock(h, self.witnessScript)
+ sigs = self.sign_block(self.keys[0], block)
result = node.combineblocksigs(h, sigs, self.witnessScript)
assert_equal(result["complete"], True)
@@ -180,9 +186,6 @@ class TrimHeadersTest(BitcoinTestFramework):
def run_test(self):
- for i in range(self.num_keys):
- self.nodes[i].importprivkey(self.wifs[i])
-
expected_height = 0
self.check_height(expected_height, all=True)
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 757d045..61940c3 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -87,7 +87,6 @@ EXTENDED_SCRIPTS = [
# 'feature_dbcrash.py', ELEMENTS: long running test and uses excessive disk space on GHA
# 'feature_fee_estimation.py', ELEMENTS: this is broken on v23
'feature_index_prune.py',
- 'feature_trim_headers.py',
'wallet_pruning.py --legacy-wallet',
]
@@ -95,6 +94,7 @@ BASE_SCRIPTS = [
# Scripts that are run by default.
# vv First elements tests vv
'example_elements_code_tutorial.py',
+ 'feature_trim_headers.py',
'feature_fedpeg.py --legacy-wallet',
'feature_fedpeg.py --pre_transition --legacy-wallet',
'feature_fedpeg.py --post_transition --legacy-wallet',
@@ -365,7 +365,7 @@ BASE_SCRIPTS = [
#'wallet_upgradewallet.py --legacy-wallet',
'wallet_crosschain.py',
'mining_basic.py',
- # ELEMENTS: PoW test set-up disabled.
+ # ELEMENTS: PoW test set-up disabled.
# 'mining_mainnet.py',
'feature_signet.py',
'p2p_mutated_blocks.py',
Why this scored 28/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.