Skip to content

Commit

Permalink
Merge bitcoin#11182: [tests] Add P2P interface to TestNode
Browse files Browse the repository at this point in the history
32ae82f [tests] use TestNode p2p connection in tests (John Newbery)
5e5725c [tests] Add p2p connection to TestNode (John Newbery)
b86c1cd [tests] fix TestNode.__getattr__() method (John Newbery)

Pull request description:

  Final two steps of bitcoin#10082 : Adding the "mininode" P2P interface to `TestNode`

  This PR adds the mininode P2P interface to `TestNode`. It simplifies the process for opening a P2P connection to the node-under-test from this:

  ```python
  node0 = NodeConnCB()
  connections = []
  connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0))
  node0.add_connection(connections[0])
  ```

  to this:

  ```python
  self.nodes[0].add_p2p_connection(p2p_conn_type=NodeConnCB)
  ```

  The first commit adds the infrastructure to `test_node.py`. The second updates the individual test cases to use it. Can be separated if this is too much review for one PR.

Tree-SHA512: 44f1a6320f44eefc70489ae8350c6a16ad1a6035e4b9b7bafbdf19f5905ed0e2db85beaaf4758eec3059dd89a375a47a45352a029f39f57a86ab38a9ae66650e
  • Loading branch information
MarcoFalke authored and codablock committed Oct 2, 2019
1 parent e06c116 commit ec1d2b6
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 229 deletions.
50 changes: 21 additions & 29 deletions test/functional/assumevalid.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,12 @@
CTxIn,
CTxOut,
NetworkThread,
NodeConn,
NodeConnCB,
msg_block,
msg_headers)
from test_framework.script import (CScript, OP_TRUE)
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (p2p_port, assert_equal, set_node_times)
from test_framework.util import (assert_equal, set_node_times)

class BaseNode(NodeConnCB):
def send_header_for_blocks(self, new_blocks):
Expand All @@ -66,13 +65,13 @@ def setup_network(self):
# signature so we can pass in the block hash as assumevalid.
self.start_node(0, extra_args=self.extra_args)

def send_blocks_until_disconnected(self, node):
def send_blocks_until_disconnected(self, p2p_conn):
"""Keep sending blocks to the node until we're disconnected."""
for i in range(len(self.blocks)):
if not node.connection:
if not p2p_conn.connection:
break
try:
node.send_message(msg_block(self.blocks[i]))
p2p_conn.send_message(msg_block(self.blocks[i]))
# TODO There is a race condition between send_message and on_close which causes an AttributError on Travis
# We can reenable the correct exception handling and the assert when Bitcoin 0.16 mininode.py changes have been
# backported
Expand Down Expand Up @@ -102,13 +101,10 @@ def assert_blockchain_height(self, node, height):
def run_test(self):

# Connect to node0
node0 = BaseNode()
connections = []
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0))
node0.add_connection(connections[0])
p2p0 = self.nodes[0].add_p2p_connection(BaseNode())

NetworkThread().start() # Start up network handling in another thread
node0.wait_for_verack()
self.nodes[0].p2p.wait_for_verack()

# Build the blockchain
self.tip = int(self.nodes[0].getbestblockhash(), 16)
Expand Down Expand Up @@ -170,16 +166,12 @@ def run_test(self):

# Start node1 and node2 with assumevalid so they accept a block with a bad signature.
self.start_node(1, extra_args=self.extra_args + ["-assumevalid=" + hex(block102.sha256)])
node1 = BaseNode() # connects to node1
connections.append(NodeConn('127.0.0.1', p2p_port(1), self.nodes[1], node1))
node1.add_connection(connections[1])
node1.wait_for_verack()
p2p1 = self.nodes[1].add_p2p_connection(BaseNode())
p2p1.wait_for_verack()

self.start_node(2, extra_args=self.extra_args + ["-assumevalid=" + hex(block102.sha256)])
node2 = BaseNode() # connects to node2
connections.append(NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], node2))
node2.add_connection(connections[2])
node2.wait_for_verack()
p2p2 = self.nodes[2].add_p2p_connection(BaseNode())
p2p2.wait_for_verack()

