Skip to content

Commit 52e15aa

Browse files
committed
Adds helper functions to NodeConnCB
This commit adds some helper functions to NodeConnCB which are useful for many tests: - NodeConnCB now keeps track of the number of each message type that it's received and the most recent message of each type. Many tests assert on the most recent block, tx or reject message. - NodeConnCB now keeps track of its connection state by setting a connected boolean in on_open() and on_close() - NodeConnCB now has wait_for_block, wait_for_getdata, wait_for_getheaders, wait_for_inv and wait_for_verack methods I have updated the individual test cases to make sure that there are no namespace problems that cause them to fail with these new definitions. Future commits will remove the duplicate code.
1 parent 2584925 commit 52e15aa

File tree

6 files changed

+107
-78
lines changed

6 files changed

+107
-78
lines changed

test/functional/maxuploadtarget.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,6 @@ def on_block(self, conn, message):
4444
except KeyError as e:
4545
self.block_receive_map[message.block.sha256] = 1
4646

47-
# Spin until verack message is received from the node.
48-
# We use this to signal that our test can begin. This
49-
# is called from the testing thread, so it needs to acquire
50-
# the global lock.
51-
def wait_for_verack(self):
52-
def veracked():
53-
return self.verack_received
54-
return wait_until(veracked, timeout=10)
55-
5647
def wait_for_disconnect(self):
5748
def disconnected():
5849
return self.peer_disconnected

test/functional/p2p-acceptblock.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,6 @@ def add_connection(self, conn):
7070
def on_getdata(self, conn, message):
7171
self.last_getdata = message
7272

73-
# Spin until verack message is received from the node.
74-
# We use this to signal that our test can begin. This
75-
# is called from the testing thread, so it needs to acquire
76-
# the global lock.
77-
def wait_for_verack(self):
78-
while True:
79-
with mininode_lock:
80-
if self.verack_received:
81-
return
82-
time.sleep(0.05)
83-
8473
# Wrapper for the NodeConn's send_message function
8574
def send_message(self, message):
8675
self.connection.send_message(message)

test/functional/p2p-leaktests.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def __init__(self):
2323
self.connection = None
2424
self.unexpected_msg = False
2525
self.connected = False
26+
self.ever_connected = False
2627

2728
def add_connection(self, conn):
2829
self.connection = conn
@@ -36,6 +37,7 @@ def bad_message(self, message):
3637

3738
def on_open(self, conn):
3839
self.connected = True
40+
self.ever_connected = True
3941

4042
def on_version(self, conn, message): self.bad_message(message)
4143
def on_verack(self, conn, message): self.bad_message(message)
@@ -121,7 +123,9 @@ def run_test(self):
121123

122124
NetworkThread().start() # Start up network handling in another thread
123125

124-
assert(wait_until(lambda: no_version_bannode.connected and no_version_idlenode.connected and no_verack_idlenode.version_received, timeout=10))
126+
assert wait_until(lambda: no_version_bannode.ever_connected, timeout=10)
127+
assert wait_until(lambda: no_version_idlenode.ever_connected, timeout=10)
128+
assert wait_until(lambda: no_verack_idlenode.version_received, timeout=10)
125129

126130
# Mine a block and make sure that it's not sent to the connected nodes
127131
self.nodes[0].generate(1)
@@ -130,7 +134,7 @@ def run_test(self):
130134
time.sleep(5)
131135

132136
#This node should have been banned
133-
assert(no_version_bannode.connection.state == "closed")
137+
assert not no_version_bannode.connected
134138

135139
[conn.disconnect_node() for conn in connections]
136140

test/functional/p2p-mempool.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,6 @@ def on_block(self, conn, message):
3838
except KeyError as e:
3939
self.block_receive_map[message.block.sha256] = 1
4040

41-
# Spin until verack message is received from the node.
42-
# We use this to signal that our test can begin. This
43-
# is called from the testing thread, so it needs to acquire
44-
# the global lock.
45-
def wait_for_verack(self):
46-
def veracked():
47-
return self.verack_received
48-
return wait_until(veracked, timeout=10)
49-
5041
def wait_for_disconnect(self):
5142
def disconnected():
5243
return self.peer_disconnected

test/functional/test_framework/comptool.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,9 +192,7 @@ def disconnected():
192192
return wait_until(disconnected, timeout=10)
193193

194194
def wait_for_verack(self):
195-
def veracked():
196-
return all(node.verack_received for node in self.test_nodes)
197-
return wait_until(veracked, timeout=10)
195+
[node.wait_for_verack() for node in self.test_nodes]
198196

