Changeset View
Changeset View
Standalone View
Standalone View
qa/rpc-tests/p2p-versionbits-warning.py
Show All 11 Lines | |||||
''' | ''' | ||||
Test version bits' warning system. | Test version bits' warning system. | ||||
Generate chains with block versions that appear to be signalling unknown | Generate chains with block versions that appear to be signalling unknown | ||||
soft-forks, and test that warning alerts are generated. | soft-forks, and test that warning alerts are generated. | ||||
''' | ''' | ||||
VB_PERIOD = 144 # versionbits period length for regtest | VB_PERIOD = 144 # versionbits period length for regtest | ||||
VB_THRESHOLD = 108 # versionbits activation threshold for regtest | VB_THRESHOLD = 108 # versionbits activation threshold for regtest | ||||
VB_TOP_BITS = 0x20000000 | VB_TOP_BITS = 0x20000000 | ||||
VB_UNKNOWN_BIT = 27 # Choose a bit unassigned to any deployment | VB_UNKNOWN_BIT = 27 # Choose a bit unassigned to any deployment | ||||
WARN_UNKNOWN_RULES_MINED = "Unknown block versions being mined! It's possible unknown rules are in effect" | WARN_UNKNOWN_RULES_MINED = "Unknown block versions being mined! It's possible unknown rules are in effect" | ||||
WARN_UNKNOWN_RULES_ACTIVE = "unknown new rules activated (versionbit {})".format(VB_UNKNOWN_BIT) | WARN_UNKNOWN_RULES_ACTIVE = "unknown new rules activated (versionbit {})".format( | ||||
VB_UNKNOWN_BIT) | |||||
VB_PATTERN = re.compile("^Warning.*versionbit") | VB_PATTERN = re.compile("^Warning.*versionbit") | ||||
# TestNode: bare-bones "peer". Used mostly as a conduit for a test to sending | # TestNode: bare-bones "peer". Used mostly as a conduit for a test to sending | ||||
# p2p messages to a node, generating the messages in the main testing logic. | # p2p messages to a node, generating the messages in the main testing logic. | ||||
class TestNode(NodeConnCB): | class TestNode(NodeConnCB): | ||||
def __init__(self): | def __init__(self): | ||||
NodeConnCB.__init__(self) | NodeConnCB.__init__(self) | ||||
self.connection = None | self.connection = None | ||||
self.ping_counter = 1 | self.ping_counter = 1 | ||||
self.last_pong = msg_pong() | self.last_pong = msg_pong() | ||||
def add_connection(self, conn): | def add_connection(self, conn): | ||||
self.connection = conn | self.connection = conn | ||||
Show All 19 Lines | def sync_with_ping(self, timeout=30): | ||||
with mininode_lock: | with mininode_lock: | ||||
if self.last_pong.nonce == self.ping_counter: | if self.last_pong.nonce == self.ping_counter: | ||||
received_pong = True | received_pong = True | ||||
self.ping_counter += 1 | self.ping_counter += 1 | ||||
return received_pong | return received_pong | ||||
class VersionBitsWarningTest(BitcoinTestFramework): | class VersionBitsWarningTest(BitcoinTestFramework): | ||||
def __init__(self): | def __init__(self): | ||||
super().__init__() | super().__init__() | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.num_nodes = 1 | self.num_nodes = 1 | ||||
def setup_network(self): | def setup_network(self): | ||||
self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt") | self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt") | ||||
# Open and close to create zero-length file | # Open and close to create zero-length file | ||||
with open(self.alert_filename, 'w', encoding='utf8') as _: | with open(self.alert_filename, 'w', encoding='utf8') as _: | ||||
pass | pass | ||||
self.extra_args = [["-debug", "-logtimemicros=1", "-alertnotify=echo %s >> \"" + self.alert_filename + "\""]] | self.extra_args = [ | ||||
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.extra_args) | ["-debug", "-logtimemicros=1", "-alertnotify=echo %s >> \"" + self.alert_filename + "\""]] | ||||
self.nodes = start_nodes( | |||||
self.num_nodes, self.options.tmpdir, self.extra_args) | |||||
# Send numblocks blocks via peer with nVersionToUse set. | # Send numblocks blocks via peer with nVersionToUse set. | ||||
def send_blocks_with_version(self, peer, numblocks, nVersionToUse): | def send_blocks_with_version(self, peer, numblocks, nVersionToUse): | ||||
tip = self.nodes[0].getbestblockhash() | tip = self.nodes[0].getbestblockhash() | ||||
height = self.nodes[0].getblockcount() | height = self.nodes[0].getblockcount() | ||||
block_time = self.nodes[0].getblockheader(tip)["time"]+1 | block_time = self.nodes[0].getblockheader(tip)["time"] + 1 | ||||
tip = int(tip, 16) | tip = int(tip, 16) | ||||
for _ in range(numblocks): | for _ in range(numblocks): | ||||
block = create_block(tip, create_coinbase(height+1), block_time) | block = create_block(tip, create_coinbase(height + 1), block_time) | ||||
block.nVersion = nVersionToUse | block.nVersion = nVersionToUse | ||||
block.solve() | block.solve() | ||||
peer.send_message(msg_block(block)) | peer.send_message(msg_block(block)) | ||||
block_time += 1 | block_time += 1 | ||||
height += 1 | height += 1 | ||||
tip = block.sha256 | tip = block.sha256 | ||||
peer.sync_with_ping() | peer.sync_with_ping() | ||||
def test_versionbits_in_alert_file(self): | def test_versionbits_in_alert_file(self): | ||||
with open(self.alert_filename, 'r', encoding='utf8') as f: | with open(self.alert_filename, 'r', encoding='utf8') as f: | ||||
alert_text = f.read() | alert_text = f.read() | ||||
assert(VB_PATTERN.match(alert_text)) | assert(VB_PATTERN.match(alert_text)) | ||||
def run_test(self): | def run_test(self): | ||||
# Setup the p2p connection and start up the network thread. | # Setup the p2p connection and start up the network thread. | ||||
test_node = TestNode() | test_node = TestNode() | ||||
connections = [] | connections = [] | ||||
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node)) | connections.append( | ||||
NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node)) | |||||
test_node.add_connection(connections[0]) | test_node.add_connection(connections[0]) | ||||
NetworkThread().start() # Start up network handling in another thread | NetworkThread().start() # Start up network handling in another thread | ||||
# Test logic begins here | # Test logic begins here | ||||
test_node.wait_for_verack() | test_node.wait_for_verack() | ||||
# 1. Have the node mine one period worth of blocks | # 1. Have the node mine one period worth of blocks | ||||
self.nodes[0].generate(VB_PERIOD) | self.nodes[0].generate(VB_PERIOD) | ||||
# 2. Now build one period of blocks on the tip, with < VB_THRESHOLD | # 2. Now build one period of blocks on the tip, with < VB_THRESHOLD | ||||
# blocks signaling some unknown bit. | # blocks signaling some unknown bit. | ||||
nVersion = VB_TOP_BITS | (1<<VB_UNKNOWN_BIT) | nVersion = VB_TOP_BITS | (1 << VB_UNKNOWN_BIT) | ||||
self.send_blocks_with_version(test_node, VB_THRESHOLD-1, nVersion) | self.send_blocks_with_version(test_node, VB_THRESHOLD - 1, nVersion) | ||||
# Fill rest of period with regular version blocks | # Fill rest of period with regular version blocks | ||||
self.nodes[0].generate(VB_PERIOD - VB_THRESHOLD + 1) | self.nodes[0].generate(VB_PERIOD - VB_THRESHOLD + 1) | ||||
# Check that we're not getting any versionbit-related errors in | # Check that we're not getting any versionbit-related errors in | ||||
# get*info() | # get*info() | ||||
assert(not VB_PATTERN.match(self.nodes[0].getinfo()["errors"])) | assert(not VB_PATTERN.match(self.nodes[0].getinfo()["errors"])) | ||||
assert(not VB_PATTERN.match(self.nodes[0].getmininginfo()["errors"])) | assert(not VB_PATTERN.match(self.nodes[0].getmininginfo()["errors"])) | ||||
assert(not VB_PATTERN.match(self.nodes[0].getnetworkinfo()["warnings"])) | assert(not VB_PATTERN.match( | ||||
self.nodes[0].getnetworkinfo()["warnings"])) | |||||
# 3. Now build one period of blocks with >= VB_THRESHOLD blocks signaling | # 3. Now build one period of blocks with >= VB_THRESHOLD blocks signaling | ||||
# some unknown bit | # some unknown bit | ||||
self.send_blocks_with_version(test_node, VB_THRESHOLD, nVersion) | self.send_blocks_with_version(test_node, VB_THRESHOLD, nVersion) | ||||
self.nodes[0].generate(VB_PERIOD - VB_THRESHOLD) | self.nodes[0].generate(VB_PERIOD - VB_THRESHOLD) | ||||
# Might not get a versionbits-related alert yet, as we should | # Might not get a versionbits-related alert yet, as we should | ||||
# have gotten a different alert due to more than 51/100 blocks | # have gotten a different alert due to more than 51/100 blocks | ||||
# being of unexpected version. | # being of unexpected version. | ||||
# Check that get*info() shows some kind of error. | # Check that get*info() shows some kind of error. | ||||
assert(WARN_UNKNOWN_RULES_MINED in self.nodes[0].getinfo()["errors"]) | assert(WARN_UNKNOWN_RULES_MINED in self.nodes[0].getinfo()["errors"]) | ||||
assert(WARN_UNKNOWN_RULES_MINED in self.nodes[0].getmininginfo()["errors"]) | assert(WARN_UNKNOWN_RULES_MINED in self.nodes[ | ||||
assert(WARN_UNKNOWN_RULES_MINED in self.nodes[0].getnetworkinfo()["warnings"]) | 0].getmininginfo()["errors"]) | ||||
assert(WARN_UNKNOWN_RULES_MINED in self.nodes[ | |||||
0].getnetworkinfo()["warnings"]) | |||||
# Mine a period worth of expected blocks so the generic block-version warning | # Mine a period worth of expected blocks so the generic block-version warning | ||||
# is cleared, and restart the node. This should move the versionbit state | # is cleared, and restart the node. This should move the versionbit state | ||||
# to ACTIVE. | # to ACTIVE. | ||||
self.nodes[0].generate(VB_PERIOD) | self.nodes[0].generate(VB_PERIOD) | ||||
stop_nodes(self.nodes) | stop_nodes(self.nodes) | ||||
# Empty out the alert file | # Empty out the alert file | ||||
with open(self.alert_filename, 'w', encoding='utf8') as _: | with open(self.alert_filename, 'w', encoding='utf8') as _: | ||||
pass | pass | ||||
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.extra_args) | self.nodes = start_nodes( | ||||
self.num_nodes, self.options.tmpdir, self.extra_args) | |||||
# Connecting one block should be enough to generate an error. | # Connecting one block should be enough to generate an error. | ||||
self.nodes[0].generate(1) | self.nodes[0].generate(1) | ||||
assert(WARN_UNKNOWN_RULES_ACTIVE in self.nodes[0].getinfo()["errors"]) | assert(WARN_UNKNOWN_RULES_ACTIVE in self.nodes[0].getinfo()["errors"]) | ||||
assert(WARN_UNKNOWN_RULES_ACTIVE in self.nodes[0].getmininginfo()["errors"]) | assert(WARN_UNKNOWN_RULES_ACTIVE in self.nodes[ | ||||
assert(WARN_UNKNOWN_RULES_ACTIVE in self.nodes[0].getnetworkinfo()["warnings"]) | 0].getmininginfo()["errors"]) | ||||
assert(WARN_UNKNOWN_RULES_ACTIVE in self.nodes[ | |||||
0].getnetworkinfo()["warnings"]) | |||||
stop_nodes(self.nodes) | stop_nodes(self.nodes) | ||||
self.test_versionbits_in_alert_file() | self.test_versionbits_in_alert_file() | ||||
# Test framework expects the node to still be running... | # Test framework expects the node to still be running... | ||||
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir, self.extra_args) | self.nodes = start_nodes( | ||||
self.num_nodes, self.options.tmpdir, self.extra_args) | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
VersionBitsWarningTest().main() | VersionBitsWarningTest().main() |