p2p_leak_tx.py raw
1 #!/usr/bin/env python3
2 # Copyright (c) 2017-2021 The Limenka developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """Test transaction upload"""
6
7 from test_framework.messages import msg_getdata, CInv, MSG_TX, MSG_WTX
8 from test_framework.p2p import p2p_lock, P2PDataStore, P2PTxInvStore
9 from test_framework.test_framework import LimenkaTestFramework
10 from test_framework.util import (
11 assert_equal,
12 )
13 from test_framework.wallet import MiniWallet
14
15
16 class P2PNode(P2PDataStore):
17 def on_inv(self, msg):
18 pass
19
20
21 class P2PLeakTxTest(LimenkaTestFramework):
22 def set_test_params(self):
23 self.num_nodes = 1
24
25 def run_test(self):
26 self.gen_node = self.nodes[0] # The block and tx generating node
27 self.miniwallet = MiniWallet(self.gen_node)
28
29 self.test_tx_in_block()
30 self.test_notfound_on_replaced_tx()
31 self.test_notfound_on_unannounced_tx()
32
33 def test_tx_in_block(self):
34 self.log.info("Check that a transaction in the last block is uploaded (beneficial for compact block relay)")
35 inbound_peer = self.gen_node.add_p2p_connection(P2PNode())
36
37 self.log.debug("Generate transaction and block")
38 inbound_peer.last_message.pop("inv", None)
39 wtxid = self.miniwallet.send_self_transfer(from_node=self.gen_node)["wtxid"]
40 inbound_peer.wait_until(lambda: "inv" in inbound_peer.last_message and inbound_peer.last_message.get("inv").inv[0].hash == int(wtxid, 16))
41 want_tx = msg_getdata(inv=inbound_peer.last_message.get("inv").inv)
42 self.generate(self.gen_node, 1)
43
44 self.log.debug("Request transaction")
45 inbound_peer.last_message.pop("tx", None)
46 inbound_peer.send_and_ping(want_tx)
47 assert_equal(inbound_peer.last_message.get("tx").tx.getwtxid(), wtxid)
48
49 def test_notfound_on_replaced_tx(self):
50 self.gen_node.disconnect_p2ps()
51 inbound_peer = self.gen_node.add_p2p_connection(P2PTxInvStore())
52
53 self.log.info("Transaction tx_a is broadcast")
54 tx_a = self.miniwallet.send_self_transfer(from_node=self.gen_node)
55 inbound_peer.wait_for_broadcast(txns=[tx_a["wtxid"]])
56
57 tx_b = tx_a["tx"]
58 tx_b.vout[0].nValue -= 9000
59 self.gen_node.sendrawtransaction(tx_b.serialize().hex())
60 inbound_peer.wait_until(lambda: "tx" in inbound_peer.last_message and inbound_peer.last_message.get("tx").tx.getwtxid() == tx_b.getwtxid())
61
62 self.log.info("Re-request of tx_a after replacement is answered with notfound")
63 req_vec = [
64 CInv(t=MSG_TX, h=int(tx_a["txid"], 16)),
65 CInv(t=MSG_WTX, h=int(tx_a["wtxid"], 16)),
66 ]
67 want_tx = msg_getdata()
68 want_tx.inv = req_vec
69 with p2p_lock:
70 inbound_peer.last_message.pop("notfound", None)
71 inbound_peer.last_message.pop("tx", None)
72 inbound_peer.send_and_ping(want_tx)
73
74 assert_equal(inbound_peer.last_message.get("notfound").vec, req_vec)
75 assert "tx" not in inbound_peer.last_message
76
77 def test_notfound_on_unannounced_tx(self):
78 self.log.info("Check that we don't leak txs to inbound peers that we haven't yet announced to")
79 self.gen_node.disconnect_p2ps()
80 inbound_peer = self.gen_node.add_p2p_connection(P2PNode()) # An "attacking" inbound peer
81
82 MAX_REPEATS = 100
83 self.log.info("Running test up to {} times.".format(MAX_REPEATS))
84 for i in range(MAX_REPEATS):
85 self.log.info('Run repeat {}'.format(i + 1))
86 txid = self.miniwallet.send_self_transfer(from_node=self.gen_node)["wtxid"]
87
88 want_tx = msg_getdata()
89 want_tx.inv.append(CInv(t=MSG_TX, h=int(txid, 16)))
90 with p2p_lock:
91 inbound_peer.last_message.pop('notfound', None)
92 inbound_peer.send_and_ping(want_tx)
93
94 if inbound_peer.last_message.get('notfound'):
95 self.log.debug('tx {} was not yet announced to us.'.format(txid))
96 self.log.debug("node has responded with a notfound message. End test.")
97 assert_equal(inbound_peer.last_message['notfound'].vec[0].hash, int(txid, 16))
98 with p2p_lock:
99 inbound_peer.last_message.pop('notfound')
100 break
101 else:
102 self.log.debug('tx {} was already announced to us. Try test again.'.format(txid))
103 assert int(txid, 16) in [inv.hash for inv in inbound_peer.last_message['inv'].inv]
104
105
106 if __name__ == '__main__':
107 P2PLeakTxTest(__file__).main()
108