Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

functional test framework: Port parts of P2PDataStore to Mintlayer #1222

Merged
merged 1 commit into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 2 additions & 29 deletions test/functional/p2p_submit_orphan.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,40 +21,13 @@
* After submitting a transaction that defines the UTXO, both are in non-orphan mempool
"""

from test_framework.p2p import P2PInterface
from test_framework.p2p import (P2PInterface, P2PDataStore)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.mintlayer import (make_tx_dict, reward_input, tx_input, calc_tx_id)
import scalecodec
import time


class SimpleTxSendingNode(P2PInterface):
""" Simple node capable of sending transactions on command """

def __init__(self):
super().__init__()
self.txs = {}

def submit_tx(self, tx):
self.store_tx(tx)
return self.send_tx_announcement(calc_tx_id(tx))

def store_tx(self, tx):
self.txs[calc_tx_id(tx)] = tx

def send_tx_announcement(self, tx_id):
return self.send_message({ 'new_transaction': "0x" + tx_id })

def on_transaction_request(self, request):
tx_id = request['transaction_request'][2:]
tx = self.txs.get(tx_id)

if tx: response = {'found': tx}
else: response = {'not_found': tx_id}

self.send_message({'transaction_response': response})


class MempoolOrphanSubmissionTest(BitcoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
Expand All @@ -69,7 +42,7 @@ def setup_network(self):

def run_test(self):
# Spin up our node, connect it to the rest via node0
my_node = SimpleTxSendingNode()
my_node = P2PDataStore()
node0 = self.nodes[0]
node0.add_p2p_connection(my_node)

Expand Down
4 changes: 3 additions & 1 deletion test/functional/test_framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,8 @@ def init_mintlayer_types():

"HeaderListRequest": "Vec<Id>",

"HeaderList": "Vec<SignedBlockHeader>",

"TransactionResponse": {
"type": "enum",
"type_mapping": [
Expand All @@ -409,7 +411,7 @@ def init_mintlayer_types():
["ping_response", "PingMessage"],
["new_transaction", "Id"],
["header_list_request", "HeaderListRequest"],
["header_list", "()"], # TODO
["header_list", "HeaderList"],
["block_list_request", "Vec<Id>"], # TODO
["block_response", "()"], # TODO
["announce_addr_request", "PeerAddress"], # TODO
Expand Down
11 changes: 10 additions & 1 deletion test/functional/test_framework/mintlayer.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,15 @@ def calc_tx_id(tx):
return hash_object(base_tx_obj, tx)


def calc_block_id(block):
# If block is a full block, we need block['header']['header'] to strip out body and signature
# If block is a header with signature, we need block['header'] to strip out the signature
# If block is already a header without signature, we keep the object as is (no 'header' field)
while 'header' in block:
block = block['header']
return hash_object(block_header_obj, tx)


def make_tx(inputs, outputs, flags = 0):
signed_tx = make_tx_dict(inputs, outputs, flags)
tx_id = calc_tx_id(signed_tx)
Expand Down Expand Up @@ -150,4 +159,4 @@ def make_delegation_id(outpoint):
blake2b_hasher.update(bytes.fromhex("01000000"))

# Truncate output to match Rust's split()
return hex_to_dec_array(blake2b_hasher.hexdigest()[:64])
return hex_to_dec_array(blake2b_hasher.hexdigest()[:64])
69 changes: 40 additions & 29 deletions test/functional/test_framework/p2p.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@
p2p_port,
wait_until_helper,
)
from test_framework.mintlayer import (
calc_tx_id,
)

logger = logging.getLogger("TestFramework.p2p")

Expand Down Expand Up @@ -662,54 +665,60 @@ class P2PDataStore(P2PInterface):

def __init__(self):
super().__init__()
# store of blocks. key is block hash, value is a CBlock object
# store of blocks. key is block hash, value is a block
self.block_store = {}
self.last_block_hash = ''

# store of txs. key is txid, value is a CTransaction object
self.tx_store = {}
self.getdata_requests = []

def on_getdata(self, message):
"""Check for the tx/block in our stores and if found, reply with an inv message."""
for inv in message.inv:
self.getdata_requests.append(inv.hash)
if (inv.type & MSG_TYPE_MASK) == MSG_TX and inv.hash in self.tx_store.keys():
self.send_message(msg_tx(self.tx_store[inv.hash]))
elif (inv.type & MSG_TYPE_MASK) == MSG_BLOCK and inv.hash in self.block_store.keys():
self.send_message(msg_block(self.block_store[inv.hash]))
else:
logger.debug('getdata message type {} received.'.format(hex(inv.type)))
def submit_tx(self, tx):
self._store_txs([tx])
return self._send_tx_announcement(calc_tx_id(tx))

def on_getheaders(self, message):
"""Search back through our block store for the locator, and reply with a headers message if found."""
def _store_txs(self, txs):
with p2p_lock:
for tx in txs:
self.tx_store[calc_tx_id(tx)] = tx

def _send_tx_announcement(self, tx_id):
return self.send_message({ 'new_transaction': "0x" + tx_id })

def on_transaction_request(self, request):
tx_id = request['transaction_request'][2:]
tx = self.tx_store.get(tx_id)

locator, hash_stop = message.locator, message.hashstop
if tx: response = {'found': tx}
else: response = {'not_found': tx_id}

# Assume that the most recent block added is the tip
if not self.block_store:
self.send_message({'transaction_response': response})

def on_header_list_request(self, message):
"""Search back through our block store for the locator, and reply with a headers message if found."""

if not self.last_block_hash:
self.send_message({'header_list': []})
return

locator = message['header_list_request']

headers_list = [self.block_store[self.last_block_hash]]
while headers_list[-1].sha256 not in locator.vHave:
while calc_block_id(headers_list[-1]) not in locator:
# Walk back through the block store, adding headers to headers_list
# as we go.
prev_block_hash = headers_list[-1].hashPrevBlock
prev_block_hash = headers_list[-1]['prev_block_id']
if prev_block_hash in self.block_store:
prev_block_header = CBlockHeader(self.block_store[prev_block_hash])
prev_block_header = self.block_store[prev_block_hash]
headers_list.append(prev_block_header)
if prev_block_header.sha256 == hash_stop:
# if this is the hashstop header, stop here
break
else:
logger.debug('block hash {} not found in block store'.format(hex(prev_block_hash)))
break

# Truncate the list if there are too many headers
headers_list = headers_list[:-MAX_HEADERS_RESULTS - 1:-1]
response = msg_headers(headers_list)

if response is not None:
self.send_message(response)
self.send_message({'header_list': ["0x" + hdr_hash for hdr_hash in headers_list]})

def send_blocks_and_test(self, blocks, node, *, success=True, force_send=False, reject_reason=None, expect_disconnect=False, timeout=60):
"""Send blocks to test node and test whether the tip advances.
Expand All @@ -723,6 +732,8 @@ def send_blocks_and_test(self, blocks, node, *, success=True, force_send=False,
- if success is False: assert that the node's tip doesn't advance
- if reject_reason is set: assert that the correct reject message is logged"""

assert False, "Not yet ported to Mintlayer"

with p2p_lock:
for block in blocks:
self.block_store[block.sha256] = block
Expand Down Expand Up @@ -760,14 +771,14 @@ def send_txs_and_test(self, txs, node, *, success=True, expect_disconnect=False,
- if expect_disconnect is True: Skip the sync with ping
- if reject_reason is set: assert that the correct reject message is logged."""

with p2p_lock:
for tx in txs:
self.tx_store[tx.sha256] = tx
assert False, "Not yet ported to Mintlayer"

self._store_txs(txs)

reject_reason = [reject_reason] if reject_reason else []
with node.assert_debug_log(expected_msgs=reject_reason):
for tx in txs:
self.send_message(msg_tx(tx))
self.send_message({ 'new_transaction': "0x" + calc_tx_id(tx) })

if expect_disconnect:
self.wait_for_disconnect()
Expand Down
Loading