Changeset View
Changeset View
Standalone View
Standalone View
qa/rpc-tests/test_framework/comptool.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2015-2016 The Bitcoin Core developers | # Copyright (c) 2015-2016 The Bitcoin Core developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
from .mininode import * | from .mininode import * | ||||
from .blockstore import BlockStore, TxStore | from .blockstore import BlockStore, TxStore | ||||
from .util import p2p_port | from .util import p2p_port | ||||
import logging | |||||
logger = logging.getLogger("TestFramework.comptool") | |||||
''' | ''' | ||||
This is a tool for comparing two or more bitcoinds to each other | This is a tool for comparing two or more bitcoinds to each other | ||||
using a script provided. | using a script provided. | ||||
To use, create a class that implements get_tests(), and pass it in | To use, create a class that implements get_tests(), and pass it in | ||||
as the test generator to TestManager. get_tests() should be a python | as the test generator to TestManager. get_tests() should be a python | ||||
generator that returns TestInstance objects. See below for definition. | generator that returns TestInstance objects. See below for definition. | ||||
''' | ''' | ||||
▲ Show 20 Lines • Show All 208 Lines • ▼ Show 20 Lines | def sync_blocks(self, blockhash, num_blocks): | ||||
return all( | return all( | ||||
blockhash in node.block_request_map and node.block_request_map[ | blockhash in node.block_request_map and node.block_request_map[ | ||||
blockhash] | blockhash] | ||||
for node in self.test_nodes | for node in self.test_nodes | ||||
) | ) | ||||
# --> error if not requested | # --> error if not requested | ||||
if not wait_until(blocks_requested, attempts=20 * num_blocks): | if not wait_until(blocks_requested, attempts=20 * num_blocks): | ||||
# print [ c.cb.block_request_map for c in self.connections ] | |||||
raise AssertionError("Not all nodes requested block") | raise AssertionError("Not all nodes requested block") | ||||
# Send getheaders message | # Send getheaders message | ||||
[c.cb.send_getheaders() for c in self.connections] | [c.cb.send_getheaders() for c in self.connections] | ||||
# Send ping and wait for response -- synchronization hack | # Send ping and wait for response -- synchronization hack | ||||
[c.cb.send_ping(self.ping_counter) for c in self.connections] | [c.cb.send_ping(self.ping_counter) for c in self.connections] | ||||
self.wait_for_pings(self.ping_counter) | self.wait_for_pings(self.ping_counter) | ||||
self.ping_counter += 1 | self.ping_counter += 1 | ||||
# Analogous to sync_block (see above) | # Analogous to sync_block (see above) | ||||
def sync_transaction(self, txhash, num_events): | def sync_transaction(self, txhash, num_events): | ||||
# Wait for nodes to request transaction (50ms sleep * 20 tries * | # Wait for nodes to request transaction (50ms sleep * 20 tries * | ||||
# num_events) | # num_events) | ||||
def transaction_requested(): | def transaction_requested(): | ||||
return all( | return all( | ||||
txhash in node.tx_request_map and node.tx_request_map[txhash] | txhash in node.tx_request_map and node.tx_request_map[txhash] | ||||
for node in self.test_nodes | for node in self.test_nodes | ||||
) | ) | ||||
# --> error if not requested | # --> error if not requested | ||||
if not wait_until(transaction_requested, attempts=20 * num_events): | if not wait_until(transaction_requested, attempts=20 * num_events): | ||||
# print [ c.cb.tx_request_map for c in self.connections ] | |||||
raise AssertionError("Not all nodes requested transaction") | raise AssertionError("Not all nodes requested transaction") | ||||
# Get the mempool | # Get the mempool | ||||
[c.cb.send_mempool() for c in self.connections] | [c.cb.send_mempool() for c in self.connections] | ||||
# Send ping and wait for response -- synchronization hack | # Send ping and wait for response -- synchronization hack | ||||
[c.cb.send_ping(self.ping_counter) for c in self.connections] | [c.cb.send_ping(self.ping_counter) for c in self.connections] | ||||
self.wait_for_pings(self.ping_counter) | self.wait_for_pings(self.ping_counter) | ||||
Show All 10 Lines | def check_results(self, blockhash, outcome): | ||||
for c in self.connections: | for c in self.connections: | ||||
if outcome is None: | if outcome is None: | ||||
if c.cb.bestblockhash != self.connections[0].cb.bestblockhash: | if c.cb.bestblockhash != self.connections[0].cb.bestblockhash: | ||||
return False | return False | ||||
elif isinstance(outcome, RejectResult): # Check that block was rejected w/ code | elif isinstance(outcome, RejectResult): # Check that block was rejected w/ code | ||||
if c.cb.bestblockhash == blockhash: | if c.cb.bestblockhash == blockhash: | ||||
return False | return False | ||||
if blockhash not in c.cb.block_reject_map: | if blockhash not in c.cb.block_reject_map: | ||||
print('Block not in reject map: %064x' % (blockhash)) | logger.error( | ||||
'Block not in reject map: %064x' % (blockhash)) | |||||
return False | return False | ||||
if not outcome.match(c.cb.block_reject_map[blockhash]): | if not outcome.match(c.cb.block_reject_map[blockhash]): | ||||
print('Block rejected with %s instead of expected %s: %064x' % | logger.error('Block rejected with %s instead of expected %s: %064x' % | ||||
(c.cb.block_reject_map[blockhash], outcome, blockhash)) | (c.cb.block_reject_map[blockhash], outcome, blockhash)) | ||||
return False | return False | ||||
elif ((c.cb.bestblockhash == blockhash) != outcome): | elif ((c.cb.bestblockhash == blockhash) != outcome): | ||||
# print c.cb.bestblockhash, blockhash, outcome | |||||
return False | return False | ||||
return True | return True | ||||
# Either check that the mempools all agree with each other, or that | # Either check that the mempools all agree with each other, or that | ||||
# txhash's presence in the mempool matches the outcome specified. | # txhash's presence in the mempool matches the outcome specified. | ||||
# This is somewhat of a strange comparison, in that we're either comparing | # This is somewhat of a strange comparison, in that we're either comparing | ||||
# a particular tx to an outcome, or the entire mempools altogether; | # a particular tx to an outcome, or the entire mempools altogether; | ||||
# perhaps it would be useful to add the ability to check explicitly that | # perhaps it would be useful to add the ability to check explicitly that | ||||
# a particular tx's existence in the mempool is the same across all nodes. | # a particular tx's existence in the mempool is the same across all nodes. | ||||
def check_mempool(self, txhash, outcome): | def check_mempool(self, txhash, outcome): | ||||
with mininode_lock: | with mininode_lock: | ||||
for c in self.connections: | for c in self.connections: | ||||
if outcome is None: | if outcome is None: | ||||
# Make sure the mempools agree with each other | # Make sure the mempools agree with each other | ||||
if c.cb.lastInv != self.connections[0].cb.lastInv: | if c.cb.lastInv != self.connections[0].cb.lastInv: | ||||
# print c.rpc.getrawmempool() | |||||
return False | return False | ||||
elif isinstance(outcome, RejectResult): # Check that tx was rejected w/ code | elif isinstance(outcome, RejectResult): # Check that tx was rejected w/ code | ||||
if txhash in c.cb.lastInv: | if txhash in c.cb.lastInv: | ||||
return False | return False | ||||
if txhash not in c.cb.tx_reject_map: | if txhash not in c.cb.tx_reject_map: | ||||
print('Tx not in reject map: %064x' % (txhash)) | logger.error('Tx not in reject map: %064x' % (txhash)) | ||||
return False | return False | ||||
if not outcome.match(c.cb.tx_reject_map[txhash]): | if not outcome.match(c.cb.tx_reject_map[txhash]): | ||||
print('Tx rejected with %s instead of expected %s: %064x' % | logger.error('Tx rejected with %s instead of expected %s: %064x' % | ||||
(c.cb.tx_reject_map[txhash], outcome, txhash)) | (c.cb.tx_reject_map[txhash], outcome, txhash)) | ||||
return False | return False | ||||
elif ((txhash in c.cb.lastInv) != outcome): | elif ((txhash in c.cb.lastInv) != outcome): | ||||
# print c.rpc.getrawmempool(), c.cb.lastInv | |||||
return False | return False | ||||
return True | return True | ||||
def run(self): | def run(self): | ||||
# Wait until verack is received | # Wait until verack is received | ||||
self.wait_for_verack() | self.wait_for_verack() | ||||
test_number = 1 | test_number = 1 | ||||
▲ Show 20 Lines • Show All 106 Lines • ▼ Show 20 Lines | def run(self): | ||||
for c in self.connections] | for c in self.connections] | ||||
invqueue = [] | invqueue = [] | ||||
self.sync_transaction( | self.sync_transaction( | ||||
tx.sha256, len(test_instance.blocks_and_transactions)) | tx.sha256, len(test_instance.blocks_and_transactions)) | ||||
if (not self.check_mempool(tx.sha256, tx_outcome)): | if (not self.check_mempool(tx.sha256, tx_outcome)): | ||||
raise AssertionError( | raise AssertionError( | ||||
"Mempool test failed at test %d" % test_number) | "Mempool test failed at test %d" % test_number) | ||||
print("Test %d: PASS" % | logger.info("Test %d: PASS" % test_number) | ||||
test_number, [c.rpc.getblockcount() for c in self.connections]) | |||||
test_number += 1 | test_number += 1 | ||||
[c.disconnect_node() for c in self.connections] | [c.disconnect_node() for c in self.connections] | ||||
self.wait_for_disconnections() | self.wait_for_disconnections() | ||||
self.block_store.close() | self.block_store.close() | ||||
self.tx_store.close() | self.tx_store.close() |