# Make sure nodes actually accept the many headers
self.mocktime = self.block_time
Expand All @@ -189,27 +181,27 @@ def run_test(self):
# node0 does not need to receive all headers
# node1 must receive all headers as otherwise assumevalid is ignored in ConnectBlock
# node2 should NOT receive all headers to force skipping of the assumevalid check in ConnectBlock
node0.send_header_for_blocks(self.blocks[0:2000])
node1.send_header_for_blocks(self.blocks[0:2000])
node1.send_header_for_blocks(self.blocks[2000:4000])
node1.send_header_for_blocks(self.blocks[4000:6000])
node1.send_header_for_blocks(self.blocks[6000:8000])
node1.send_header_for_blocks(self.blocks[8000:])
node2.send_header_for_blocks(self.blocks[0:200])
p2p0.send_header_for_blocks(self.blocks[0:2000])
p2p1.send_header_for_blocks(self.blocks[0:2000])
p2p1.send_header_for_blocks(self.blocks[2000:4000])
p2p1.send_header_for_blocks(self.blocks[4000:6000])
p2p1.send_header_for_blocks(self.blocks[6000:8000])
p2p1.send_header_for_blocks(self.blocks[8000:])
p2p2.send_header_for_blocks(self.blocks[0:200])

# Send blocks to node0. Block 102 will be rejected.
self.send_blocks_until_disconnected(node0)
self.send_blocks_until_disconnected(p2p0)
self.assert_blockchain_height(self.nodes[0], 101)

# Send 200 blocks to node1. All blocks, including block 102, will be accepted.
for i in range(200):
node1.send_message(msg_block(self.blocks[i]))
p2p1.send_message(msg_block(self.blocks[i]))
# Syncing so many blocks can take a while on slow systems. Give it plenty of time to sync.
node1.sync_with_ping(300)
p2p1.sync_with_ping(300)
assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 200)

# Send blocks to node2. Block 102 will be rejected.
self.send_blocks_until_disconnected(node2)
self.send_blocks_until_disconnected(p2p2)
self.assert_blockchain_height(self.nodes[2], 101)

if __name__ == '__main__':
Expand Down
37 changes: 17 additions & 20 deletions test/functional/bip65-cltv-p2p.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,15 +66,12 @@ def set_test_params(self):
self.setup_clean_chain = True

def run_test(self):
node0 = NodeConnCB()
connections = []
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0))
node0.add_connection(connections[0])
self.nodes[0].add_p2p_connection(NodeConnCB())

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

# wait_for_verack ensures that the P2P connection is fully up.
node0.wait_for_verack()
self.nodes[0].p2p.wait_for_verack()

self.log.info("Mining %d blocks", CLTV_HEIGHT - 2)
self.coinbase_blocks = self.nodes[0].generate(CLTV_HEIGHT - 2)
Expand All @@ -95,7 +92,7 @@ def run_test(self):
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()

node0.send_and_ping(msg_block(block))
self.nodes[0].p2p.send_and_ping(msg_block(block))
assert_equal(self.nodes[0].getbestblockhash(), block.hash)

self.log.info("Test that blocks must now be at least version 4")
Expand All @@ -104,15 +101,15 @@ def run_test(self):
block = create_block(tip, create_coinbase(CLTV_HEIGHT), block_time)
block.nVersion = 3
block.solve()
node0.send_and_ping(msg_block(block))
self.nodes[0].p2p.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)

wait_until(lambda: "reject" in node0.last_message.keys(), lock=mininode_lock)
wait_until(lambda: "reject" in self.nodes[0].p2p.last_message.keys(), lock=mininode_lock)
with mininode_lock:
assert_equal(node0.last_message["reject"].code, REJECT_OBSOLETE)
assert_equal(node0.last_message["reject"].reason, b'bad-version(0x00000003)')
assert_equal(node0.last_message["reject"].data, block.sha256)
del node0.last_message["reject"]
assert_equal(self.nodes[0].p2p.last_message["reject"].code, REJECT_OBSOLETE)
assert_equal(self.nodes[0].p2p.last_message["reject"].reason, b'bad-version(0x00000003)')
assert_equal(self.nodes[0].p2p.last_message["reject"].data, block.sha256)
del self.nodes[0].p2p.last_message["reject"]

self.log.info("Test that invalid-according-to-cltv transactions cannot appear in a block")
block.nVersion = 4
Expand All @@ -131,18 +128,18 @@ def run_test(self):
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()

node0.send_and_ping(msg_block(block))
self.nodes[0].p2p.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)

