Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dandelion support #588

Merged
merged 23 commits into from
Feb 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion qa/pull-tester/run-navcoind-for-test.sh.in
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ touch "$DATADIR/devnet/debug.log"
tail -q -n 1 -F "$DATADIR/devnet/debug.log" | grep -m 1 -q "Done loading" &
WAITER=$!
PORT=`expr 10000 + $$ % 55536`
"@abs_top_builddir@/src/navcoind@EXEEXT@" -connect=0.0.0.0 -datadir="$DATADIR" -rpcuser=user -rpcpassword=pass -listen -keypool=3 -debug -debug=net -logtimestamps -checkmempool=0 -relaypriority=0 -port=$PORT -whitelist=127.0.0.1 -devnet -rpcport=`expr $PORT + 1` &
"@abs_top_builddir@/src/navcoind@EXEEXT@" -connect=0.0.0.0 -datadir="$DATADIR" -rpcuser=user -rpcpassword=pass -listen -keypool=3 -debug -debug=net -logtimestamps -checkmempool=0 -relaypriority=0 -port=$PORT -whitelist=127.0.0.1 -devnet -dandelion=0 -rpcport=`expr $PORT + 1` &
NAVCOIND=$!

#Install a watchdog.
Expand Down
196 changes: 196 additions & 0 deletions qa/rpc-tests/p2p-dandelion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
#!/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 collections import defaultdict
from test_framework.mininode import * # NodeConnCB
from test_framework.test_framework import NavCoinTestFramework # NavCoinTestFramework
from test_framework.util import * # other stuff
import time # sleep

class TestP2PConn(NodeConnCB):
def __init__(self):
NodeConnCB.__init__(self)
self.connection = None

# Track number of messages of each type received and the most recent
# message of each type
self.message_count = defaultdict(int)
self.last_message = {}

self.ping_counter = 1
self.last_pong = msg_pong()

def add_connection(self, conn):
self.connection = conn
self.peer_disconnected = False

# Track the last getdata message we receive (used in the test)
def on_getdata(self, conn, message):
self.last_getdata = message

# Spin until verack message is received from the node.
# We use this to signal that our test can begin. This
# is called from the testing thread, so it needs to acquire
# the global lock.
def wait_for_verack(self):
def veracked():
return self.verack_received
return wait_until(veracked, timeout=10)

def wait_for_disconnect(self):
def disconnected():
return self.peer_disconnected
return wait_until(disconnected, timeout=10)

# Wrapper for the NodeConn's send_message function
def send_message(self, message):
self.connection.send_message(message)

def on_pong(self, conn, message):
self.last_pong = message

def on_close(self, conn):
self.peer_disconnected = True

# Sync up with the node after delivery of a block
def sync_with_ping(self, timeout=30):
def received_pong():
return (self.last_pong.nonce == self.ping_counter)
self.connection.send_message(msg_ping(nonce=self.ping_counter))
success = wait_until(received_pong, timeout=timeout)
self.ping_counter += 1
return success

def send_dandeliontx_getdata(self, dandeliontx_hash):
msg = msg_getdata([CInv(5,dandeliontx_hash)]) # 5: "DandelionTx"
print("Dandelion hash is ", dandeliontx_hash )
self.connection.send_message(msg)


class DandelionTest(NavCoinTestFramework):
def set_test_params(self):
self.setup_clean_chain = True
self.num_nodes = 3

def setup_nodes(self):
print("setup_nodes");
return start_nodes(
self.num_nodes, self.options.tmpdir,
extra_args=[['-whitelist=127.0.0.1', '-staking=0', '-dandelion=1']] * self.num_nodes)

def setup_network(self):
print("Setting up network for dandelion.")
self.nodes = self.setup_nodes()
# Tests 1,2,3: 0 --> 1 --> 2 --> 0
connect_nodes(self.nodes[0],1)
connect_nodes(self.nodes[1],2)
connect_nodes(self.nodes[2],0)
self.is_network_split = True
self.sync_all()
print("Finished setting up network for dandelion.")

def run_test(self):
# Convenience variables
node0 = self.nodes[0]
node1 = self.nodes[1]
node2 = self.nodes[2]

# Setup TestP2PConns
test_node0 = TestP2PConn()

connection = NodeConn('127.0.0.1', p2p_port(0), node0, test_node0)
test_node0.add_connection(connection)

# Start networking thread
NetworkThread().start()
test_node0.wait_for_verack()
print("Dandelion test: verack " + ("received." if test_node0.verack_received else "failed."))

# Get out of Initial Block Download (IBD)
for node in self.nodes:
node.generate(1)
# Generate funds for node0
node0.generate(101)

# 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.
print('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 = FromHex(CTransaction(),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
print('Success: resistance to active probing')
except AssertionError:
if not test_1_passed and tries_left == 0:
print('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
print('Success: loop behavior')
except AssertionError:
if not test_2_passed and tries_left == 0:
print('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
print('Success: resistance to black holes')
except AssertionError:
if not test_3_passed and tries_left == 0:
print('Failed: resistance to black holes')

print("Running dandelion test 7")
all_tests_passed = test_1_passed and test_2_passed and test_3_passed
assert(all_tests_passed)

if __name__ == '__main__':
DandelionTest().main()
Loading