Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-p2p-compactblocks.py
Show All 15 Lines | |||||
import time | import time | ||||
from test_framework.blocktools import ( | from test_framework.blocktools import ( | ||||
create_block, | create_block, | ||||
create_coinbase, | create_coinbase, | ||||
create_transaction, | create_transaction, | ||||
make_conform_to_ctor, | make_conform_to_ctor, | ||||
) | ) | ||||
from test_framework.comptool import TestInstance, TestManager | |||||
from test_framework.cdefs import ONE_MEGABYTE | from test_framework.cdefs import ONE_MEGABYTE | ||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
COutPoint, | COutPoint, | ||||
CTransaction, | CTransaction, | ||||
CTxIn, | CTxIn, | ||||
CTxOut, | CTxOut, | ||||
HeaderAndShortIDs, | HeaderAndShortIDs, | ||||
msg_cmpctblock, | msg_cmpctblock, | ||||
msg_sendcmpct, | msg_sendcmpct, | ||||
ser_compact_size, | ser_compact_size, | ||||
) | ) | ||||
from test_framework.mininode import ( | from test_framework.mininode import ( | ||||
mininode_lock, | mininode_lock, | ||||
network_thread_start, | network_thread_start, | ||||
network_thread_join, | P2PDataStore, | ||||
P2PInterface, | P2PInterface, | ||||
) | ) | ||||
from test_framework.script import CScript, OP_RETURN, OP_TRUE | from test_framework.script import CScript, OP_RETURN, OP_TRUE | ||||
from test_framework.test_framework import ComparisonTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.txtools import pad_tx | from test_framework.txtools import pad_tx | ||||
from test_framework.util import assert_equal, wait_until | from test_framework.util import assert_equal, wait_until | ||||
class PreviousSpendableOutput(): | class PreviousSpendableOutput(): | ||||
def __init__(self, tx=CTransaction(), n=-1): | def __init__(self, tx=CTransaction(), n=-1): | ||||
self.tx = tx | self.tx = tx | ||||
self.n = n # the output we're spending | # the output we're spending | ||||
self.n = n | |||||
# TestP2PConn: A peer we use to send messages to bitcoind, and store responses. | # TestP2PConn: A peer we use to send messages to bitcoind, and store responses. | ||||
class TestP2PConn(P2PInterface): | class TestP2PConn(P2PInterface): | ||||
def __init__(self): | def __init__(self): | ||||
self.last_sendcmpct = None | self.last_sendcmpct = None | ||||
self.last_cmpctblock = None | self.last_cmpctblock = None | ||||
Show All 17 Lines | def on_headers(self, message): | ||||
x.calc_sha256() | x.calc_sha256() | ||||
def clear_block_data(self): | def clear_block_data(self): | ||||
with mininode_lock: | with mininode_lock: | ||||
self.last_sendcmpct = None | self.last_sendcmpct = None | ||||
self.last_cmpctblock = None | self.last_cmpctblock = None | ||||
class FullBlockTest(ComparisonTestFramework): | class FullBlockTest(BitcoinTestFramework): | ||||
# Can either run this test as 1 node with expected answers, or two and compare them. | |||||
# Change the "outcome" variable from each TestInstance object to only do | |||||
# the comparison. | |||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 1 | self.num_nodes = 1 | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.block_heights = {} | self.block_heights = {} | ||||
self.tip = None | self.tip = None | ||||
self.blocks = {} | self.blocks = {} | ||||
self.excessive_block_size = 16 * ONE_MEGABYTE | self.excessive_block_size = 16 * ONE_MEGABYTE | ||||
self.extra_args = [['-norelaypriority', | self.extra_args = [['-norelaypriority', | ||||
'-whitelist=127.0.0.1', | '-whitelist=127.0.0.1', | ||||
'-limitancestorcount=999999', | '-limitancestorcount=999999', | ||||
'-limitancestorsize=999999', | '-limitancestorsize=999999', | ||||
'-limitdescendantcount=999999', | '-limitdescendantcount=999999', | ||||
'-limitdescendantsize=999999', | '-limitdescendantsize=999999', | ||||
'-maxmempool=99999', | '-maxmempool=99999', | ||||
"-excessiveblocksize={}".format(self.excessive_block_size)]] | "-excessiveblocksize={}".format(self.excessive_block_size)]] | ||||
def add_options(self, parser): | def add_options(self, parser): | ||||
super().add_options(parser) | super().add_options(parser) | ||||
parser.add_argument( | parser.add_argument( | ||||
"--runbarelyexpensive", dest="runbarelyexpensive", default=True) | "--runbarelyexpensive", dest="runbarelyexpensive", default=True) | ||||
def run_test(self): | |||||
self.test = TestManager(self, self.options.tmpdir) | |||||
self.test.add_all_connections(self.nodes) | |||||
network_thread_start() | |||||
# Set the blocksize to 2MB as initial condition | |||||
self.nodes[0].setexcessiveblock(self.excessive_block_size) | |||||
self.test.run() | |||||
def add_transactions_to_block(self, block, tx_list): | def add_transactions_to_block(self, block, tx_list): | ||||
[tx.rehash() for tx in tx_list] | [tx.rehash() for tx in tx_list] | ||||
block.vtx.extend(tx_list) | block.vtx.extend(tx_list) | ||||
# this is a little handier to use than the version in blocktools.py | # this is a little handier to use than the version in blocktools.py | ||||
def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])): | def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])): | ||||
tx = create_transaction(spend_tx, n, b"", value, script) | tx = create_transaction(spend_tx, n, b"", value, script) | ||||
return tx | return tx | ||||
▲ Show 20 Lines • Show All 101 Lines • ▼ Show 20 Lines | def next_block(self, number, spend=None, script=CScript([OP_TRUE]), block_size=0, extra_txns=0): | ||||
# Do PoW, which is cheap on regnet | # Do PoW, which is cheap on regnet | ||||
block.solve() | block.solve() | ||||
self.tip = block | self.tip = block | ||||
self.block_heights[block.sha256] = height | self.block_heights[block.sha256] = height | ||||
assert number not in self.blocks | assert number not in self.blocks | ||||
self.blocks[number] = block | self.blocks[number] = block | ||||
return block | return block | ||||
def get_tests(self): | def run_test(self): | ||||
self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) | node = self.nodes[0] | ||||
default_p2p = node.add_p2p_connection(P2PDataStore()) | |||||
test_p2p = node.add_p2p_connection(TestP2PConn()) | |||||
network_thread_start() | |||||
default_p2p.wait_for_verack() | |||||
test_p2p.wait_for_verack() | |||||
# Set the blocksize to 2MB as initial condition | |||||
node.setexcessiveblock(self.excessive_block_size) | |||||
self.genesis_hash = int(node.getbestblockhash(), 16) | |||||
self.block_heights[self.genesis_hash] = 0 | self.block_heights[self.genesis_hash] = 0 | ||||
spendable_outputs = [] | spendable_outputs = [] | ||||
# save the current tip so it can be spent by a later block | # save the current tip so it can be spent by a later block | ||||
def save_spendable_output(): | def save_spendable_output(): | ||||
spendable_outputs.append(self.tip) | spendable_outputs.append(self.tip) | ||||
# get an output that we previously marked as spendable | # get an output that we previously marked as spendable | ||||
def get_spendable_output(): | def get_spendable_output(): | ||||
return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) | return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) | ||||
# returns a test case that asserts that the current tip was accepted | |||||
def accepted(): | |||||
return TestInstance([[self.tip, True]]) | |||||
# returns a test case that asserts that the current tip was rejected | |||||
def rejected(reject=None): | |||||
if reject is None: | |||||
return TestInstance([[self.tip, False]]) | |||||
else: | |||||
return TestInstance([[self.tip, reject]]) | |||||
# move the tip back to a previous block | # move the tip back to a previous block | ||||
def tip(number): | def tip(number): | ||||
self.tip = self.blocks[number] | self.tip = self.blocks[number] | ||||
# shorthand for functions | # shorthand for functions | ||||
block = self.next_block | block = self.next_block | ||||
# Create a new block | # Create a new block | ||||
block(0) | block(0) | ||||
save_spendable_output() | save_spendable_output() | ||||
yield accepted() | default_p2p.send_blocks_and_test([self.tip], node) | ||||
# Now we need that block to mature so we can spend the coinbase. | # Now we need that block to mature so we can spend the coinbase. | ||||
test = TestInstance(sync_every_block=False) | maturity_blocks = [] | ||||
for i in range(99): | for i in range(99): | ||||
block(5000 + i) | block(5000 + i) | ||||
test.blocks_and_transactions.append([self.tip, True]) | maturity_blocks.append(self.tip) | ||||
save_spendable_output() | save_spendable_output() | ||||
# Get to one block of the May 15, 2018 HF activation | # Get to one block of the May 15, 2018 HF activation | ||||
for i in range(6): | for i in range(6): | ||||
block(5100 + i) | block(5100 + i) | ||||
test.blocks_and_transactions.append([self.tip, True]) | maturity_blocks.append(self.tip) | ||||
# Send it all to the node at once. | # Send it all to the node at once. | ||||
yield test | default_p2p.send_blocks_and_test(maturity_blocks, node) | ||||
# collect spendable outputs now to avoid cluttering the code later on | # collect spendable outputs now to avoid cluttering the code later on | ||||
out = [] | out = [] | ||||
for i in range(100): | for i in range(100): | ||||
out.append(get_spendable_output()) | out.append(get_spendable_output()) | ||||
# There can be only one network thread running at a time. | |||||
# Adding a new P2P connection here will try to start the network thread | |||||
# at init, which will throw an assertion because it's already running. | |||||
# This requires a few steps to avoid this: | |||||
# 1/ Disconnect all the TestManager nodes | |||||
# 2/ Terminate the network thread | |||||
# 3/ Add the new P2P connection | |||||
# 4/ Reconnect all the TestManager nodes | |||||
# 5/ Restart the network thread | |||||
# Disconnect all the TestManager nodes | |||||
[n.disconnect_node() for n in self.test.p2p_connections] | |||||
self.test.wait_for_disconnections() | |||||
self.test.clear_all_connections() | |||||
# Wait for the network thread to terminate | |||||
network_thread_join() | |||||
# Add the new connection | |||||
node = self.nodes[0] | |||||
node.add_p2p_connection(TestP2PConn()) | |||||
# Reconnect TestManager nodes | |||||
self.test.add_all_connections(self.nodes) | |||||
# Restart the network thread | |||||
network_thread_start() | |||||
# Wait for connection to be etablished | |||||
peer = node.p2p | |||||
peer.wait_for_verack() | |||||
FabienUnsubmitted Not Done Inline ActionsFabien: {meme, src=likestamp}
| |||||
# Check that compact block also work for big blocks | # Check that compact block also work for big blocks | ||||
# Wait for SENDCMPCT | # Wait for SENDCMPCT | ||||
def received_sendcmpct(): | def received_sendcmpct(): | ||||
return (peer.last_sendcmpct != None) | return (test_p2p.last_sendcmpct != None) | ||||
wait_until(received_sendcmpct, timeout=30) | wait_until(received_sendcmpct, timeout=30) | ||||
sendcmpct = msg_sendcmpct() | sendcmpct = msg_sendcmpct() | ||||
sendcmpct.version = 1 | sendcmpct.version = 1 | ||||
sendcmpct.announce = True | sendcmpct.announce = True | ||||
peer.send_and_ping(sendcmpct) | test_p2p.send_and_ping(sendcmpct) | ||||
# Exchange headers | # Exchange headers | ||||
def received_getheaders(): | def received_getheaders(): | ||||
return (peer.last_getheaders != None) | return (test_p2p.last_getheaders != None) | ||||
wait_until(received_getheaders, timeout=30) | wait_until(received_getheaders, timeout=30) | ||||
# Return the favor | # Return the favor | ||||
peer.send_message(peer.last_getheaders) | test_p2p.send_message(test_p2p.last_getheaders) | ||||
# Wait for the header list | # Wait for the header list | ||||
def received_headers(): | def received_headers(): | ||||
return (peer.last_headers != None) | return (test_p2p.last_headers != None) | ||||
wait_until(received_headers, timeout=30) | wait_until(received_headers, timeout=30) | ||||
# It's like we know about the same headers ! | # It's like we know about the same headers ! | ||||
peer.send_message(peer.last_headers) | test_p2p.send_message(test_p2p.last_headers) | ||||
# Send a block | # Send a block | ||||
b1 = block(1, spend=out[0], block_size=ONE_MEGABYTE + 1) | b1 = block(1, spend=out[0], block_size=ONE_MEGABYTE + 1) | ||||
yield accepted() | default_p2p.send_blocks_and_test([self.tip], node) | ||||
# Checks the node to forward it via compact block | # Checks the node to forward it via compact block | ||||
def received_block(): | def received_block(): | ||||
return (peer.last_cmpctblock != None) | return (test_p2p.last_cmpctblock != None) | ||||
wait_until(received_block, timeout=30) | wait_until(received_block, timeout=30) | ||||
# Was it our block ? | # Was it our block ? | ||||
cmpctblk_header = peer.last_cmpctblock.header_and_shortids.header | cmpctblk_header = test_p2p.last_cmpctblock.header_and_shortids.header | ||||
cmpctblk_header.calc_sha256() | cmpctblk_header.calc_sha256() | ||||
assert(cmpctblk_header.sha256 == b1.sha256) | assert(cmpctblk_header.sha256 == b1.sha256) | ||||
# Send a large block with numerous transactions. | # Send a large block with numerous transactions. | ||||
peer.clear_block_data() | test_p2p.clear_block_data() | ||||
b2 = block(2, spend=out[1], extra_txns=70000, | b2 = block(2, spend=out[1], extra_txns=70000, | ||||
block_size=self.excessive_block_size - 1000) | block_size=self.excessive_block_size - 1000) | ||||
yield accepted() | default_p2p.send_blocks_and_test([self.tip], node) | ||||
# Checks the node forwards it via compact block | # Checks the node forwards it via compact block | ||||
wait_until(received_block, timeout=30) | wait_until(received_block, timeout=30) | ||||
# Was it our block ? | # Was it our block ? | ||||
cmpctblk_header = peer.last_cmpctblock.header_and_shortids.header | cmpctblk_header = test_p2p.last_cmpctblock.header_and_shortids.header | ||||
cmpctblk_header.calc_sha256() | cmpctblk_header.calc_sha256() | ||||
assert(cmpctblk_header.sha256 == b2.sha256) | assert(cmpctblk_header.sha256 == b2.sha256) | ||||
# In order to avoid having to resend a ton of transactions, we invalidate | # In order to avoid having to resend a ton of transactions, we invalidate | ||||
# b2, which will send all its transactions in the mempool. | # b2, which will send all its transactions in the mempool. | ||||
node.invalidateblock(node.getbestblockhash()) | node.invalidateblock(node.getbestblockhash()) | ||||
# Let's send a compact block and see if the node accepts it. | # Let's send a compact block and see if the node accepts it. | ||||
# Let's modify b2 and use it so that we can reuse the mempool. | # Let's modify b2 and use it so that we can reuse the mempool. | ||||
tx = b2.vtx[0] | tx = b2.vtx[0] | ||||
tx.vout.append(CTxOut(0, CScript([random.randint(0, 256), OP_RETURN]))) | tx.vout.append(CTxOut(0, CScript([random.randint(0, 256), OP_RETURN]))) | ||||
tx.rehash() | tx.rehash() | ||||
b2.vtx[0] = tx | b2.vtx[0] = tx | ||||
b2.hashMerkleRoot = b2.calc_merkle_root() | b2.hashMerkleRoot = b2.calc_merkle_root() | ||||
b2.solve() | b2.solve() | ||||
# Now we create the compact block and send it | # Now we create the compact block and send it | ||||
comp_block = HeaderAndShortIDs() | comp_block = HeaderAndShortIDs() | ||||
comp_block.initialize_from_block(b2) | comp_block.initialize_from_block(b2) | ||||
peer.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) | test_p2p.send_and_ping(msg_cmpctblock(comp_block.to_p2p())) | ||||
# Check that compact block is received properly | # Check that compact block is received properly | ||||
assert(int(node.getbestblockhash(), 16) == b2.sha256) | assert(int(node.getbestblockhash(), 16) == b2.sha256) | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
FullBlockTest().main() | FullBlockTest().main() |