Skip to content

Commit 5f30cd2

Browse files
committed
Merge #1565: Guard against trimmed chain_start in TryLowWorkHeadersSync
e151cad test: update and re-enable trim headers functional test (Byron Hambly) 3824d7d add guard to TryLowWorkHeadersSync to prevent assert_untrimmed (Tom Trevethan) Pull request description: When -trim_headers is enabled, TryLowWorkHeadersSync can receive a chain_start_header pointing to a trimmed block index entry. The `HeadersSyncState` constructor immediately calls `GetBlockHeader()` on it via the m_last_header_received initialiser, which hits `assert_untrimmed()` and aborts the node. Conditional untrim_to guard added inside `TryLowWorkHeadersSync`, before `HeadersSyncState`. For nodes running -trim_headers, in practice the crash path is not reachable on Elements because `already_validated_work` is always true on a federated chain (nChainWork == nMinimumChainWork == 0), so `TryLowWorkHeadersSync` is not entered. But this ensures consistency with guarded serving paths. `TryLowWorkHeadersSync` and `HeadersSyncState` were introduced in Bitcoin v24 (bitcoin/bitcoin#25717) and so this is not applicable to Elements v23. ACKs for top commit: delta1: ACK e151cad; tested locally Tree-SHA512: 824344079b2136f7e2005968eafa3ad4385ed13ee3b3f3f79b3f878ed7e7dd88c68c85c1d6dd6e989f8484561984e6350fa81a6a19c96550787d0e615b76e87c
2 parents 7a5b346 + e151cad commit 5f30cd2

4 files changed

Lines changed: 39 additions & 20 deletions

File tree

src/net_processing.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2634,6 +2634,15 @@ bool PeerManagerImpl::TryLowWorkHeadersSync(Peer& peer, CNode& pfrom, const CBlo
26342634
// otherwise they don't have more headers after this so no point in
26352635
// trying to sync their too-little-work chain.
26362636
if (headers.size() == m_opts.max_headers_result) {
2637+
// chain_start_header may refer to a block deep in the chain that
2638+
// has been trimmed from memory by -trim_headers. The HeadersSyncState
2639+
// constructor calls GetBlockHeader() on it via m_last_header_received,
2640+
// which asserts untrimmed. Reload from disk first if needed.
2641+
CBlockIndex tmpBlockIndexFull;
2642+
const CBlockIndex* chain_start_untrimmed = chain_start_header->trimmed()
2643+
? chain_start_header->untrim_to(&tmpBlockIndexFull)
2644+
: chain_start_header;
2645+
26372646
// Note: we could advance to the last header in this set that is
26382647
// known to us, rather than starting at the first header (which we
26392648
// may already have); however this is unlikely to matter much since
@@ -2645,7 +2654,7 @@ bool PeerManagerImpl::TryLowWorkHeadersSync(Peer& peer, CNode& pfrom, const CBlo
26452654
// advancing to the first unknown header would be a small effect.
26462655
LOCK(peer.m_headers_sync_mutex);
26472656
peer.m_headers_sync.reset(new HeadersSyncState(peer.m_id, m_chainparams.GetConsensus(),
2648-
chain_start_header, minimum_chain_work));
2657+
chain_start_untrimmed, minimum_chain_work));
26492658

26502659
// Now a HeadersSyncState object for tracking this synchronization
26512660
// is created, process the headers using it as normal. Failures are

src/wallet/wallet.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,6 +1654,13 @@ isminetype CWallet::IsMine(const CScript& script) const
16541654
return spkm->IsMine(script);
16551655
}
16561656

1657+
// ELEMENTS: OP_TRUE outputs aren't derived from any descriptor, so they never
1658+
// land in m_cached_spks above. Custom chains (e.g. elementsregtest) rely on
1659+
// -anyonecanspendaremine to treat them as wallet funds regardless of wallet type.
1660+
if (Params().anyonecanspend_aremine && script == CScript() << OP_TRUE) {
1661+
return ISMINE_SPENDABLE;
1662+
}
1663+
16571664
return ISMINE_NO;
16581665
}
16591666

test/functional/feature_trim_headers.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,20 @@
55
from test_framework.test_framework import BitcoinTestFramework
66
from test_framework.util import assert_equal
77
from test_framework import (
8-
address,
98
key,
109
)
1110
from test_framework.messages import (
1211
CBlock,
1312
from_hex,
13+
ser_uint256,
1414
)
1515
from test_framework.script import (
1616
OP_NOP,
1717
OP_RETURN,
18-
CScript
18+
CScript,
19+
SIGHASH_ALL,
1920
)
2021

