mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 12:02:48 +01:00
f34889dcf4
d5800da5199527a366024bc80cad7fcca17d5c4a [test] Remove final references to mininode (John Newbery) 5e8df3312e47a73e747ee892face55ed9ababeea test: resort imports (John Newbery) 85165d4332b0f72d30e0c584b476249b542338e6 scripted-diff: Rename mininode to p2p (John Newbery) 9e2897d020b114a10c860f90c5405be029afddba scripted-diff: Rename mininode_lock to p2p_lock (John Newbery) Pull request description: New contributors are often confused by the terminology in the test framework, and what the difference between a _node_ and a _peer_ is. To summarize: - a 'node' is a bitcoind instance. This is the thing whose behavior is being tested. Each bitcoind node is managed by a python `TestNode` object which is used to start/stop the node, manage the node's data directory, read state about the node (eg process status, log file), and interact with the node over different interfaces. - one of the interfaces that we can use to interact with the node is the p2p interface. Each connection to a node using this interface is managed by a python `P2PInterface` or derived object (which is owned by the `TestNode` object). We can open zero, one or many p2p connections to each bitcoind node. The node sees these connections as 'peers'. For historic reasons, the word 'mininode' has been used to refer to those p2p interface objects that we use to connect to the bitcoind node (the code was originally taken from the 'mini-node' branch of https://github.com/jgarzik/pynode/tree/mini-node). However that name has proved to be confusing for new contributors, so rename the remaining references. ACKs for top commit: amitiuttarwar: ACK d5800da519 MarcoFalke: ACK d5800da5199527a366024bc80cad7fcca17d5c4a 🚞 Tree-SHA512: 2c46c2ac3c4278b6e3c647cfd8108428a41e80788fc4f0e386e5b0c47675bc687d94779496c09a3e5ea1319617295be10c422adeeff2d2bd68378e00e0eeb5de
181 lines
6.2 KiB
Python
Executable File
181 lines
6.2 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,
|
|
CTransaction,
|
|
FromHex,
|
|
MSG_TX,
|
|
MSG_TYPE_MASK,
|
|
msg_inv,
|
|
msg_notfound,
|
|
)
|
|
from test_framework.p2p import (
|
|
P2PInterface,
|
|
p2p_lock,
|
|
)
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import (
|
|
assert_equal,
|
|
wait_until,
|
|
)
|
|
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)
|
|
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 = FromHex(CTransaction(), 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
|
|
|
|
wait_until(lambda: wait_for_tx_getdata(MAX_GETDATA_IN_FLIGHT), lock=p2p_lock)
|
|
|
|
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])]))
|
|
wait_until(lambda: wait_for_tx_getdata(MAX_GETDATA_IN_FLIGHT + 1), timeout=10, lock=p2p_lock)
|
|
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)
|
|
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):
|
|
# 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))
|
|
|
|
self.test_spurious_notfound()
|
|
|
|
# Test the in-flight max first, because we want no transactions in
|
|
# flight ahead of this test.
|
|
self.test_in_flight_max()
|
|
|
|
self.test_inv_block()
|
|
|
|
self.test_tx_requests()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
TxDownloadTest().main()
|