wait_until(lambda: "reject" in node0.last_message.keys(), lock=mininode_lock)
wait_until(lambda: "reject" in self.nodes[0].p2p.last_message.keys(), lock=mininode_lock)
with mininode_lock:
assert node0.last_message["reject"].code in [REJECT_INVALID, REJECT_NONSTANDARD]
assert_equal(node0.last_message["reject"].data, block.sha256)
if node0.last_message["reject"].code == REJECT_INVALID:
assert self.nodes[0].p2p.last_message["reject"].code in [REJECT_INVALID, REJECT_NONSTANDARD]
assert_equal(self.nodes[0].p2p.last_message["reject"].data, block.sha256)
if self.nodes[0].p2p.last_message["reject"].code == REJECT_INVALID:
# Generic rejection when a block is invalid
assert_equal(node0.last_message["reject"].reason, b'block-validation-failed')
assert_equal(self.nodes[0].p2p.last_message["reject"].reason, b'block-validation-failed')
else:
assert b'Negative locktime' in node0.last_message["reject"].reason
assert b'Negative locktime' in self.nodes[0].p2p.last_message["reject"].reason

self.log.info("Test that a version 4 block with a valid-according-to-CLTV transaction is accepted")
spendtx = cltv_validate(self.nodes[0], spendtx, CLTV_HEIGHT - 1)
Expand All @@ -153,7 +150,7 @@ def run_test(self):
block.hashMerkleRoot = block.calc_merkle_root()
block.solve()

node0.send_and_ping(msg_block(block))
self.nodes[0].p2p.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)


Expand Down
38 changes: 18 additions & 20 deletions test/functional/bipdersig-p2p.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,12 @@ def set_test_params(self):
self.setup_clean_chain = True

def run_test(self):
node0 = NodeConnCB()
connections = []
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0))
node0.add_connection(connections[0])
self.nodes[0].add_p2p_connection(NodeConnCB())

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

# wait_for_verack ensures that the P2P connection is fully up.
node0.wait_for_verack()
self.nodes[0].p2p.wait_for_verack()

self.log.info("Mining %d blocks", DERSIG_HEIGHT - 2)
self.coinbase_blocks = self.nodes[0].generate(DERSIG_HEIGHT - 2)
Expand All @@ -84,7 +82,7 @@ def run_test(self):
block.rehash()
block.solve()

node0.send_and_ping(msg_block(block))
self.nodes[0].p2p.send_and_ping(msg_block(block))
assert_equal(self.nodes[0].getbestblockhash(), block.hash)

self.log.info("Test that blocks must now be at least version 3")
Expand All @@ -94,15 +92,15 @@ def run_test(self):
block.nVersion = 2
block.rehash()
block.solve()
node0.send_and_ping(msg_block(block))
self.nodes[0].p2p.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)

wait_until(lambda: "reject" in node0.last_message.keys(), lock=mininode_lock)
wait_until(lambda: "reject" in self.nodes[0].p2p.last_message.keys(), lock=mininode_lock)
with mininode_lock:
assert_equal(node0.last_message["reject"].code, REJECT_OBSOLETE)
assert_equal(node0.last_message["reject"].reason, b'bad-version(0x00000002)')
assert_equal(node0.last_message["reject"].data, block.sha256)
del node0.last_message["reject"]
assert_equal(self.nodes[0].p2p.last_message["reject"].code, REJECT_OBSOLETE)
assert_equal(self.nodes[0].p2p.last_message["reject"].reason, b'bad-version(0x00000002)')
assert_equal(self.nodes[0].p2p.last_message["reject"].data, block.sha256)
del self.nodes[0].p2p.last_message["reject"]

self.log.info("Test that transactions with non-DER signatures cannot appear in a block")
block.nVersion = 3
Expand All @@ -122,23 +120,23 @@ def run_test(self):
block.rehash()
block.solve()

node0.send_and_ping(msg_block(block))
self.nodes[0].p2p.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), tip)