21-
# Generate wallet import format from private key.
22-
def wif(pk):
23-
# Base58Check version for regtest WIF keys is 0xef = 239
24-
pk_compressed = pk + bytes([0x1])
25-
return address.byte_to_base58(pk_compressed, 239)
26-
2722
# The signblockscript is a Bitcoin Script k-of-n multisig script.
2823
def make_signblockscript(num_nodes, required_signers, keys):
2924
assert num_nodes >= required_signers
@@ -39,21 +34,17 @@ def make_signblockscript(num_nodes, required_signers, keys):
3934
class TrimHeadersTest(BitcoinTestFramework):
4035
def skip_test_if_missing_module(self):
4136
self.skip_if_no_wallet()
42-
self.skip_if_no_bdb()
4337

4438
def add_options(self, parser):
4539
self.add_wallet_options(parser)
4640

4741
# Dynamically generate N keys to be used for block signing.
4842
def init_keys(self, num_keys):
4943
self.keys = []
50-
self.wifs = []
5144
for i in range(num_keys):
5245
k = key.ECKey()
5346
k.generate()
54-
w = wif(k.get_bytes())
5547
self.keys.append(k)
56-
self.wifs.append(w)
5748

5849
def set_test_params(self):
5950
self.num_nodes = 3
@@ -92,6 +83,21 @@ def check_height(self, expected_height, all=False, verbose=True):
9283
else:
9384
assert_equal(self.nodes[0].getblockcount(), expected_height)
9485

86+
# The signblock RPC only works on legacy (BDB) wallets.
87+
# Sign the block header hash directly with the key instead,
88+
# then use combineblocksigs (a non-wallet RPC).
89+
def sign_block(self, key, block):
90+
block.rehash()
91+
msg = ser_uint256(block.sha256)
92+
sig = key.sign_ecdsa(msg)
93+
if not block.m_dynafed_params.is_null():
94+
sig += bytes([SIGHASH_ALL])
95+
96+
return [{
97+
"pubkey": key.get_pubkey().get_bytes().hex(),
98+
"sig": sig.hex(),
99+
}]
100+
95101
def mine_block(self, make_transactions):
96102
# alternate mining between the signing nodes
97103
mineridx = self.nodes[0].getblockcount() % self.required_signers # assuming in sync
@@ -141,7 +147,7 @@ def mine_block(self, make_transactions):
141147
sigs = []
142148
for i in range(self.num_keys):
143149
result = miner.combineblocksigs(block, sigs, self.witnessScript)
144-
sigs = sigs + self.nodes[i].signblock(block, self.witnessScript)
150+
sigs = sigs + self.sign_block(self.keys[i], block_struct)
145151
assert_equal(result["complete"], i >= self.required_signers)
146152
# submitting should have no effect pre-threshhold
147153
if i < self.required_signers:
@@ -171,7 +177,7 @@ def mine_large_blocks(self, n):
171177
block.solve()
172178
h = block.serialize().hex()
173179

174-
sigs = node.signblock(h, self.witnessScript)
180+
sigs = self.sign_block(self.keys[0], block)
175181

176182
result = node.combineblocksigs(h, sigs, self.witnessScript)
177183
assert_equal(result["complete"], True)
@@ -180,9 +186,6 @@ def mine_large_blocks(self, n):
180186

181187

182188
def run_test(self):
183-
for i in range(self.num_keys):
184-
self.nodes[i].importprivkey(self.wifs[i])
185-
186189
expected_height = 0
187190
self.check_height(expected_height, all=True)
188191

test/functional/test_runner.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,14 @@
8787
# 'feature_dbcrash.py', ELEMENTS: long running test and uses excessive disk space on GHA
8888
# 'feature_fee_estimation.py', ELEMENTS: this is broken on v23
8989
'feature_index_prune.py',
90-
'feature_trim_headers.py',
9190
'wallet_pruning.py --legacy-wallet',
9291
]
9392

9493
BASE_SCRIPTS = [
9594
# Scripts that are run by default.
9695
# vv First elements tests vv
9796
'example_elements_code_tutorial.py',
97+
'feature_trim_headers.py',
9898
'feature_fedpeg.py --legacy-wallet',
9999
'feature_fedpeg.py --pre_transition --legacy-wallet',
100100
'feature_fedpeg.py --post_transition --legacy-wallet',
@@ -365,7 +365,7 @@
365365
#'wallet_upgradewallet.py --legacy-wallet',
366366
'wallet_crosschain.py',
367367
'mining_basic.py',
368-
# ELEMENTS: PoW test set-up disabled.
368+
# ELEMENTS: PoW test set-up disabled.
369369
# 'mining_mainnet.py',
370370
'feature_signet.py',
371371
'p2p_mutated_blocks.py',

0 commit comments

Comments
 (0)