199197
def wait_for_pings(self, counter):
200198
def received_pongs():

test/functional/test_framework/mininode.py

Lines changed: 100 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,22 @@
2020
ser_*, deser_*: functions that handle serialization/deserialization
2121
"""
2222

23-
import struct
24-
import socket
2523
import asyncore
26-
import time
27-
import sys
28-
import random
29-
from .util import hex_str_to_bytes, bytes_to_hex_str
30-
from io import BytesIO
3124
from codecs import encode
25+
from collections import defaultdict
26+
import copy
3227
import hashlib
33-
from threading import RLock
34-
from threading import Thread
28+
from io import BytesIO
3529
import logging
36-
import copy
30+
import random
31+
import socket
32+
import struct
33+
import sys
34+
import time
35+
from threading import RLock, Thread
36+
3737
from test_framework.siphash import siphash256
38+
from test_framework.util import hex_str_to_bytes, bytes_to_hex_str
3839

3940
BIP0031_VERSION = 60000
4041
MY_VERSION = 70014 # past bip-31 for ping/pong
@@ -1465,30 +1466,57 @@ def serialize(self):
14651466
r += self.block_transactions.serialize(with_witness=True)
14661467
return r
14671468

1468-
# This is what a callback should look like for NodeConn
1469-
# Reimplement the on_* functions to provide handling for events
14701469
class NodeConnCB(object):
1470+
"""Callback and helper functions for P2P connection to a bitcoind node.
1471+
1472+
Individual testcases should subclass this and override the on_* methods
1473+
if they want to alter message handling behaviour.
1474+
"""
1475+
14711476
def __init__(self):
1472-
self.verack_received = False
1477+
# Track whether we have a P2P connection open to the node
1478+
self.connected = False
1479+
self.connection = None
1480+
1481+
# Track number of messages of each type received and the most recent
1482+
# message of each type
1483+
self.message_count = defaultdict(int)
1484+
self.last_message = {}
1485+
1486+
# A count of the number of ping messages we've sent to the node
1487+
self.ping_counter = 1
1488+
14731489
# deliver_sleep_time is helpful for debugging race conditions in p2p
14741490
# tests; it causes message delivery to sleep for the specified time
14751491
# before acquiring the global lock and delivering the next message.
14761492
self.deliver_sleep_time = None
1493+
14771494
# Remember the services our peer has advertised
14781495
self.peer_services = None
1479-
self.connection = None
1480-
self.ping_counter = 1
1481-
self.last_pong = msg_pong()
1496+
1497+
# Message receiving methods
14821498

14831499
def deliver(self, conn, message):
1500+
"""Receive message and dispatch message to appropriate callback.
1501+
1502+
We keep a count of how many of each message type has been received
1503+
and the most recent message of each type.
1504+
1505+
Optionally waits for deliver_sleep_time before dispatching message.
1506+
"""
1507+
14841508
deliver_sleep = self.get_deliver_sleep_time()
14851509
if deliver_sleep is not None:
14861510
time.sleep(deliver_sleep)
14871511
with mininode_lock:
14881512
try:
1489-
getattr(self, 'on_' + message.command.decode('ascii'))(conn, message)
1513+
command = message.command.decode('ascii')
1514+
self.message_count[command] += 1
1515+
self.last_message[command] = message
1516+
getattr(self, 'on_' + command)(conn, message)
14901517
except:
1491-
logger.exception("ERROR delivering %s" % repr(message))
1518+
print("ERROR delivering %s (%s)" % (repr(message),
1519+
sys.exc_info()[0]))
14921520

14931521
def set_deliver_sleep_time(self, value):
14941522
with mininode_lock:
@@ -1498,14 +1526,20 @@ def get_deliver_sleep_time(self):
14981526
with mininode_lock:
14991527
return self.deliver_sleep_time
15001528

1501-
# Callbacks which can be overridden by subclasses
1502-
#################################################
1529+
# Callback methods. Can be overridden by subclasses in individual test
1530+
# cases to provide custom message handling behaviour.
1531+
1532+
def on_open(self, conn):
1533+
self.connected = True
1534+
1535+
def on_close(self, conn):
1536+
self.connected = False
1537+
self.connection = None
15031538

15041539
def on_addr(self, conn, message): pass
15051540
def on_alert(self, conn, message): pass
15061541
def on_block(self, conn, message): pass
15071542
def on_blocktxn(self, conn, message): pass
1508-
def on_close(self, conn): pass
15091543
def on_cmpctblock(self, conn, message): pass
15101544
def on_feefilter(self, conn, message): pass
15111545
def on_getaddr(self, conn, message): pass
@@ -1515,7 +1549,7 @@ def on_getdata(self, conn, message): pass
15151549
def on_getheaders(self, conn, message): pass
15161550
def on_headers(self, conn, message): pass
15171551
def on_mempool(self, conn): pass
1518-
def on_open(self, conn): pass
1552+
def on_pong(self, conn, message): pass
15191553
def on_reject(self, conn, message): pass
15201554
def on_sendcmpct(self, conn, message): pass
15211555
def on_sendheaders(self, conn, message): pass
@@ -1533,9 +1567,6 @@ def on_ping(self, conn, message):
15331567
if conn.ver_send > BIP0031_VERSION:
15341568
conn.send_message(msg_pong(message.nonce))
15351569

1536-
def on_pong(self, conn, message):
1537-
self.last_pong = message
1538-
15391570
def on_verack(self, conn, message):
15401571
conn.ver_recv = conn.ver_send
15411572
self.verack_received = True
@@ -1548,44 +1579,69 @@ def on_version(self, conn, message):
15481579
conn.ver_recv = conn.ver_send
15491580
conn.nServices = message.nServices
15501581

1551-
# Helper functions
1552-
##################
1582+
# Connection helper methods
15531583

15541584
def add_connection(self, conn):
15551585
self.connection = conn
15561586

1557-
# Wrapper for the NodeConn's send_message function
1587+
def wait_for_disconnect(self, timeout=60):
1588+
test_function = lambda: not self.connected
1589+
assert wait_until(test_function, timeout=timeout)
1590+
1591+
# Message receiving helper methods
1592+
1593+
def sync(self, test_function, timeout=60):
1594+
while timeout > 0:
1595+
with mininode_lock:
1596+
if test_function():
1597+
return
1598+
time.sleep(0.05)
1599+
timeout -= 0.05
1600+
raise AssertionError("Sync failed to complete")
1601+
1602+
def wait_for_block(self, blockhash, timeout=60):
1603+
test_function = lambda: self.last_message.get("block") and self.last_message["block"].block.rehash() == blockhash
1604+
self.sync(test_function, timeout)
1605+
1606+
def wait_for_getdata(self, timeout=60):
1607+
test_function = lambda: self.last_message.get("getdata")
1608+
self.sync(test_function, timeout)
1609+
1610+
def wait_for_getheaders(self, timeout=60):
1611+
test_function = lambda: self.last_message.get("getheaders")
1612+
self.sync(test_function, timeout)
1613+
1614+
def wait_for_inv(self, expected_inv, timeout=60):
1615+
test_function = lambda: self.last_message.get("inv") and self.last_message["inv"] != expected_inv
1616+
self.sync(test_function, timeout)
1617+
1618+
def wait_for_verack(self, timeout=60):
1619+
test_function = lambda: self.message_count["verack"]
1620+
self.sync(test_function, timeout)
1621+
1622+
# Message sending helper functions
1623+
15581624
def send_message(self, message):
1559-
self.connection.send_message(message)
1625+
if self.connection:
1626+
self.connection.send_message(message)
1627+
else:
1628+
logger.error("Cannot send message. No connection to node!")
15601629

15611630
def send_and_ping(self, message):
15621631
self.send_message(message)
15631632
self.sync_with_ping()
15641633

15651634
# Sync up with the node
15661635
def sync_with_ping(self, timeout=60):
1567-
def received_pong():
1568-
return (self.last_pong.nonce == self.ping_counter)
15691636
self.send_message(msg_ping(nonce=self.ping_counter))
1570-
success = wait_until(received_pong, timeout=timeout)
1637+
test_function = lambda: self.last_message.get("pong") and self.last_message["pong"].nonce == self.ping_counter
1638+
success = wait_until(test_function, timeout = timeout)
15711639
if not success:
15721640
logger.error("sync_with_ping failed!")
15731641
raise AssertionError("sync_with_ping failed!")
15741642
self.ping_counter += 1
1575-
15761643
return success
15771644

1578-
# Spin until verack message is received from the node.
1579-
# Tests may want to use this as a signal that the test can begin.
1580-
# This can be called from the testing thread, so it needs to acquire the
1581-
# global lock.
1582-
def wait_for_verack(self):
1583-
while True:
1584-
with mininode_lock:
1585-
if self.verack_received:
1586-
return
1587-
time.sleep(0.05)
1588-
15891645
# The actual NodeConn class
15901646
# This class provides an interface for a p2p connection to a specified node
15911647
class NodeConn(asyncore.dispatcher):

0 commit comments

Comments
 (0)