wait_until(lambda: "reject" in node0.last_message.keys(), lock=mininode_lock)
wait_until(lambda: "reject" in self.nodes[0].p2p.last_message.keys(), lock=mininode_lock)
with mininode_lock:
# We can receive different reject messages depending on whether
# bitcoind is running with multiple script check threads. If script
# check threads are not in use, then transaction script validation
# happens sequentially, and bitcoind produces more specific reject
# reasons.
assert node0.last_message["reject"].code in [REJECT_INVALID, REJECT_NONSTANDARD]
assert_equal(node0.last_message["reject"].data, block.sha256)
if node0.last_message["reject"].code == REJECT_INVALID:
assert self.nodes[0].p2p.last_message["reject"].code in [REJECT_INVALID, REJECT_NONSTANDARD]
assert_equal(self.nodes[0].p2p.last_message["reject"].data, block.sha256)
if self.nodes[0].p2p.last_message["reject"].code == REJECT_INVALID:
# Generic rejection when a block is invalid
assert_equal(node0.last_message["reject"].reason, b'block-validation-failed')
assert_equal(self.nodes[0].p2p.last_message["reject"].reason, b'block-validation-failed')
else:
assert b'Non-canonical DER signature' in node0.last_message["reject"].reason
assert b'Non-canonical DER signature' in self.nodes[0].p2p.last_message["reject"].reason

self.log.info("Test that a version 3 block with a DERSIG-compliant transaction is accepted")
block.vtx[1] = create_transaction(self.nodes[0],
Expand All @@ -147,7 +145,7 @@ def run_test(self):
block.rehash()
block.solve()

node0.send_and_ping(msg_block(block))
self.nodes[0].p2p.send_and_ping(msg_block(block))
assert_equal(int(self.nodes[0].getbestblockhash(), 16), block.sha256)

if __name__ == '__main__':
Expand Down
23 changes: 8 additions & 15 deletions test/functional/example_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
from test_framework.mininode import (
CInv,
NetworkThread,
NodeConn,
NodeConnCB,
mininode_lock,
msg_block,
Expand All @@ -28,7 +27,6 @@
from test_framework.util import (
assert_equal,
connect_nodes,
p2p_port,
wait_until,
)

Expand Down Expand Up @@ -134,16 +132,13 @@ def run_test(self):
"""Main test logic"""

# Create a P2P connection to one of the nodes
node0 = BaseNode()
connections = []
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node0))
node0.add_connection(connections[0])
self.nodes[0].add_p2p_connection(BaseNode())

# Start up network handling in another thread. This needs to be called
# after the P2P connections have been created.
NetworkThread().start()
# wait_for_verack ensures that the P2P connection is fully up.
node0.wait_for_verack()
self.nodes[0].p2p.wait_for_verack()

# Generating a block on one of the nodes will get us out of IBD
blocks = [int(self.nodes[0].generate(nblocks=1)[0], 16)]
Expand Down Expand Up @@ -180,7 +175,7 @@ def run_test(self):
block.solve()
block_message = msg_block(block)
# Send message is used to send a P2P message to the node over our NodeConn connection
node0.send_message(block_message)
self.nodes[0].p2p.send_message(block_message)
self.tip = block.sha256
blocks.append(self.tip)
self.block_time += 1
Expand All @@ -193,28 +188,26 @@ def run_test(self):
connect_nodes(self.nodes[1], 2)

self.log.info("Add P2P connection to node2")
node2 = BaseNode()
connections.append(NodeConn('127.0.0.1', p2p_port(2), self.nodes[2], node2))
node2.add_connection(connections[1])
node2.wait_for_verack()
self.nodes[2].add_p2p_connection(BaseNode())
self.nodes[2].p2p.wait_for_verack()

self.log.info("Wait for node2 reach current tip. Test that it has propogated all the blocks to us")

getdata_request = msg_getdata()
for block in blocks:
getdata_request.inv.append(CInv(2, block))
node2.send_message(getdata_request)
self.nodes[2].p2p.send_message(getdata_request)

# wait_until() will loop until a predicate condition is met. Use it to test properties of the
# NodeConnCB objects.
wait_until(lambda: sorted(blocks) == sorted(list(node2.block_receive_map.keys())), timeout=5, lock=mininode_lock)
wait_until(lambda: sorted(blocks) == sorted(list(self.nodes[2].p2p.block_receive_map.keys())), timeout=5, lock=mininode_lock)

self.log.info("Check that each block was received only once")
# The network thread uses a global lock on data access to the NodeConn objects when sending and receiving
# messages. The test thread should acquire the global lock before accessing any NodeConn data to avoid locking
# and synchronization issues. Note wait_until() acquires this global lock when testing the predicate.
with mininode_lock:
for block in node2.block_receive_map.values():
for block in self.nodes[2].p2p.block_receive_map.values():
assert_equal(block, 1)

if __name__ == '__main__':
Expand Down
Loading

0 comments on commit ec1d2b6

Please sign in to comment.