forked from bitcoin/bitcoin
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: add functional test for local tx relay
- Loading branch information
Showing
2 changed files
with
249 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
#!/usr/bin/env python3 | ||
# Copyright (c) 2017-2023 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 how locally submitted transactions are sent to the network when sensitive relay is used. | ||
The topology in the test is: | ||
Bitcoin Core (node0) | ||
^ v The default full-outbound + block-only connections | ||
| | (MAX_OUTBOUND_FULL_RELAY_CONNECTIONS + MAX_BLOCK_RELAY_ONLY_CONNECTIONS) | ||
| | | ||
| *-----> SOCKS5 Proxy ---> P2PInterface (self.socks5_server.conf.destinations[0]["node"]) | ||
| | | ||
| *-----> SOCKS5 Proxy ---> P2PInterface (self.socks5_server.conf.destinations[1]["node"]) | ||
| ... | ||
| | The sensitive relay TX recipients (SENSITIVE_RELAY_NUM_BROADCAST_PER_TX): | ||
| | | ||
| *-----> SOCKS5 Proxy ---> P2PInterface (self.socks5_server.conf.destinations[i]["node"]) | ||
| | | ||
| *-----> SOCKS5 Proxy ---> P2PInterface (self.socks5_server.conf.destinations[i + 1]["node"]) | ||
| ... | ||
| | ||
*---------< observer_inbound | ||
""" | ||
|
||
from test_framework.p2p import P2PDataStore, P2PInterface, P2P_SERVICES | ||
from test_framework.messages import msg_getdata, msg_inv, msg_mempool, CInv, MSG_WTX | ||
from test_framework.socks5 import Socks5Configuration, Socks5Server | ||
from test_framework.test_framework import BitcoinTestFramework | ||
from test_framework.util import assert_equal, MAX_NODES, p2p_port | ||
from test_framework.wallet import MiniWallet | ||
|
||
MAX_OUTBOUND_FULL_RELAY_CONNECTIONS = 8 | ||
MAX_BLOCK_RELAY_ONLY_CONNECTIONS = 2 | ||
SENSITIVE_RELAY_NUM_BROADCAST_PER_TX = 5 | ||
|
||
g_p2p_index = None | ||
def new_p2p_index(): | ||
global g_p2p_index | ||
g_p2p_index += 1 | ||
return g_p2p_index | ||
|
||
# Fill addrman with these addresses. Must have enough Tor addresses, so that even | ||
# if all 10 default connections are opened to a Tor address (!?) there must be more | ||
# for sensitive relays. | ||
addresses = [ | ||
"1.65.195.98", | ||
"2.59.236.56", | ||
"2.83.114.20", | ||
"2.248.194.16", | ||
"5.2.154.6", | ||
"5.101.140.30", | ||
"5.128.87.126", | ||
"5.144.21.49", | ||
"5.172.132.104", | ||
"5.188.62.18", | ||
"5.200.2.180", | ||
"8.129.184.255", | ||
"8.209.105.138", | ||
"12.34.98.148", | ||
"14.199.102.151", | ||
"18.27.79.17", | ||
"18.27.124.231", | ||
"18.216.249.151", | ||
"23.88.155.58", | ||
"23.93.101.158", | ||
"[2001:19f0:1000:1db3:5400:4ff:fe56:5a8d]", | ||
"[2001:19f0:5:24da:3eec:efff:feb9:f36e]", | ||
"[2001:19f0:5:24da::]", | ||
"[2001:19f0:5:4535:3eec:efff:feb9:87e4]", | ||
"[2001:19f0:5:4535::]", | ||
"[2001:1bc0:c1::2000]", | ||
"[2001:1c04:4008:6300:8a5f:2678:114b:a660]", | ||
"[2001:41d0:203:3739::]", | ||
"[2001:41d0:203:8f49::]", | ||
"[2001:41d0:203:bb0a::]", | ||
"[2001:41d0:2:bf8f::]", | ||
"[2001:41d0:303:de8b::]", | ||
"[2001:41d0:403:3d61::]", | ||
"[2001:41d0:405:9600::]", | ||
"[2001:41d0:8:ed7f::1]", | ||
"[2001:41d0:a:69a2::1]", | ||
"[2001:41f0::62:6974:636f:696e]", | ||
"[2001:470:1b62::]", | ||
"[2001:470:1f05:43b:2831:8530:7179:5864]", | ||
"[2001:470:1f09:b14::11]", | ||
"2bqghnldu6mcug4pikzprwhtjjnsyederctvci6klcwzepnjd46ikjyd.onion", | ||
"4lr3w2iyyl5u5l6tosizclykf5v3smqroqdn2i4h3kq6pfbbjb2xytad.onion", | ||
"5g72ppm3krkorsfopcm2bi7wlv4ohhs4u4mlseymasn7g7zhdcyjpfid.onion", | ||
"5sbmcl4m5api5tqafi4gcckrn3y52sz5mskxf3t6iw4bp7erwiptrgqd.onion", | ||
"776aegl7tfhg6oiqqy76jnwrwbvcytsx2qegcgh2mjqujll4376ohlid.onion", | ||
"77mdte42srl42shdh2mhtjr7nf7dmedqrw6bkcdekhdvmnld6ojyyiad.onion", | ||
"azbpsh4arqlm6442wfimy7qr65bmha2zhgjg7wbaji6vvaug53hur2qd.onion", | ||
"b64xcbleqmwgq2u46bh4hegnlrzzvxntyzbmucn3zt7cssm7y4ubv3id.onion", | ||
"bsqbtcparrfihlwolt4xgjbf4cgqckvrvsfyvy6vhiqrnh4w6ghixoid.onion", | ||
"bsqbtctulf2g4jtjsdfgl2ed7qs6zz5wqx27qnyiik7laockryvszqqd.onion", | ||
"cwi3ekrwhig47dhhzfenr5hbvckj7fzaojygvazi2lucsenwbzwoyiqd.onion", | ||
"devinbtcmwkuitvxl3tfi5of4zau46ymeannkjv6fpnylkgf3q5fa3id.onion", | ||
"devinbtctu7uctl7hly2juu3thbgeivfnvw3ckj3phy6nyvpnx66yeyd.onion", | ||
"devinbtcyk643iruzfpaxw3on2jket7rbjmwygm42dmdyub3ietrbmid.onion", | ||
"dtql5vci4iaml4anmueftqr7bfgzqlauzfy4rc2tfgulldd3ekyijjyd.onion", | ||
"emzybtc25oddoa2prol2znpz2axnrg6k77xwgirmhv7igoiucddsxiad.onion", | ||
"emzybtc3ewh7zihpkdvuwlgxrhzcxy2p5fvjggp7ngjbxcytxvt4rjid.onion", | ||
"emzybtc454ewbviqnmgtgx3rgublsgkk23r4onbhidcv36wremue4kqd.onion", | ||
"emzybtc5bnpb2o6gh54oquiox54o4r7yn4a2wiiwzrjonlouaibm2zid.onion", | ||
"fpz6r5ppsakkwypjcglz6gcnwt7ytfhxskkfhzu62tnylcknh3eq6pad.onion", | ||
"255fhcp6ajvftnyo7bwz3an3t4a4brhopm3bamyh2iu5r3gnr2rq.b32.i2p", | ||
"27yrtht5b5bzom2w5ajb27najuqvuydtzb7bavlak25wkufec5mq.b32.i2p", | ||
"3gocb7wc4zvbmmebktet7gujccuux4ifk3kqilnxnj5wpdpqx2hq.b32.i2p", | ||
"4fcc23wt3hyjk3csfzcdyjz5pcwg5dzhdqgma6bch2qyiakcbboa.b32.i2p", | ||
"4osyqeknhx5qf3a73jeimexwclmt42cju6xdp7icja4ixxguu2hq.b32.i2p", | ||
"4umsi4nlmgyp4rckosg4vegd2ysljvid47zu7pqsollkaszcbpqq.b32.i2p", | ||
"6j2ezegd3e2e2x3o3pox335f5vxfthrrigkdrbgfbdjchm5h4awa.b32.i2p", | ||
"6n36ljyr55szci5ygidmxqer64qr24f4qmnymnbvgehz7qinxnla.b32.i2p", | ||
"72yjs6mvlby3ky6mgpvvlemmwq5pfcznrzd34jkhclgrishqdxva.b32.i2p", | ||
"a5qsnv3maw77mlmmzlcglu6twje6ttctd3fhpbfwcbpmewx6fczq.b32.i2p", | ||
"aovep2pco7v2k4rheofrgytbgk23eg22dczpsjqgqtxcqqvmxk6a.b32.i2p", | ||
"bitcoi656nll5hu6u7ddzrmzysdtwtnzcnrjd4rfdqbeey7dmn5a.b32.i2p", | ||
"brifkruhlkgrj65hffybrjrjqcgdgqs2r7siizb5b2232nruik3a.b32.i2p", | ||
"c4gfnttsuwqomiygupdqqqyy5y5emnk5c73hrfvatri67prd7vyq.b32.i2p", | ||
"day3hgxyrtwjslt54sikevbhxxs4qzo7d6vi72ipmscqtq3qmijq.b32.i2p", | ||
"du5kydummi23bjfp6bd7owsvrijgt7zhvxmz5h5f5spcioeoetwq.b32.i2p", | ||
"e55k6wu46rzp4pg5pk5npgbr3zz45bc3ihtzu2xcye5vwnzdy7pq.b32.i2p", | ||
"eciohu5nq7vsvwjjc52epskuk75d24iccgzmhbzrwonw6lx4gdva.b32.i2p", | ||
"ejlnngarmhqvune74ko7kk55xtgbz5i5ncs4vmnvjpy3l7y63xaa.b32.i2p", | ||
"fhzlp3xroabohnmjonu5iqazwhlbbwh5cpujvw2azcu3srqdceja.b32.i2p", | ||
"[fc32:17ea:e415:c3bf:9808:149d:b5a2:c9aa]", | ||
"[fcc7:be49:ccd1:dc91:3125:f0da:457d:8ce]", | ||
"[fcdc:73ae:b1a9:1bf8:d4c2:811:a4c7:c34e]", | ||
] | ||
|
||
class P2PLocalTxRelay(BitcoinTestFramework): | ||
def set_test_params(self): | ||
self.disable_autoconnect = False | ||
self.num_nodes = 1 | ||
global g_p2p_index | ||
g_p2p_index = self.num_nodes - 1 | ||
|
||
def setup_nodes(self): | ||
# Start a SOCKS5 proxy server. | ||
socks5_server_config = Socks5Configuration() | ||
socks5_server_config.addr = ("127.0.0.1", p2p_port(new_p2p_index())) | ||
socks5_server_config.unauth = True | ||
socks5_server_config.auth = True | ||
|
||
self.socks5_server = Socks5Server(socks5_server_config) | ||
self.socks5_server.start() | ||
|
||
ports_base = p2p_port(MAX_NODES) + 15000 | ||
|
||
def listen_callback(addr, port): | ||
# Instruct the SOCKS5 server to redirect a connection to this Python P2P listener. | ||
self.socks5_server.conf.destinations.append({ | ||
"listen_addr": addr, | ||
"listen_port": port, | ||
"node": None, | ||
"requested_to_addr": None, | ||
}) | ||
for i in range(MAX_OUTBOUND_FULL_RELAY_CONNECTIONS + MAX_BLOCK_RELAY_ONLY_CONNECTIONS + SENSITIVE_RELAY_NUM_BROADCAST_PER_TX): | ||
# Create a Python P2P listening node and add it to self.socks5_server.conf.destinations[] | ||
listener = P2PInterface() | ||
listener.peer_connect_helper(dstaddr="0.0.0.0", dstport=0, net=self.chain, timeout_factor=self.options.timeout_factor) | ||
listener.peer_connect_send_version(services=P2P_SERVICES) | ||
self.network_thread.listen(p2p=listener, callback=listen_callback, addr="127.0.0.1", port=ports_base + i) | ||
self.wait_until(lambda: len(self.socks5_server.conf.destinations) == i + 1) | ||
self.socks5_server.conf.destinations[i]["node"] = listener | ||
|
||
self.add_nodes(self.num_nodes, extra_args=[ | ||
[ | ||
"-peerbloomfilters", # needed to test replies to MEMPOOL | ||
"-sensitiverelayowntx", | ||
f"-proxy={socks5_server_config.addr[0]}:{socks5_server_config.addr[1]}" | ||
] | ||
]) | ||
self.start_nodes() | ||
|
||
def run_test(self): | ||
node0 = self.nodes[0] | ||
|
||
# Fill node0's addrman. | ||
for addr in addresses: | ||
res = node0.addpeeraddress(address=addr, port=8333, tried=False) | ||
if res["success"]: | ||
self.log.debug(f"Added {addr} to node0's addrman") | ||
else: | ||
self.log.debug(f"Could not add {addr} to node0's addrman (collision?)") | ||
|
||
observer_inbound = node0.add_p2p_connection(P2PDataStore()) | ||
|
||
self.log.info("Getting out of IBD") | ||
self.generatetoaddress(node0, 1, node0.get_deterministic_priv_key().address) | ||
|
||
num_initial_connections = MAX_OUTBOUND_FULL_RELAY_CONNECTIONS + MAX_BLOCK_RELAY_ONLY_CONNECTIONS | ||
self.wait_until(lambda: self.socks5_server.conf.destinations_used == num_initial_connections) | ||
assert_equal(self.socks5_server.conf.destinations_used, num_initial_connections) | ||
|
||
# The next opened connections should be "sensitive relay" for sending the transaction. | ||
|
||
miniwallet = MiniWallet(node0) | ||
tx = miniwallet.send_self_transfer(from_node=node0) | ||
self.log.info(f"Created transaction txid={tx['txid']}") | ||
|
||
for i in range(num_initial_connections, num_initial_connections + SENSITIVE_RELAY_NUM_BROADCAST_PER_TX): | ||
tx_recipient = self.socks5_server.conf.destinations[i]["node"] | ||
tx_recipient.wait_for_connect() | ||
assert self.socks5_server.conf.destinations[i]["requested_to_addr"].endswith(".onion") | ||
tx_recipient.wait_for_tx(tx['txid']) | ||
tx_recipient.wait_for_disconnect() | ||
assert_equal(tx_recipient.message_count, { | ||
"version": 1, | ||
"verack": 1, | ||
"tx": 1, | ||
"ping": 1 | ||
}) | ||
|
||
wtxid_int = int(tx["wtxid"], 16) | ||
inv = CInv(MSG_WTX, wtxid_int) | ||
|
||
observer_outbound = self.socks5_server.conf.destinations[0]["node"] | ||
|
||
self.log.info("Checking that the node hides the transaction from GETDATA requests") | ||
for observer in [observer_inbound, observer_outbound]: | ||
observer.last_message.pop("notfound", None) | ||
observer.send_message(msg_getdata([inv])) | ||
observer.wait_until(lambda: "notfound" in observer.last_message) | ||
assert "tx" not in observer.last_message | ||
|
||
self.log.info("Checking that the node hides the transaction from MEMPOOL requests") | ||
msg = f"Omitting INV for unbroadcast transaction (txid={tx['txid']}) from MEMPOOL reply".encode("utf-8") | ||
for observer in [observer_inbound, observer_outbound]: | ||
with node0.wait_for_debug_log([msg]): | ||
observer.send_message(msg_mempool()) | ||
assert "tx" not in observer.last_message | ||
|
||
self.log.info("Sending INV from an observer and waiting for GETDATA from node") | ||
observer_inbound.tx_store[wtxid_int] = tx["tx"] | ||
msg = f"Received own transaction (txid={tx['txid']}) from the network".encode("utf-8") | ||
with node0.wait_for_debug_log(expected_msgs=[msg]): | ||
observer_inbound.send_message(msg_inv([inv])) | ||
|
||
self.log.info("Waiting for normal broadcast to another observer") | ||
observer_outbound.wait_for_inv([inv]) | ||
|
||
|
||
if __name__ == '__main__': | ||
P2PLocalTxRelay().main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters