Changeset View
Changeset View
Standalone View
Standalone View
test/functional/test_framework/mininode.py
Show First 20 Lines • Show All 547 Lines • ▼ Show 20 Lines | |||||
class P2PDataStore(P2PInterface): | class P2PDataStore(P2PInterface): | ||||
"""A P2P data store class. | """A P2P data store class. | ||||
Keeps a block and transaction store and responds correctly to getdata and getheaders requests.""" | Keeps a block and transaction store and responds correctly to getdata and getheaders requests.""" | ||||
def __init__(self): | def __init__(self): | ||||
super().__init__() | super().__init__() | ||||
self.reject_code_received = None | |||||
self.reject_reason_received = None | |||||
# store of blocks. key is block hash, value is a CBlock object | # store of blocks. key is block hash, value is a CBlock object | ||||
self.block_store = {} | self.block_store = {} | ||||
self.last_block_hash = '' | self.last_block_hash = '' | ||||
# store of txs. key is txid, value is a CTransaction object | # store of txs. key is txid, value is a CTransaction object | ||||
self.tx_store = {} | self.tx_store = {} | ||||
self.getdata_requests = [] | self.getdata_requests = [] | ||||
def on_getdata(self, message): | def on_getdata(self, message): | ||||
Show All 37 Lines | def on_getheaders(self, message): | ||||
# Truncate the list if there are too many headers | # Truncate the list if there are too many headers | ||||
headers_list = headers_list[:-maxheaders - 1:-1] | headers_list = headers_list[:-maxheaders - 1:-1] | ||||
response = msg_headers(headers_list) | response = msg_headers(headers_list) | ||||
if response is not None: | if response is not None: | ||||
self.send_message(response) | self.send_message(response) | ||||
def on_reject(self, message): | def send_blocks_and_test(self, blocks, node, *, success=True, request_block=True, reject_code=None, reject_reason=None, expect_disconnect=False, timeout=60): | ||||
"""Store reject reason and code for testing.""" | # TODO: Migrate all tests off of using `reject_code` and then remove it. | ||||
self.reject_code_received = message.code | |||||
self.reject_reason_received = message.reason | |||||
def send_blocks_and_test(self, blocks, node, *, success=True, request_block=True, reject_code=None, reject_reason=None, timeout=60): | |||||
"""Send blocks to test node and test whether the tip advances. | """Send blocks to test node and test whether the tip advances. | ||||
- add all blocks to our block_store | - add all blocks to our block_store | ||||
- send a headers message for the final block | - send a headers message for the final block | ||||
- the on_getheaders handler will ensure that any getheaders are responded to | - the on_getheaders handler will ensure that any getheaders are responded to | ||||
- if request_block is True: wait for getdata for each of the blocks. The on_getdata handler will | - if request_block is True: wait for getdata for each of the blocks. The on_getdata handler will | ||||
ensure that any getdata messages are responded to | ensure that any getdata messages are responded to | ||||
- if success is True: assert that the node's tip advances to the most recent block | - if success is True: assert that the node's tip advances to the most recent block | ||||
- if success is False: assert that the node's tip doesn't advance | - if success is False: assert that the node's tip doesn't advance | ||||
- if reject_code and reject_reason are set: assert that the correct reject message is received""" | - if reject_reason is set: assert that the correct reject message is logged""" | ||||
with mininode_lock: | with mininode_lock: | ||||
self.reject_code_received = None | |||||
self.reject_reason_received = None | |||||
for block in blocks: | for block in blocks: | ||||
self.block_store[block.sha256] = block | self.block_store[block.sha256] = block | ||||
self.last_block_hash = block.sha256 | self.last_block_hash = block.sha256 | ||||
# TODO: Remove decode() once all callers are migrated to use strings instead of bytes. | |||||
if isinstance(reject_reason, bytes): | |||||
reject_reason = reject_reason.decode('utf-8') | |||||
reject_reason = [reject_reason] if reject_reason else [] | |||||
with node.assert_debug_log(expected_msgs=reject_reason): | |||||
self.send_message(msg_headers([CBlockHeader(blocks[-1])])) | self.send_message(msg_headers([CBlockHeader(blocks[-1])])) | ||||
if request_block: | if request_block: | ||||
wait_until( | wait_until( | ||||
lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout, lock=mininode_lock) | lambda: blocks[-1].sha256 in self.getdata_requests, timeout=timeout, lock=mininode_lock) | ||||
if expect_disconnect: | |||||
self.wait_for_disconnect() | |||||
else: | |||||
self.sync_with_ping() | |||||
if success: | if success: | ||||
wait_until(lambda: node.getbestblockhash() == | wait_until(lambda: node.getbestblockhash() == | ||||
blocks[-1].hash, timeout=timeout) | blocks[-1].hash, timeout=timeout) | ||||
else: | else: | ||||
assert node.getbestblockhash() != blocks[-1].hash | assert node.getbestblockhash() != blocks[-1].hash | ||||
if reject_code is not None: | |||||
wait_until(lambda: self.reject_code_received == | |||||
reject_code, lock=mininode_lock) | |||||
if reject_reason is not None: | |||||
wait_until(lambda: self.reject_reason_received == | |||||
reject_reason, lock=mininode_lock) | |||||
def send_txs_and_test(self, txs, node, *, success=True, expect_disconnect=False, reject_code=None, reject_reason=None): | def send_txs_and_test(self, txs, node, *, success=True, expect_disconnect=False, reject_code=None, reject_reason=None): | ||||
# TODO: Migrate all tests off of using `reject_code` and then remove it. | |||||
"""Send txs to test node and test whether they're accepted to the mempool. | """Send txs to test node and test whether they're accepted to the mempool. | ||||
- add all txs to our tx_store | - add all txs to our tx_store | ||||
- send tx messages for all txs | - send tx messages for all txs | ||||
- if success is True/False: assert that the txs are/are not accepted to the mempool | - if success is True/False: assert that the txs are/are not accepted to the mempool | ||||
- if expect_disconnect is True: Skip the sync with ping | - if expect_disconnect is True: Skip the sync with ping | ||||
- if reject_code and reject_reason are set: assert that the correct reject message is received.""" | - if reject_reason is set: assert that the correct reject message is logged.""" | ||||
with mininode_lock: | with mininode_lock: | ||||
self.reject_code_received = None | |||||
self.reject_reason_received = None | |||||
for tx in txs: | for tx in txs: | ||||
self.tx_store[tx.sha256] = tx | self.tx_store[tx.sha256] = tx | ||||
# TODO: Remove decode() once all callers are migrated to use strings instead of bytes. | |||||
if isinstance(reject_reason, bytes): | |||||
reject_reason = reject_reason.decode('utf-8') | |||||
reject_reason = [reject_reason] if reject_reason else [] | |||||
with node.assert_debug_log(expected_msgs=reject_reason): | |||||
for tx in txs: | for tx in txs: | ||||
self.send_message(msg_tx(tx)) | self.send_message(msg_tx(tx)) | ||||
if expect_disconnect: | if expect_disconnect: | ||||
self.wait_for_disconnect() | self.wait_for_disconnect() | ||||
else: | else: | ||||
self.sync_with_ping() | self.sync_with_ping() | ||||
raw_mempool = node.getrawmempool() | raw_mempool = node.getrawmempool() | ||||
if success: | if success: | ||||
# Check that all txs are now in the mempool | # Check that all txs are now in the mempool | ||||
for tx in txs: | for tx in txs: | ||||
assert tx.hash in raw_mempool, "{} not found in mempool".format( | assert tx.hash in raw_mempool, "{} not found in mempool".format( | ||||
tx.hash) | tx.hash) | ||||
else: | else: | ||||
# Check that none of the txs are now in the mempool | # Check that none of the txs are now in the mempool | ||||
for tx in txs: | for tx in txs: | ||||
assert tx.hash not in raw_mempool, "{} tx found in mempool".format( | assert tx.hash not in raw_mempool, "{} tx found in mempool".format( | ||||
tx.hash) | tx.hash) | ||||
if reject_code is not None: | |||||
wait_until(lambda: self.reject_code_received == | |||||
reject_code, lock=mininode_lock) | |||||
if reject_reason is not None: | |||||
wait_until(lambda: self.reject_reason_received == | |||||
reject_reason, lock=mininode_lock) |