149 lines
5.7 KiB
Python
149 lines
5.7 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
# Copyright (c) 2018 Bradley Denby
|
||
|
|
# Distributed under the MIT software license. See the accompanying file COPYING
|
||
|
|
# or http://www.opensource.org/licenses/mit-license.php.
|
||
|
|
|
||
|
|
"""Test transaction behaviors under the Dandelion spreading policy
|
||
|
|
|
||
|
|
Tests:
|
||
|
|
1. Resistance to active probing:
|
||
|
|
Stem: 0 --> 1 --> 2 --> 0 where each node has argument "-dandelion=1"
|
||
|
|
Probe: TestNode --> 0
|
||
|
|
Node 0 generates a Dandelion transaction "tx": 1.0 BTC from Node 0 to Node 2
|
||
|
|
TestNode immediately sends getdata for tx to Node 0
|
||
|
|
Assert that Node 0 does not reply with tx
|
||
|
|
|
||
|
|
2. Loop behavior:
|
||
|
|
Stem: 0 --> 1 --> 2 --> 0 where each node has argument "-dandelion=1"
|
||
|
|
Probe: TestNode --> 0
|
||
|
|
Wait ~5 seconds after Test 1, then TestNode sends getdata for tx to Node 0
|
||
|
|
Assert that Node 0 does not reply with tx
|
||
|
|
|
||
|
|
3. Resistance to black holes:
|
||
|
|
Stem: 0 --> 1 --> 2 --> 0 where each node has argument "-dandelion=1"
|
||
|
|
Probe: TestNode --> 0
|
||
|
|
Wait ~45 seconds after Test 2, then TestNode sends getdata for tx to Node 0
|
||
|
|
Assert that Node 0 replies with tx
|
||
|
|
"""
|
||
|
|
|
||
|
|
from test_framework.p2p import P2PInterface
|
||
|
|
from test_framework.test_framework import DigiByteTestFramework
|
||
|
|
from test_framework.blocktools import COINBASE_MATURITY_2
|
||
|
|
from test_framework.messages import (
|
||
|
|
CTransaction,
|
||
|
|
tx_from_hex,
|
||
|
|
msg_getdata,
|
||
|
|
CInv,
|
||
|
|
)
|
||
|
|
|
||
|
|
import time # sleep
|
||
|
|
|
||
|
|
class TestP2PConn(P2PInterface):
|
||
|
|
def __init__(self):
|
||
|
|
super().__init__()
|
||
|
|
|
||
|
|
def send_dandeliontx_getdata(self, dandeliontx_hash):
|
||
|
|
msg = msg_getdata()
|
||
|
|
msg.inv.append(CInv(5, dandeliontx_hash)) # 5: "DandelionTx"
|
||
|
|
self.send_message(msg)
|
||
|
|
|
||
|
|
class DandelionTest(DigiByteTestFramework):
|
||
|
|
def set_test_params(self):
|
||
|
|
self.setup_clean_chain = True
|
||
|
|
self.num_nodes = 3
|
||
|
|
self.extra_args = []
|
||
|
|
for i in range(self.num_nodes):
|
||
|
|
self.extra_args.append(["-dandelion=1"]) # ,"-debug=dandelion","-printtoconsole=1"
|
||
|
|
|
||
|
|
def setup_network(self):
|
||
|
|
self.setup_nodes()
|
||
|
|
# Tests 1,2,3: 0 --> 1 --> 2 --> 0
|
||
|
|
self.connect_nodes(0, 1)
|
||
|
|
self.connect_nodes(1, 2)
|
||
|
|
self.connect_nodes(2, 0)
|
||
|
|
|
||
|
|
self.nodes[0].createwallet(wallet_name='w0', load_on_startup=True)
|
||
|
|
self.nodes[1].createwallet(wallet_name='w1', load_on_startup=True)
|
||
|
|
self.nodes[2].createwallet(wallet_name='w2', load_on_startup=True)
|
||
|
|
|
||
|
|
def run_test(self):
|
||
|
|
# Convenience variables
|
||
|
|
node0, node1, node2 = self.nodes
|
||
|
|
w0 = node0.get_wallet_rpc('w0')
|
||
|
|
w1 = node1.get_wallet_rpc('w1')
|
||
|
|
w2 = node2.get_wallet_rpc('w2')
|
||
|
|
|
||
|
|
# Setup TestP2PConns
|
||
|
|
test_node0 = node0.add_p2p_connection(TestP2PConn())
|
||
|
|
|
||
|
|
# Start networking thread
|
||
|
|
test_node0.wait_for_verack()
|
||
|
|
|
||
|
|
# Get out of Initial Block Download (IBD)
|
||
|
|
for node in self.nodes:
|
||
|
|
self.generatetoaddress(node, 1, w0.getnewaddress(), sync_fun=self.no_op)
|
||
|
|
|
||
|
|
# Generate mature funds for node0 wallet
|
||
|
|
self.generatetoaddress(node0, COINBASE_MATURITY_2 + 1, w0.getnewaddress())
|
||
|
|
|
||
|
|
# Tests 1,2,3
|
||
|
|
# There is a low probability that one of these tests will fail even if
|
||
|
|
# the implementation is correct. Thus, these tests are repeated upon
|
||
|
|
# failure. A true bug will result in repeated failures.
|
||
|
|
self.log.info('Starting tests...')
|
||
|
|
test_1_passed = False
|
||
|
|
test_2_passed = False
|
||
|
|
test_3_passed = False
|
||
|
|
tries_left = 5
|
||
|
|
while(not (test_1_passed and test_2_passed and test_3_passed) and tries_left > 0):
|
||
|
|
tries_left -= 1
|
||
|
|
# Test 1: Resistance to active probing
|
||
|
|
test_node0.message_count['notfound'] = 0
|
||
|
|
node0_txid = node0.sendtoaddress(node2.getnewaddress(), 1.0)
|
||
|
|
node0_tx = tx_from_hex(node0.gettransaction(node0_txid)['hex'])
|
||
|
|
test_node0.send_dandeliontx_getdata(node0_tx.calc_sha256(True))
|
||
|
|
time.sleep(1)
|
||
|
|
|
||
|
|
try:
|
||
|
|
assert(test_node0.message_count['notfound']==1)
|
||
|
|
if not test_1_passed:
|
||
|
|
test_1_passed = True
|
||
|
|
self.log.info('Success: resistance to active probing')
|
||
|
|
except AssertionError:
|
||
|
|
if not test_1_passed and tries_left == 0:
|
||
|
|
self.log.info('Failed: resistance to active probing')
|
||
|
|
|
||
|
|
# Test 2: Loop behavior
|
||
|
|
test_node0.message_count['notfound'] = 0
|
||
|
|
time.sleep(3)
|
||
|
|
test_node0.send_dandeliontx_getdata(node0_tx.calc_sha256(True))
|
||
|
|
time.sleep(1)
|
||
|
|
|
||
|
|
try:
|
||
|
|
assert(test_node0.message_count['notfound']==1)
|
||
|
|
if not test_2_passed:
|
||
|
|
test_2_passed = True
|
||
|
|
self.log.info('Success: loop behavior')
|
||
|
|
except AssertionError:
|
||
|
|
if not test_2_passed and tries_left == 0:
|
||
|
|
self.log.info('Failed: loop behavior')
|
||
|
|
|
||
|
|
# Test 3: Resistance to black holes
|
||
|
|
test_node0.message_count['tx'] = 0
|
||
|
|
time.sleep(44)
|
||
|
|
test_node0.send_dandeliontx_getdata(node0_tx.calc_sha256(True))
|
||
|
|
time.sleep(1)
|
||
|
|
try:
|
||
|
|
assert(test_node0.message_count['tx']==1)
|
||
|
|
if not test_3_passed:
|
||
|
|
test_3_passed = True
|
||
|
|
self.log.info('Success: resistance to black holes')
|
||
|
|
except AssertionError:
|
||
|
|
if not test_3_passed and tries_left == 0:
|
||
|
|
self.log.info('Failed: resistance to black holes')
|
||
|
|
|
||
|
|
all_tests_passed = test_1_passed and test_2_passed and test_3_passed
|
||
|
|
assert(all_tests_passed)
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
DandelionTest().main()
|