-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathfeature_versionbits_warning.py
More file actions
executable file
·168 lines (139 loc) · 7.27 KB
/
Copy pathfeature_versionbits_warning.py
File metadata and controls
executable file
·168 lines (139 loc) · 7.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
#!/usr/bin/env python3
# Copyright (c) 2016-2020 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test version bits warning system.
Generate chains with block versions that appear to be signalling unknown
soft-forks, and test that warning alerts are generated.
"""
import os
import re
from test_framework.address import key_to_p2pkh
from test_framework.blocktools import create_block, create_coinbase, sign_block, NORMAL_GBT_REQUEST_PARAMS
from test_framework.messages import msg_block
from test_framework.p2p import P2PInterface
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import advance_time_for_pos
VB_PERIOD = 144 # versionbits period length for regtest
VB_THRESHOLD = 108 # versionbits activation threshold for regtest
VB_TOP_BITS = 0x20000000
VB_UNKNOWN_BIT = 27 # Choose a bit unassigned to any deployment
VB_UNKNOWN_VERSION = VB_TOP_BITS | (1 << VB_UNKNOWN_BIT)
WARN_UNKNOWN_RULES_ACTIVE = "Unknown new rules activated (versionbit {})".format(VB_UNKNOWN_BIT)
VB_PATTERN = re.compile("Unknown new rules activated.*versionbit")
class VersionBitsWarningTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 1
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def setup_network(self):
self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt")
# Open and close to create zero-length file
with open(self.alert_filename, 'w', encoding='utf8'):
pass
self.extra_args = [["-alertnotify=echo %s >> \"" + self.alert_filename + "\"", "-whitelist=127.0.0.1"]]
self.setup_nodes()
def get_signing_key(self, node, block):
"""Extract the signing key for a PoS block from the coinstake transaction."""
coinstake = block.vtx[1]
coinstake_hex = coinstake.serialize().hex()
decoded_tx = node.decoderawtransaction(coinstake_hex)
signing_key = None
try:
script_pubkey = decoded_tx['vout'][1]['scriptPubKey']
coinstake_address = script_pubkey.get('address')
if not coinstake_address:
addresses = script_pubkey.get('addresses', [])
if addresses:
coinstake_address = addresses[0]
if not coinstake_address and script_pubkey.get('type') == 'pubkey':
asm = script_pubkey.get('asm', '')
parts = asm.split()
if len(parts) >= 1 and parts[0] != 'OP_CHECKSIG':
pubkey_hex = parts[0]
coinstake_address = key_to_p2pkh(pubkey_hex, main=False)
if coinstake_address:
signing_key = node.dumpprivkey(coinstake_address)
except Exception as e:
self.log.debug(f"Could not get signing key: {e}")
if not signing_key:
self.log.debug("No signing key found, using deterministic key")
signing_key = node.get_deterministic_priv_key().key
return signing_key
def send_blocks_with_version(self, peer, numblocks, version):
"""Send numblocks blocks to peer with version set"""
node = self.nodes[0]
height = node.getblockcount()
# PoW path for heights <= 89 (nLastPowHeight)
if height < 89:
tip = node.getbestblockhash()
block_time = node.getblockheader(tip)["time"] + 1
tip = int(tip, 16)
pow_blocks = min(numblocks, 89 - height)
for _ in range(pow_blocks):
block = create_block(tip, create_coinbase(height + 1), block_time)
block.nVersion = version
block.solve()
peer.send_message(msg_block(block))
block_time += 1
height += 1
tip = block.sha256
peer.sync_with_ping()
numblocks -= pow_blocks
# PoS path for heights >= 90
for _ in range(numblocks):
max_attempts = 10
for attempt in range(max_attempts):
try:
advance_time_for_pos(node, seconds=60)
tmpl = node.getblocktemplate(NORMAL_GBT_REQUEST_PARAMS)
block = create_block(tmpl=tmpl, version=version)
block.solve()
signing_key = self.get_signing_key(node, block)
sign_block(block, signing_key)
peer.send_message(msg_block(block))
peer.sync_with_ping()
break
except Exception as e:
if "no valid coinstake found" in str(e) and attempt < max_attempts - 1:
advance_time_for_pos(node, seconds=120)
continue
raise
def versionbits_in_alert_file(self):
"""Test that the versionbits warning has been written to the alert file."""
alert_text = open(self.alert_filename, 'r', encoding='utf8').read()
return VB_PATTERN.search(alert_text) is not None
def run_test(self):
node = self.nodes[0]
peer = node.add_p2p_connection(P2PInterface())
# Mine one period worth of blocks
node.generate(VB_PERIOD)
self.log.info("Check that there is no warning if previous VB_BLOCKS have <VB_THRESHOLD blocks with unknown versionbits version.")
# Build one period of blocks with < VB_THRESHOLD blocks signaling some unknown bit
self.send_blocks_with_version(peer, VB_THRESHOLD - 1, VB_UNKNOWN_VERSION)
node.generate(VB_PERIOD - VB_THRESHOLD + 1)
# Check that we're not getting any versionbit-related errors in get*info()
assert not VB_PATTERN.match(node.getmininginfo()["warnings"])
assert not VB_PATTERN.match(node.getnetworkinfo()["warnings"])
# Build one period of blocks with VB_THRESHOLD blocks signaling some unknown bit
self.send_blocks_with_version(peer, VB_THRESHOLD, VB_UNKNOWN_VERSION)
node.generate(VB_PERIOD - VB_THRESHOLD)
self.log.info("Check that there is a warning if previous VB_BLOCKS have >=VB_THRESHOLD blocks with unknown versionbits version.")
# Mine a period worth of expected blocks so the generic block-version warning
# is cleared. This will move the versionbit state to ACTIVE.
node.generate(VB_PERIOD)
# Stop-start the node. This is required because bitcoind will only warn once about unknown versions or unknown rules activating.
self.restart_node(0)
# Generating one block guarantees that we'll get out of IBD
node.generate(1)
self.wait_until(lambda: not node.getblockchaininfo()['initialblockdownload'])
# Generating one more block will be enough to generate an error.
node.generate(1)
# Check that get*info() shows the versionbits unknown rules warning
assert WARN_UNKNOWN_RULES_ACTIVE in node.getmininginfo()["warnings"]
assert WARN_UNKNOWN_RULES_ACTIVE in node.getnetworkinfo()["warnings"]
# Check that the alert file shows the versionbits unknown rules warning
self.wait_until(lambda: self.versionbits_in_alert_file())
if __name__ == '__main__':
VersionBitsWarningTest().main()