mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 20:42:59 +01:00
57b6fe7327
bdb8b9a347e68f80a2e8d44ce5590a2e8214b6bb test: doc: improve doc for `from_hex` helper (mention `to_hex` alternative) (Sebastian Falbesoner) 191405420815d49ab50184513717a303fc2744d6 scripted-diff: test: rename `FromHex` to `from_hex` (Sebastian Falbesoner) a79396fe5f8f81c78cf84117a87074c6ff6c9d95 test: remove `ToHex` helper, use .serialize().hex() instead (Sebastian Falbesoner) 2ce7b47958c4a10ba20dc86c011d71cda4b070a5 test: introduce `tx_from_hex` helper for tx deserialization (Sebastian Falbesoner) Pull request description: There are still many functional tests that perform conversions from a hex-string to a message object (deserialization) manually. This PR identifies all those instances and replaces them with a newly introduced helper `tx_from_hex`. Instances were found via * `git grep "deserialize.*BytesIO"` and some of them manually, when it were not one-liners. Further, the helper `ToHex` was removed and simply replaced by `.serialize().hex()`, since now both variants are in use (sometimes even within the same test) and using the helper doesn't really have an advantage in readability. (see discussion https://github.com/bitcoin/bitcoin/pull/22257#discussion_r652404782) ACKs for top commit: MarcoFalke: review re-ACK bdb8b9a347e68f80a2e8d44ce5590a2e8214b6bb 😁 Tree-SHA512: e25d7dc85918de1d6755a5cea65471b07a743204c20ad1c2f71ff07ef48cc1b9ad3fe5f515c1efaba2b2e3d89384e7980380c5d81895f9826e2046808cd3266e
175 lines
6.3 KiB
Python
Executable File
175 lines
6.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2019-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 transaction download behavior
|
|
"""
|
|
|
|
from test_framework.messages import (
|
|
CInv,
|
|
MSG_TX,
|
|
MSG_TYPE_MASK,
|
|
msg_inv,
|
|
msg_notfound,
|
|
tx_from_hex,
|
|
)
|
|
from test_framework.p2p import (
|
|
P2PInterface,
|
|
p2p_lock,
|
|
)
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import (
|
|
assert_equal,
|
|
)
|
|
from test_framework.address import ADDRESS_BCRT1_UNSPENDABLE
|
|
|
|
|
|
class TestP2PConn(P2PInterface):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.tx_getdata_count = 0
|
|
|
|
def on_getdata(self, message):
|
|
for i in message.inv:
|
|
if i.type & MSG_TYPE_MASK == MSG_TX:
|
|
self.tx_getdata_count += 1
|
|
|
|
|
|
# Constants from net_processing
|
|
GETDATA_TX_INTERVAL = 60 # seconds
|
|
MAX_GETDATA_RANDOM_DELAY = 2 # seconds
|
|
INBOUND_PEER_TX_DELAY = 2 # seconds
|
|
MAX_GETDATA_IN_FLIGHT = 100
|
|
TX_EXPIRY_INTERVAL = GETDATA_TX_INTERVAL * 10
|
|
|
|
# Python test constants
|
|
NUM_INBOUND = 10
|
|
MAX_GETDATA_INBOUND_WAIT = GETDATA_TX_INTERVAL + MAX_GETDATA_RANDOM_DELAY + INBOUND_PEER_TX_DELAY
|
|
|
|
|
|
class TxDownloadTest(BitcoinTestFramework):
|
|
def set_test_params(self):
|
|
self.num_nodes = 2
|
|
|
|
def test_tx_requests(self):
|
|
self.log.info("Test that we request transactions from all our peers, eventually")
|
|
|
|
txid = 0xdeadbeef
|
|
|
|
self.log.info("Announce the txid from each incoming peer to node 0")
|
|
msg = msg_inv([CInv(t=1, h=txid)])
|
|
for p in self.nodes[0].p2ps:
|
|
p.send_and_ping(msg)
|
|
|
|
outstanding_peer_index = [i for i in range(len(self.nodes[0].p2ps))]
|
|
|
|
def getdata_found(peer_index):
|
|
p = self.nodes[0].p2ps[peer_index]
|
|
with p2p_lock:
|
|
return p.last_message.get("getdata") and p.last_message["getdata"].inv[-1].hash == txid
|
|
|
|
while outstanding_peer_index:
|
|
self.bump_mocktime(MAX_GETDATA_INBOUND_WAIT)
|
|
self.wait_until(lambda: any(getdata_found(i) for i in outstanding_peer_index))
|
|
for i in outstanding_peer_index:
|
|
if getdata_found(i):
|
|
outstanding_peer_index.remove(i)
|
|
|
|
self.log.info("All outstanding peers received a getdata")
|
|
|
|
def test_inv_block(self):
|
|
self.log.info("Generate a transaction on node 0")
|
|
tx = self.nodes[0].createrawtransaction(
|
|
inputs=[{ # coinbase
|
|
"txid": self.nodes[0].getblock(self.nodes[0].getblockhash(1))['tx'][0],
|
|
"vout": 0
|
|
}],
|
|
outputs={ADDRESS_BCRT1_UNSPENDABLE: 500 - 0.00025},
|
|
)
|
|
tx = self.nodes[0].signrawtransactionwithkey(
|
|
hexstring=tx,
|
|
privkeys=[self.nodes[0].get_deterministic_priv_key().key],
|
|
)['hex']
|
|
ctx = tx_from_hex(tx)
|
|
txid = int(ctx.rehash(), 16)
|
|
|
|
self.log.info(
|
|
"Announce the transaction to all nodes from all {} incoming peers, but never send it".format(NUM_INBOUND))
|
|
msg = msg_inv([CInv(t=1, h=txid)])
|
|
for p in self.peers:
|
|
p.send_and_ping(msg)
|
|
p.sync_with_ping()
|
|
self.bump_mocktime(1)
|
|
|
|
self.log.info("Put the tx in node 0's mempool")
|
|
self.nodes[0].sendrawtransaction(tx)
|
|
|
|
# Since node 1 is connected outbound to an honest peer (node 0), it
|
|
# should get the tx within a timeout. (Assuming that node 0
|
|
# announced the tx within the timeout)
|
|
# The timeout is the sum of
|
|
# * the worst case until the tx is first requested from an inbound
|
|
# peer, plus
|
|
# * the first time it is re-requested from the outbound peer, plus
|
|
# * 2 seconds to avoid races
|
|
timeout = 2 + (MAX_GETDATA_RANDOM_DELAY + INBOUND_PEER_TX_DELAY) + (
|
|
GETDATA_TX_INTERVAL + MAX_GETDATA_RANDOM_DELAY)
|
|
self.log.info("Tx should be received at node 1 after {} seconds".format(timeout))
|
|
self.sync_mempools(timeout=timeout)
|
|
|
|
def test_in_flight_max(self):
|
|
self.log.info("Test that we don't request more than {} transactions from any peer, every {} minutes".format(
|
|
MAX_GETDATA_IN_FLIGHT, TX_EXPIRY_INTERVAL / 60))
|
|
txids = [i for i in range(MAX_GETDATA_IN_FLIGHT + 2)]
|
|
|
|
p = self.nodes[0].p2ps[0]
|
|
|
|
with p2p_lock:
|
|
p.tx_getdata_count = 0
|
|
|
|
p.send_message(msg_inv([CInv(t=1, h=i) for i in txids]))
|
|
|
|
def wait_for_tx_getdata(target):
|
|
self.bump_mocktime(1)
|
|
return p.tx_getdata_count >= target
|
|
|
|
p.wait_until(lambda: wait_for_tx_getdata(MAX_GETDATA_IN_FLIGHT))
|
|
|
|
with p2p_lock:
|
|
assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT)
|
|
|
|
self.log.info("Now check that if we send a NOTFOUND for a transaction, we'll get one more request")
|
|
p.send_message(msg_notfound(vec=[CInv(t=1, h=txids[0])]))
|
|
p.wait_until(lambda: wait_for_tx_getdata(MAX_GETDATA_IN_FLIGHT + 1), timeout=10)
|
|
with p2p_lock:
|
|
assert_equal(p.tx_getdata_count, MAX_GETDATA_IN_FLIGHT + 1)
|
|
|
|
WAIT_TIME = TX_EXPIRY_INTERVAL // 2 + TX_EXPIRY_INTERVAL
|
|
self.log.info("if we wait about {} minutes, we should eventually get more requests".format(WAIT_TIME / 60))
|
|
self.bump_mocktime(WAIT_TIME)
|
|
p.wait_until(lambda: wait_for_tx_getdata(MAX_GETDATA_IN_FLIGHT + 2))
|
|
|
|
def test_spurious_notfound(self):
|
|
self.log.info('Check that spurious notfound is ignored')
|
|
self.nodes[0].p2ps[0].send_message(msg_notfound(vec=[CInv(1, 1)]))
|
|
|
|
def run_test(self):
|
|
# Run each test against new bitcoind instances, as setting mocktimes has long-term effects on when
|
|
# the next trickle relay event happens.
|
|
for test in [self.test_spurious_notfound, self.test_in_flight_max, self.test_inv_block, self.test_tx_requests]:
|
|
self.stop_nodes()
|
|
self.start_nodes()
|
|
self.connect_nodes(1, 0)
|
|
# Setup the p2p connections
|
|
self.peers = []
|
|
for node in self.nodes:
|
|
for _ in range(NUM_INBOUND):
|
|
self.peers.append(node.add_p2p_connection(TestP2PConn()))
|
|
self.log.info("Nodes are setup with {} incoming connections each".format(NUM_INBOUND))
|
|
test()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
TxDownloadTest().main()
|