Skip to content

Commit

Permalink
Merge #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 #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 committed Nov 8, 2017
2 parents 0a2f46b + 32ae82f commit f7388e9
Show file tree
Hide file tree
Showing 16 changed files with 212 additions and 269 deletions.
46 changes: 19 additions & 27 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)
from test_framework.util import assert_equal

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

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]))
except IOError as e:
assert str(e) == 'Not connected, no pushbuf'
break
Expand All @@ -97,13 +96,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 @@ -165,37 +161,33 @@ 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=["-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=["-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()

# send header lists to all three nodes
node0.send_header_for_blocks(self.blocks[0:2000])
node0.send_header_for_blocks(self.blocks[2000:])
node1.send_header_for_blocks(self.blocks[0:2000])
node1.send_header_for_blocks(self.blocks[2000:])
node2.send_header_for_blocks(self.blocks[0:200])
p2p0.send_header_for_blocks(self.blocks[0:2000])
p2p0.send_header_for_blocks(self.blocks[2000:])
p2p1.send_header_for_blocks(self.blocks[0:2000])
p2p1.send_header_for_blocks(self.blocks[2000:])
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 all blocks to node1. All blocks will be accepted.
for i in range(2202):
node1.send_message(msg_block(self.blocks[i]))
p2p1.send_message(msg_block(self.blocks[i]))
# Syncing 2200 blocks can take a while on slow systems. Give it plenty of time to sync.
node1.sync_with_ping(120)
p2p1.sync_with_ping(120)
assert_equal(self.nodes[1].getblock(self.nodes[1].getbestblockhash())['height'], 2202)

# 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
39 changes: 18 additions & 21 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 @@ -125,26 +122,26 @@ def run_test(self):
# First we show that this tx is valid except for CLTV by getting it
# accepted to the mempool (which we can achieve with
# -promiscuousmempoolflags).
node0.send_and_ping(msg_tx(spendtx))
self.nodes[0].p2p.send_and_ping(msg_tx(spendtx))
assert spendtx.hash in self.nodes[0].getrawmempool()

# Now we verify that a block with this transaction is invalid.
block.vtx.append(spendtx)
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 @@ -155,7 +152,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
40 changes: 19 additions & 21 deletions test/functional/bipdersig-p2p.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,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 @@ -83,7 +81,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 @@ -93,15 +91,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 @@ -114,7 +112,7 @@ def run_test(self):
# First we show that this tx is valid except for DERSIG by getting it
# accepted to the mempool (which we can achieve with
# -promiscuousmempoolflags).
node0.send_and_ping(msg_tx(spendtx))
self.nodes[0].p2p.send_and_ping(msg_tx(spendtx))
assert spendtx.hash in self.nodes[0].getrawmempool()

# Now we verify that a block with this transaction is invalid.
Expand All @@ -123,23 +121,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 @@ -148,7 +146,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 propagated 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

0 comments on commit f7388e9

Please sign in to comment.