Changeset View
Changeset View
Standalone View
Standalone View
test/functional/feature_block.py
Show All 38 Lines | from test_framework.script import ( | ||||
SIGHASH_ALL, | SIGHASH_ALL, | ||||
SIGHASH_FORKID, | SIGHASH_FORKID, | ||||
SignatureHashForkId, | SignatureHashForkId, | ||||
) | ) | ||||
from test_framework.test_framework import BitcoinTestFramework | 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 | from test_framework.util import assert_equal | ||||
from data import invalid_txs | |||||
# Use this class for tests that require behavior other than normal "mininode" behavior. | # Use this class for tests that require behavior other than normal "mininode" behavior. | ||||
# For now, it is used to serialize a bloated varint (b64). | # For now, it is used to serialize a bloated varint (b64). | ||||
class CBrokenBlock(CBlock): | class CBrokenBlock(CBlock): | ||||
def initialize(self, base_block): | def initialize(self, base_block): | ||||
self.vtx = copy.deepcopy(base_block.vtx) | self.vtx = copy.deepcopy(base_block.vtx) | ||||
Show All 34 Lines | def run_test(self): | ||||
self.block_heights[self.genesis_hash] = 0 | self.block_heights[self.genesis_hash] = 0 | ||||
self.spendable_outputs = [] | self.spendable_outputs = [] | ||||
# Create a new block | # Create a new block | ||||
b0 = self.next_block(0) | b0 = self.next_block(0) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
self.send_blocks([b0]) | self.send_blocks([b0]) | ||||
# These constants chosen specifically to trigger an immature coinbase spend | |||||
# at a certain time below. | |||||
NUM_BUFFER_BLOCKS_TO_GENERATE = 99 | |||||
NUM_OUTPUTS_TO_COLLECT = 33 | |||||
# Allow the block to mature | # Allow the block to mature | ||||
blocks = [] | blocks = [] | ||||
for i in range(99): | for i in range(NUM_BUFFER_BLOCKS_TO_GENERATE): | ||||
blocks.append(self.next_block(5000 + i)) | blocks.append(self.next_block("maturitybuffer.{}".format(i))) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
self.send_blocks(blocks) | self.send_blocks(blocks) | ||||
# 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(33): | for i in range(NUM_OUTPUTS_TO_COLLECT): | ||||
out.append(self.get_spendable_output()) | out.append(self.get_spendable_output()) | ||||
# Start by building a couple of blocks on top (which output is spent is | # Start by building a couple of blocks on top (which output is spent is | ||||
# in parentheses): | # in parentheses): | ||||
# genesis -> b1 (0) -> b2 (1) | # genesis -> b1 (0) -> b2 (1) | ||||
b1 = self.next_block(1, spend=out[0]) | b1 = self.next_block(1, spend=out[0]) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
b2 = self.next_block(2, spend=out[1]) | b2 = self.next_block(2, spend=out[1]) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
self.send_blocks([b1, b2]) | self.send_blocks([b1, b2], timeout=4) | ||||
# Select a txn with an output eligible for spending. This won't actually be spent, | |||||
# since we're testing submission of a series of blocks with invalid | |||||
# txns. | |||||
attempt_spend_tx = out[2] | |||||
# Submit blocks for rejection, each of which contains a single transaction | |||||
# (aside from coinbase) which should be considered invalid. | |||||
for TxTemplate in invalid_txs.iter_all_templates(): | |||||
template = TxTemplate(spend_tx=attempt_spend_tx) | |||||
# Something about the serialization code for missing inputs creates | |||||
# a different hash in the test client than on bitcoind, resulting | |||||
# in a mismatching merkle root during block validation. | |||||
# Skip until we figure out what's going on. | |||||
if TxTemplate == invalid_txs.InputMissing: | |||||
continue | |||||
if template.valid_in_block: | |||||
continue | |||||
self.log.info( | |||||
"Reject block with invalid tx: %s", | |||||
TxTemplate.__name__) | |||||
blockname = "for_invalid.{}".format(TxTemplate.__name__) | |||||
badblock = self.next_block(blockname) | |||||
badtx = template.get_tx() | |||||
self.sign_tx(badtx, attempt_spend_tx) | |||||
badtx.rehash() | |||||
badblock = self.update_block(blockname, [badtx]) | |||||
self.send_blocks( | |||||
[badblock], success=False, | |||||
reject_reason=( | |||||
template.block_reject_reason or template.reject_reason), | |||||
reconnect=True, timeout=2) | |||||
self.move_tip(2) | |||||
# Fork like this: | # Fork like this: | ||||
# | # | ||||
# genesis -> b1 (0) -> b2 (1) | # genesis -> b1 (0) -> b2 (1) | ||||
# \-> b3 (1) | # \-> b3 (1) | ||||
# | # | ||||
# Nothing should happen at this point. We saw b2 first so it takes | # Nothing should happen at this point. We saw b2 first so it takes | ||||
# priority. | # priority. | ||||
▲ Show 20 Lines • Show All 1,006 Lines • ▼ Show 20 Lines | def update_block(self, block_number, new_transactions, reorder=True): | ||||
# Update the internal state just like in next_block | # Update the internal state just like in next_block | ||||
self.tip = block | self.tip = block | ||||
if block.sha256 != old_sha256: | if block.sha256 != old_sha256: | ||||
self.block_heights[block.sha256] = self.block_heights[old_sha256] | self.block_heights[block.sha256] = self.block_heights[old_sha256] | ||||
del self.block_heights[old_sha256] | del self.block_heights[old_sha256] | ||||
self.blocks[block_number] = block | self.blocks[block_number] = block | ||||
return block | return block | ||||
def bootstrap_p2p(self): | def bootstrap_p2p(self, timeout=10): | ||||
"""Add a P2P connection to the node. | """Add a P2P connection to the node. | ||||
Helper to connect and wait for version handshake.""" | Helper to connect and wait for version handshake.""" | ||||
self.nodes[0].add_p2p_connection(P2PDataStore()) | self.nodes[0].add_p2p_connection(P2PDataStore()) | ||||
# We need to wait for the initial getheaders from the peer before we | # We need to wait for the initial getheaders from the peer before we | ||||
# start populating our blockstore. If we don't, then we may run ahead | # start populating our blockstore. If we don't, then we may run ahead | ||||
# to the next subtest before we receive the getheaders. We'd then send | # to the next subtest before we receive the getheaders. We'd then send | ||||
# an INV for the next block and receive two getheaders - one for the | # an INV for the next block and receive two getheaders - one for the | ||||
# IBD and one for the INV. We'd respond to both and could get | # IBD and one for the INV. We'd respond to both and could get | ||||
# unexpectedly disconnected if the DoS score for that error is 50. | # unexpectedly disconnected if the DoS score for that error is 50. | ||||
self.nodes[0].p2p.wait_for_getheaders(timeout=5) | self.nodes[0].p2p.wait_for_getheaders(timeout=timeout) | ||||
def reconnect_p2p(self): | def reconnect_p2p(self, timeout=60): | ||||
"""Tear down and bootstrap the P2P connection to the node. | """Tear down and bootstrap the P2P connection to the node. | ||||
The node gets disconnected several times in this test. This helper | The node gets disconnected several times in this test. This helper | ||||
method reconnects the p2p and restarts the network thread.""" | method reconnects the p2p and restarts the network thread.""" | ||||
self.nodes[0].disconnect_p2ps() | self.nodes[0].disconnect_p2ps() | ||||
self.bootstrap_p2p() | self.bootstrap_p2p(timeout=timeout) | ||||
def send_blocks(self, blocks, success=True, reject_reason=None, | def send_blocks(self, blocks, success=True, reject_reason=None, | ||||
force_send=False, reconnect=False, timeout=60): | force_send=False, reconnect=False, timeout=60): | ||||
"""Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. | """Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. | ||||
Call with success = False if the tip shouldn't advance to the most recent block.""" | Call with success = False if the tip shouldn't advance to the most recent block.""" | ||||
self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, | self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, | ||||
reject_reason=reject_reason, force_send=force_send, timeout=timeout, expect_disconnect=reconnect) | reject_reason=reject_reason, force_send=force_send, timeout=timeout, expect_disconnect=reconnect) | ||||
if reconnect: | if reconnect: | ||||
self.reconnect_p2p() | self.reconnect_p2p(timeout=timeout) | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
FullBlockTest().main() | FullBlockTest().main() |