Changeset View
Changeset View
Standalone View
Standalone View
test/functional/feature_block.py
Show First 20 Lines • Show All 61 Lines • ▼ Show 20 Lines | def serialize(self): | ||||
r += tx.serialize() | r += tx.serialize() | ||||
return r | return r | ||||
def normal_serialize(self): | def normal_serialize(self): | ||||
return super().serialize() | return super().serialize() | ||||
# Valid for block at height 120 | # Valid for block at height 120 | ||||
DUPLICATE_COINBASE_SCRIPT_SIG = b'\x01\x78' | DUPLICATE_COINBASE_SCRIPT_SIG = b"\x01\x78" | ||||
class FullBlockTest(BitcoinTestFramework): | class FullBlockTest(BitcoinTestFramework): | ||||
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 | ||||
# This is a consensus block test, we don't care about tx policy | # This is a consensus block test, we don't care about tx policy | ||||
self.extra_args = [['-noparkdeepreorg', | self.extra_args = [["-noparkdeepreorg", "-acceptnonstdtxn=1"]] | ||||
'-acceptnonstdtxn=1']] | |||||
def run_test(self): | def run_test(self): | ||||
node = self.nodes[0] # convenience reference to the node | node = self.nodes[0] # convenience reference to the node | ||||
self.bootstrap_p2p() # Add one p2p connection to the node | self.bootstrap_p2p() # Add one p2p connection to the node | ||||
self.block_heights = {} | self.block_heights = {} | ||||
self.coinbase_key = ECKey() | self.coinbase_key = ECKey() | ||||
self.coinbase_key.generate() | self.coinbase_key.generate() | ||||
self.coinbase_pubkey = self.coinbase_key.get_pubkey().get_bytes() | self.coinbase_pubkey = self.coinbase_key.get_pubkey().get_bytes() | ||||
self.tip = None | self.tip = None | ||||
self.blocks = {} | self.blocks = {} | ||||
self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) | self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) | ||||
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 | ||||
b_dup_cb = self.next_block('dup_cb') | b_dup_cb = self.next_block("dup_cb") | ||||
b_dup_cb.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG | b_dup_cb.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG | ||||
b_dup_cb.vtx[0].rehash() | b_dup_cb.vtx[0].rehash() | ||||
duplicate_tx = b_dup_cb.vtx[0] | duplicate_tx = b_dup_cb.vtx[0] | ||||
b_dup_cb = self.update_block('dup_cb', []) | b_dup_cb = self.update_block("dup_cb", []) | ||||
self.send_blocks([b_dup_cb]) | self.send_blocks([b_dup_cb]) | ||||
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 | # These constants chosen specifically to trigger an immature coinbase spend | ||||
# at a certain time below. | # at a certain time below. | ||||
Show All 31 Lines | def run_test(self): | ||||
# Submit blocks for rejection, each of which contains a single transaction | # Submit blocks for rejection, each of which contains a single transaction | ||||
# (aside from coinbase) which should be considered invalid. | # (aside from coinbase) which should be considered invalid. | ||||
for TxTemplate in invalid_txs.iter_all_templates(): | for TxTemplate in invalid_txs.iter_all_templates(): | ||||
template = TxTemplate(spend_tx=attempt_spend_tx) | template = TxTemplate(spend_tx=attempt_spend_tx) | ||||
if template.valid_in_block: | if template.valid_in_block: | ||||
continue | continue | ||||
self.log.info( | self.log.info("Reject block with invalid tx: %s", TxTemplate.__name__) | ||||
"Reject block with invalid tx: %s", | |||||
TxTemplate.__name__) | |||||
blockname = f"for_invalid.{TxTemplate.__name__}" | blockname = f"for_invalid.{TxTemplate.__name__}" | ||||
badblock = self.next_block(blockname) | badblock = self.next_block(blockname) | ||||
badtx = template.get_tx() | badtx = template.get_tx() | ||||
if TxTemplate != invalid_txs.InputMissing: | if TxTemplate != invalid_txs.InputMissing: | ||||
self.sign_tx(badtx, attempt_spend_tx) | self.sign_tx(badtx, attempt_spend_tx) | ||||
badtx.rehash() | badtx.rehash() | ||||
badblock = self.update_block(blockname, [badtx]) | badblock = self.update_block(blockname, [badtx]) | ||||
self.send_blocks( | self.send_blocks( | ||||
[badblock], success=False, | [badblock], | ||||
reject_reason=( | success=False, | ||||
template.block_reject_reason or template.reject_reason), | reject_reason=(template.block_reject_reason or template.reject_reason), | ||||
reconnect=True, timeout=2) | reconnect=True, | ||||
timeout=2, | |||||
) | |||||
self.move_tip(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) | ||||
# | # | ||||
Show All 24 Lines | def run_test(self): | ||||
self.log.info("Reorg back to the original chain") | self.log.info("Reorg back to the original chain") | ||||
b6 = self.next_block(6, spend=out[3]) | b6 = self.next_block(6, spend=out[3]) | ||||
self.send_blocks([b6], True) | self.send_blocks([b6], True) | ||||
# Try to create a fork that double-spends | # Try to create a fork that double-spends | ||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | ||||
# \-> b7 (2) -> b8 (4) | # \-> b7 (2) -> b8 (4) | ||||
# \-> b3 (1) -> b4 (2) | # \-> b3 (1) -> b4 (2) | ||||
self.log.info( | self.log.info("Reject a chain with a double spend, even if it is longer") | ||||
"Reject a chain with a double spend, even if it is longer") | |||||
self.move_tip(5) | self.move_tip(5) | ||||
b7 = self.next_block(7, spend=out[2]) | b7 = self.next_block(7, spend=out[2]) | ||||
self.send_blocks([b7], False) | self.send_blocks([b7], False) | ||||
b8 = self.next_block(8, spend=out[4]) | b8 = self.next_block(8, spend=out[4]) | ||||
self.send_blocks([b8], False, reconnect=True) | self.send_blocks([b8], False, reconnect=True) | ||||
# Try to create a block that has too much fee | # Try to create a block that has too much fee | ||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | ||||
# \-> b9 (4) | # \-> b9 (4) | ||||
# \-> b3 (1) -> b4 (2) | # \-> b3 (1) -> b4 (2) | ||||
self.log.info( | self.log.info("Reject a block where the miner creates too much coinbase reward") | ||||
"Reject a block where the miner creates too much coinbase reward") | |||||
self.move_tip(6) | self.move_tip(6) | ||||
b9 = self.next_block(9, spend=out[4], additional_coinbase_value=1) | b9 = self.next_block(9, spend=out[4], additional_coinbase_value=1) | ||||
self.send_blocks([b9], success=False, | self.send_blocks( | ||||
reject_reason='bad-cb-amount', reconnect=True) | [b9], success=False, reject_reason="bad-cb-amount", reconnect=True | ||||
) | |||||
# Create a fork that ends in a block with too much fee (the one that causes the reorg) | # Create a fork that ends in a block with too much fee (the one that causes the reorg) | ||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | ||||
# \-> b10 (3) -> b11 (4) | # \-> b10 (3) -> b11 (4) | ||||
# \-> b3 (1) -> b4 (2) | # \-> b3 (1) -> b4 (2) | ||||
self.log.info( | self.log.info( | ||||
"Reject a chain where the miner creates too much coinbase reward, even if the chain is longer") | "Reject a chain where the miner creates too much coinbase reward, even if" | ||||
" the chain is longer" | |||||
) | |||||
self.move_tip(5) | self.move_tip(5) | ||||
b10 = self.next_block(10, spend=out[3]) | b10 = self.next_block(10, spend=out[3]) | ||||
self.send_blocks([b10], False) | self.send_blocks([b10], False) | ||||
b11 = self.next_block(11, spend=out[4], additional_coinbase_value=1) | b11 = self.next_block(11, spend=out[4], additional_coinbase_value=1) | ||||
self.send_blocks([b11], success=False, | self.send_blocks( | ||||
reject_reason='bad-cb-amount', reconnect=True) | [b11], success=False, reject_reason="bad-cb-amount", reconnect=True | ||||
) | |||||
# Try again, but with a valid fork first | # Try again, but with a valid fork first | ||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | ||||
# \-> b12 (3) -> b13 (4) -> b14 (5) | # \-> b12 (3) -> b13 (4) -> b14 (5) | ||||
# \-> b3 (1) -> b4 (2) | # \-> b3 (1) -> b4 (2) | ||||
self.log.info( | self.log.info( | ||||
"Reject a chain where the miner creates too much coinbase reward, even if the chain is longer (on a forked chain)") | "Reject a chain where the miner creates too much coinbase reward, even if" | ||||
" the chain is longer (on a forked chain)" | |||||
) | |||||
self.move_tip(5) | self.move_tip(5) | ||||
b12 = self.next_block(12, spend=out[3]) | b12 = self.next_block(12, spend=out[3]) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
b13 = self.next_block(13, spend=out[4]) | b13 = self.next_block(13, spend=out[4]) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
b14 = self.next_block(14, spend=out[5], additional_coinbase_value=1) | b14 = self.next_block(14, spend=out[5], additional_coinbase_value=1) | ||||
self.send_blocks([b12, b13, b14], success=False, | self.send_blocks( | ||||
reject_reason='bad-cb-amount', reconnect=True) | [b12, b13, b14], | ||||
success=False, | |||||
reject_reason="bad-cb-amount", | |||||
reconnect=True, | |||||
) | |||||
# New tip should be b13. | # New tip should be b13. | ||||
assert_equal(node.getbestblockhash(), b13.hash) | assert_equal(node.getbestblockhash(), b13.hash) | ||||
self.move_tip(13) | self.move_tip(13) | ||||
b15 = self.next_block(15) | b15 = self.next_block(15) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
self.send_blocks([b15], True) | self.send_blocks([b15], True) | ||||
# Attempt to spend a transaction created on a different fork | # Attempt to spend a transaction created on a different fork | ||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | ||||
# \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (b3.vtx[1]) | # \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (b3.vtx[1]) | ||||
# \-> b3 (1) -> b4 (2) | # \-> b3 (1) -> b4 (2) | ||||
self.log.info("Reject a block with a spend from a re-org'ed out tx") | self.log.info("Reject a block with a spend from a re-org'ed out tx") | ||||
self.move_tip(15) | self.move_tip(15) | ||||
b17 = self.next_block(17, spend=txout_b3) | b17 = self.next_block(17, spend=txout_b3) | ||||
self.send_blocks([b17], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | [b17], | ||||
success=False, | |||||
reject_reason="bad-txns-inputs-missingorspent", | |||||
reconnect=True, | |||||
) | |||||
# Attempt to spend a transaction created on a different fork (on a fork this time) | # Attempt to spend a transaction created on a different fork (on a fork this time) | ||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | ||||
# \-> b12 (3) -> b13 (4) -> b15 (5) | # \-> b12 (3) -> b13 (4) -> b15 (5) | ||||
# \-> b18 (b3.vtx[1]) -> b19 (6) | # \-> b18 (b3.vtx[1]) -> b19 (6) | ||||
# \-> b3 (1) -> b4 (2) | # \-> b3 (1) -> b4 (2) | ||||
self.log.info( | self.log.info( | ||||
"Reject a block with a spend from a re-org'ed out tx (on a forked chain)") | "Reject a block with a spend from a re-org'ed out tx (on a forked chain)" | ||||
) | |||||
self.move_tip(13) | self.move_tip(13) | ||||
b18 = self.next_block(18, spend=txout_b3) | b18 = self.next_block(18, spend=txout_b3) | ||||
self.send_blocks([b18], False) | self.send_blocks([b18], False) | ||||
b19 = self.next_block(19, spend=out[6]) | b19 = self.next_block(19, spend=out[6]) | ||||
self.send_blocks([b19], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | [b19], | ||||
success=False, | |||||
reject_reason="bad-txns-inputs-missingorspent", | |||||
reconnect=True, | |||||
) | |||||
# Attempt to spend a coinbase at depth too low | # Attempt to spend a coinbase at depth too low | ||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | ||||
# \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7) | # \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7) | ||||
# \-> b3 (1) -> b4 (2) | # \-> b3 (1) -> b4 (2) | ||||
self.log.info("Reject a block spending an immature coinbase.") | self.log.info("Reject a block spending an immature coinbase.") | ||||
self.move_tip(15) | self.move_tip(15) | ||||
b20 = self.next_block(20, spend=out[7]) | b20 = self.next_block(20, spend=out[7]) | ||||
self.send_blocks( | self.send_blocks( | ||||
[b20], | [b20], | ||||
success=False, | success=False, | ||||
reject_reason='bad-txns-premature-spend-of-coinbase', | reject_reason="bad-txns-premature-spend-of-coinbase", | ||||
reconnect=True) | reconnect=True, | ||||
) | |||||
# Attempt to spend a coinbase at depth too low (on a fork this time) | # Attempt to spend a coinbase at depth too low (on a fork this time) | ||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | ||||
# \-> b12 (3) -> b13 (4) -> b15 (5) | # \-> b12 (3) -> b13 (4) -> b15 (5) | ||||
# \-> b21 (6) -> b22 (5) | # \-> b21 (6) -> b22 (5) | ||||
# \-> b3 (1) -> b4 (2) | # \-> b3 (1) -> b4 (2) | ||||
self.log.info( | self.log.info( | ||||
"Reject a block spending an immature coinbase (on a forked chain)") | "Reject a block spending an immature coinbase (on a forked chain)" | ||||
) | |||||
self.move_tip(13) | self.move_tip(13) | ||||
b21 = self.next_block(21, spend=out[6]) | b21 = self.next_block(21, spend=out[6]) | ||||
self.send_blocks([b21], False) | self.send_blocks([b21], False) | ||||
b22 = self.next_block(22, spend=out[5]) | b22 = self.next_block(22, spend=out[5]) | ||||
self.send_blocks( | self.send_blocks( | ||||
[b22], | [b22], | ||||
success=False, | success=False, | ||||
reject_reason='bad-txns-premature-spend-of-coinbase', | reject_reason="bad-txns-premature-spend-of-coinbase", | ||||
reconnect=True) | reconnect=True, | ||||
) | |||||
# Create a block on either side of LEGACY_MAX_BLOCK_SIZE and make sure its accepted/rejected | # Create a block on either side of LEGACY_MAX_BLOCK_SIZE and make sure its accepted/rejected | ||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | ||||
# \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) | # \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) | ||||
# \-> b24 (6) -> b25 (7) | # \-> b24 (6) -> b25 (7) | ||||
# \-> b3 (1) -> b4 (2) | # \-> b3 (1) -> b4 (2) | ||||
self.log.info("Accept a block of size LEGACY_MAX_BLOCK_SIZE") | self.log.info("Accept a block of size LEGACY_MAX_BLOCK_SIZE") | ||||
self.move_tip(15) | self.move_tip(15) | ||||
b23 = self.next_block(23, spend=out[6]) | b23 = self.next_block(23, spend=out[6]) | ||||
tx = CTransaction() | tx = CTransaction() | ||||
script_length = LEGACY_MAX_BLOCK_SIZE - len(b23.serialize()) - 69 | script_length = LEGACY_MAX_BLOCK_SIZE - len(b23.serialize()) - 69 | ||||
script_output = CScript([b'\x00' * script_length]) | script_output = CScript([b"\x00" * script_length]) | ||||
tx.vout.append(CTxOut(0, script_output)) | tx.vout.append(CTxOut(0, script_output)) | ||||
tx.vin.append(CTxIn(COutPoint(b23.vtx[1].sha256, 0))) | tx.vin.append(CTxIn(COutPoint(b23.vtx[1].sha256, 0))) | ||||
b23 = self.update_block(23, [tx]) | b23 = self.update_block(23, [tx]) | ||||
# Make sure the math above worked out to produce a max-sized block | # Make sure the math above worked out to produce a max-sized block | ||||
assert_equal(len(b23.serialize()), LEGACY_MAX_BLOCK_SIZE) | assert_equal(len(b23.serialize()), LEGACY_MAX_BLOCK_SIZE) | ||||
self.send_blocks([b23], True) | self.send_blocks([b23], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
# Create blocks with a coinbase input script size out of range | # Create blocks with a coinbase input script size out of range | ||||
# genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) | ||||
# \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) | # \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) | ||||
# \-> ... (6) -> ... (7) | # \-> ... (6) -> ... (7) | ||||
# \-> b3 (1) -> b4 (2) | # \-> b3 (1) -> b4 (2) | ||||
self.log.info( | self.log.info("Reject a block with coinbase input script size out of range") | ||||
"Reject a block with coinbase input script size out of range") | |||||
self.move_tip(15) | self.move_tip(15) | ||||
b26 = self.next_block(26, spend=out[6]) | b26 = self.next_block(26, spend=out[6]) | ||||
b26.vtx[0].vin[0].scriptSig = b'\x00' | b26.vtx[0].vin[0].scriptSig = b"\x00" | ||||
b26.vtx[0].rehash() | b26.vtx[0].rehash() | ||||
# update_block causes the merkle root to get updated, even with no new | # update_block causes the merkle root to get updated, even with no new | ||||
# transactions, and updates the required state. | # transactions, and updates the required state. | ||||
b26 = self.update_block(26, []) | b26 = self.update_block(26, []) | ||||
self.send_blocks([b26], success=False, | self.send_blocks( | ||||
reject_reason='bad-cb-length', reconnect=True) | [b26], success=False, reject_reason="bad-cb-length", reconnect=True | ||||
) | |||||
# Extend the b26 chain to make sure bitcoind isn't accepting b26 | # Extend the b26 chain to make sure bitcoind isn't accepting b26 | ||||
b27 = self.next_block(27, spend=out[7]) | b27 = self.next_block(27, spend=out[7]) | ||||
self.send_blocks([b27], False) | self.send_blocks([b27], False) | ||||
# Now try a too-large-coinbase script | # Now try a too-large-coinbase script | ||||
self.move_tip(15) | self.move_tip(15) | ||||
b28 = self.next_block(28, spend=out[6]) | b28 = self.next_block(28, spend=out[6]) | ||||
b28.vtx[0].vin[0].scriptSig = b'\x00' * 101 | b28.vtx[0].vin[0].scriptSig = b"\x00" * 101 | ||||
b28.vtx[0].rehash() | b28.vtx[0].rehash() | ||||
b28 = self.update_block(28, []) | b28 = self.update_block(28, []) | ||||
self.send_blocks([b28], success=False, | self.send_blocks( | ||||
reject_reason='bad-cb-length', reconnect=True) | [b28], success=False, reject_reason="bad-cb-length", reconnect=True | ||||
) | |||||
# Extend the b28 chain to make sure bitcoind isn't accepting b28 | # Extend the b28 chain to make sure bitcoind isn't accepting b28 | ||||
b29 = self.next_block(29, spend=out[7]) | b29 = self.next_block(29, spend=out[7]) | ||||
self.send_blocks([b29], False) | self.send_blocks([b29], False) | ||||
# b30 has a max-sized coinbase scriptSig. | # b30 has a max-sized coinbase scriptSig. | ||||
self.move_tip(23) | self.move_tip(23) | ||||
b30 = self.next_block(30) | b30 = self.next_block(30) | ||||
b30.vtx[0].vin[0].scriptSig = b'\x00' * 100 | b30.vtx[0].vin[0].scriptSig = b"\x00" * 100 | ||||
b30.vtx[0].rehash() | b30.vtx[0].rehash() | ||||
b30 = self.update_block(30, []) | b30 = self.update_block(30, []) | ||||
self.send_blocks([b30], True) | self.send_blocks([b30], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
b31 = self.next_block(31) | b31 = self.next_block(31) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
b33 = self.next_block(33) | b33 = self.next_block(33) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
b35 = self.next_block(35) | b35 = self.next_block(35) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
self.send_blocks([b31, b33, b35], True) | self.send_blocks([b31, b33, b35], True) | ||||
# Check spending of a transaction in a block which failed to connect | # Check spending of a transaction in a block which failed to connect | ||||
# | # | ||||
# b6 (3) | # b6 (3) | ||||
# b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) | # b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) | ||||
# \-> b37 (11) | # \-> b37 (11) | ||||
# \-> b38 (11/37) | # \-> b38 (11/37) | ||||
# | # | ||||
# save 37's spendable output, but then double-spend out11 to invalidate | # save 37's spendable output, but then double-spend out11 to invalidate | ||||
# the block | # the block | ||||
self.log.info( | self.log.info( | ||||
"Reject a block spending transaction from a block which failed to connect") | "Reject a block spending transaction from a block which failed to connect" | ||||
) | |||||
self.move_tip(35) | self.move_tip(35) | ||||
b37 = self.next_block(37, spend=out[11]) | b37 = self.next_block(37, spend=out[11]) | ||||
txout_b37 = b37.vtx[1] | txout_b37 = b37.vtx[1] | ||||
tx = self.create_and_sign_transaction(out[11], 0) | tx = self.create_and_sign_transaction(out[11], 0) | ||||
b37 = self.update_block(37, [tx]) | b37 = self.update_block(37, [tx]) | ||||
self.send_blocks([b37], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | [b37], | ||||
success=False, | |||||
reject_reason="bad-txns-inputs-missingorspent", | |||||
reconnect=True, | |||||
) | |||||
# attempt to spend b37's first non-coinbase tx, at which point b37 was | # attempt to spend b37's first non-coinbase tx, at which point b37 was | ||||
# still considered valid | # still considered valid | ||||
self.move_tip(35) | self.move_tip(35) | ||||
b38 = self.next_block(38, spend=txout_b37) | b38 = self.next_block(38, spend=txout_b37) | ||||
self.send_blocks([b38], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | [b38], | ||||
success=False, | |||||
reject_reason="bad-txns-inputs-missingorspent", | |||||
reconnect=True, | |||||
) | |||||
self.move_tip(35) | self.move_tip(35) | ||||
b39 = self.next_block(39) | b39 = self.next_block(39) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
b41 = self.next_block(41) | b41 = self.next_block(41) | ||||
self.send_blocks([b39, b41], True) | self.send_blocks([b39, b41], True) | ||||
# Fork off of b39 to create a constant base again | # Fork off of b39 to create a constant base again | ||||
Show All 18 Lines | def run_test(self): | ||||
# the first transaction be non-coinbase, etc. The purpose of b44 is to | # the first transaction be non-coinbase, etc. The purpose of b44 is to | ||||
# make sure this works. | # make sure this works. | ||||
self.log.info("Build block 44 manually") | self.log.info("Build block 44 manually") | ||||
height = self.block_heights[self.tip.sha256] + 1 | height = self.block_heights[self.tip.sha256] + 1 | ||||
coinbase = create_coinbase(height, self.coinbase_pubkey) | coinbase = create_coinbase(height, self.coinbase_pubkey) | ||||
b44 = CBlock() | b44 = CBlock() | ||||
b44.nTime = self.tip.nTime + 1 | b44.nTime = self.tip.nTime + 1 | ||||
b44.hashPrevBlock = self.tip.sha256 | b44.hashPrevBlock = self.tip.sha256 | ||||
b44.nBits = 0x207fffff | b44.nBits = 0x207FFFFF | ||||
b44.vtx.append(coinbase) | b44.vtx.append(coinbase) | ||||
b44.hashMerkleRoot = b44.calc_merkle_root() | b44.hashMerkleRoot = b44.calc_merkle_root() | ||||
b44.solve() | b44.solve() | ||||
self.tip = b44 | self.tip = b44 | ||||
self.block_heights[b44.sha256] = height | self.block_heights[b44.sha256] = height | ||||
self.blocks[44] = b44 | self.blocks[44] = b44 | ||||
self.send_blocks([b44], True) | self.send_blocks([b44], True) | ||||
self.log.info("Reject a block with a non-coinbase as the first tx") | self.log.info("Reject a block with a non-coinbase as the first tx") | ||||
non_coinbase = self.create_tx(out[15], 0, 1) | non_coinbase = self.create_tx(out[15], 0, 1) | ||||
b45 = CBlock() | b45 = CBlock() | ||||
b45.nTime = self.tip.nTime + 1 | b45.nTime = self.tip.nTime + 1 | ||||
b45.hashPrevBlock = self.tip.sha256 | b45.hashPrevBlock = self.tip.sha256 | ||||
b45.nBits = 0x207fffff | b45.nBits = 0x207FFFFF | ||||
b45.vtx.append(non_coinbase) | b45.vtx.append(non_coinbase) | ||||
b45.hashMerkleRoot = b45.calc_merkle_root() | b45.hashMerkleRoot = b45.calc_merkle_root() | ||||
b45.calc_sha256() | b45.calc_sha256() | ||||
b45.solve() | b45.solve() | ||||
self.block_heights[b45.sha256] = self.block_heights[ | self.block_heights[b45.sha256] = self.block_heights[self.tip.sha256] + 1 | ||||
self.tip.sha256] + 1 | |||||
self.tip = b45 | self.tip = b45 | ||||
self.blocks[45] = b45 | self.blocks[45] = b45 | ||||
self.send_blocks([b45], success=False, | self.send_blocks( | ||||
reject_reason='bad-cb-missing', reconnect=True) | [b45], success=False, reject_reason="bad-cb-missing", reconnect=True | ||||
) | |||||
self.log.info("Reject a block with no transactions") | self.log.info("Reject a block with no transactions") | ||||
self.move_tip(44) | self.move_tip(44) | ||||
b46 = CBlock() | b46 = CBlock() | ||||
b46.nTime = b44.nTime + 1 | b46.nTime = b44.nTime + 1 | ||||
b46.hashPrevBlock = b44.sha256 | b46.hashPrevBlock = b44.sha256 | ||||
b46.nBits = 0x207fffff | b46.nBits = 0x207FFFFF | ||||
b46.vtx = [] | b46.vtx = [] | ||||
b46.hashMerkleRoot = 0 | b46.hashMerkleRoot = 0 | ||||
b46.solve() | b46.solve() | ||||
self.block_heights[b46.sha256] = self.block_heights[b44.sha256] + 1 | self.block_heights[b46.sha256] = self.block_heights[b44.sha256] + 1 | ||||
self.tip = b46 | self.tip = b46 | ||||
assert 46 not in self.blocks | assert 46 not in self.blocks | ||||
self.blocks[46] = b46 | self.blocks[46] = b46 | ||||
self.send_blocks([b46], success=False, | self.send_blocks( | ||||
reject_reason='bad-cb-missing', reconnect=True) | [b46], success=False, reject_reason="bad-cb-missing", reconnect=True | ||||
) | |||||
self.log.info("Reject a block with invalid work") | self.log.info("Reject a block with invalid work") | ||||
self.move_tip(44) | self.move_tip(44) | ||||
b47 = self.next_block(47) | b47 = self.next_block(47) | ||||
target = uint256_from_compact(b47.nBits) | target = uint256_from_compact(b47.nBits) | ||||
while b47.sha256 <= target: | while b47.sha256 <= target: | ||||
# Rehash nonces until an invalid too-high-hash block is found. | # Rehash nonces until an invalid too-high-hash block is found. | ||||
b47.nNonce += 1 | b47.nNonce += 1 | ||||
b47.rehash() | b47.rehash() | ||||
self.send_blocks( | self.send_blocks( | ||||
[b47], | [b47], False, force_send=True, reject_reason="high-hash", reconnect=True | ||||
False, | ) | ||||
force_send=True, | |||||
reject_reason='high-hash', | |||||
reconnect=True) | |||||
self.log.info("Reject a block with a timestamp >2 hours in the future") | self.log.info("Reject a block with a timestamp >2 hours in the future") | ||||
self.move_tip(44) | self.move_tip(44) | ||||
b48 = self.next_block(48) | b48 = self.next_block(48) | ||||
b48.nTime = int(time.time()) + 60 * 60 * 3 | b48.nTime = int(time.time()) + 60 * 60 * 3 | ||||
# Header timestamp has changed. Re-solve the block. | # Header timestamp has changed. Re-solve the block. | ||||
b48.solve() | b48.solve() | ||||
self.send_blocks([b48], False, force_send=True, | self.send_blocks([b48], False, force_send=True, reject_reason="time-too-new") | ||||
reject_reason='time-too-new') | |||||
self.log.info("Reject a block with invalid merkle hash") | self.log.info("Reject a block with invalid merkle hash") | ||||
self.move_tip(44) | self.move_tip(44) | ||||
b49 = self.next_block(49) | b49 = self.next_block(49) | ||||
b49.hashMerkleRoot += 1 | b49.hashMerkleRoot += 1 | ||||
b49.solve() | b49.solve() | ||||
self.send_blocks([b49], success=False, | self.send_blocks( | ||||
reject_reason='bad-txnmrklroot', reconnect=True) | [b49], success=False, reject_reason="bad-txnmrklroot", reconnect=True | ||||
) | |||||
self.log.info("Reject a block with incorrect POW limit") | self.log.info("Reject a block with incorrect POW limit") | ||||
self.move_tip(44) | self.move_tip(44) | ||||
b50 = self.next_block(50) | b50 = self.next_block(50) | ||||
b50.nBits = b50.nBits - 1 | b50.nBits = b50.nBits - 1 | ||||
b50.solve() | b50.solve() | ||||
self.send_blocks( | self.send_blocks( | ||||
[b50], | [b50], False, force_send=True, reject_reason="bad-diffbits", reconnect=True | ||||
False, | ) | ||||
force_send=True, | |||||
reject_reason='bad-diffbits', | |||||
reconnect=True) | |||||
self.log.info("Reject a block with two coinbase transactions") | self.log.info("Reject a block with two coinbase transactions") | ||||
self.move_tip(44) | self.move_tip(44) | ||||
b51 = self.next_block(51) | b51 = self.next_block(51) | ||||
cb2 = create_coinbase(51, self.coinbase_pubkey) | cb2 = create_coinbase(51, self.coinbase_pubkey) | ||||
b51 = self.update_block(51, [cb2]) | b51 = self.update_block(51, [cb2]) | ||||
self.send_blocks([b51], success=False, | self.send_blocks( | ||||
reject_reason='bad-tx-coinbase', reconnect=True) | [b51], success=False, reject_reason="bad-tx-coinbase", reconnect=True | ||||
) | |||||
self.log.info("Reject a block with duplicate transactions") | self.log.info("Reject a block with duplicate transactions") | ||||
self.move_tip(44) | self.move_tip(44) | ||||
b52 = self.next_block(52, spend=out[15]) | b52 = self.next_block(52, spend=out[15]) | ||||
b52 = self.update_block(52, [b52.vtx[1]]) | b52 = self.update_block(52, [b52.vtx[1]]) | ||||
self.send_blocks([b52], success=False, | self.send_blocks( | ||||
reject_reason='tx-duplicate', reconnect=True) | [b52], success=False, reject_reason="tx-duplicate", reconnect=True | ||||
) | |||||
# Test block timestamps | # Test block timestamps | ||||
# -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) | # -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) | ||||
# \-> b54 (15) | # \-> b54 (15) | ||||
# | # | ||||
self.move_tip(43) | self.move_tip(43) | ||||
b53 = self.next_block(53, spend=out[14]) | b53 = self.next_block(53, spend=out[14]) | ||||
self.send_blocks([b53], False) | self.send_blocks([b53], False) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
self.log.info("Reject a block with timestamp before MedianTimePast") | self.log.info("Reject a block with timestamp before MedianTimePast") | ||||
b54 = self.next_block(54, spend=out[15]) | b54 = self.next_block(54, spend=out[15]) | ||||
b54.nTime = b35.nTime - 1 | b54.nTime = b35.nTime - 1 | ||||
b54.solve() | b54.solve() | ||||
self.send_blocks( | self.send_blocks( | ||||
[b54], | [b54], False, force_send=True, reject_reason="time-too-old", reconnect=True | ||||
False, | ) | ||||
force_send=True, | |||||
reject_reason='time-too-old', | |||||
reconnect=True) | |||||
# valid timestamp | # valid timestamp | ||||
self.move_tip(53) | self.move_tip(53) | ||||
b55 = self.next_block(55, spend=out[15]) | b55 = self.next_block(55, spend=out[15]) | ||||
b55.nTime = b35.nTime | b55.nTime = b35.nTime | ||||
self.update_block(55, []) | self.update_block(55, []) | ||||
self.send_blocks([b55], True) | self.send_blocks([b55], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
Show All 29 Lines | def run_test(self): | ||||
self.move_tip(55) | self.move_tip(55) | ||||
b57 = self.next_block(57) | b57 = self.next_block(57) | ||||
tx = self.create_and_sign_transaction(out[16], 1) | tx = self.create_and_sign_transaction(out[16], 1) | ||||
tx1 = self.create_tx(tx, 0, 1) | tx1 = self.create_tx(tx, 0, 1) | ||||
b57 = self.update_block(57, [tx, tx1]) | b57 = self.update_block(57, [tx, tx1]) | ||||
# b56 - copy b57, add a duplicate tx | # b56 - copy b57, add a duplicate tx | ||||
self.log.info( | self.log.info( | ||||
"Reject a block with a duplicate transaction in the Merkle Tree (but with a valid Merkle Root)") | "Reject a block with a duplicate transaction in the Merkle Tree (but with a" | ||||
" valid Merkle Root)" | |||||
) | |||||
self.move_tip(55) | self.move_tip(55) | ||||
b56 = copy.deepcopy(b57) | b56 = copy.deepcopy(b57) | ||||
self.blocks[56] = b56 | self.blocks[56] = b56 | ||||
assert_equal(len(b56.vtx), 3) | assert_equal(len(b56.vtx), 3) | ||||
b56 = self.update_block(56, [b57.vtx[2]]) | b56 = self.update_block(56, [b57.vtx[2]]) | ||||
assert_equal(b56.hash, b57.hash) | assert_equal(b56.hash, b57.hash) | ||||
self.send_blocks([b56], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-duplicate', reconnect=True) | [b56], success=False, reject_reason="bad-txns-duplicate", reconnect=True | ||||
) | |||||
# b57p2 - a good block with 6 tx'es, don't submit until end | # b57p2 - a good block with 6 tx'es, don't submit until end | ||||
self.move_tip(55) | self.move_tip(55) | ||||
b57p2 = self.next_block("57p2") | b57p2 = self.next_block("57p2") | ||||
tx = self.create_and_sign_transaction(out[16], 1) | tx = self.create_and_sign_transaction(out[16], 1) | ||||
tx1 = self.create_tx(tx, 0, 1) | tx1 = self.create_tx(tx, 0, 1) | ||||
tx2 = self.create_tx(tx1, 0, 1) | tx2 = self.create_tx(tx1, 0, 1) | ||||
tx3 = self.create_tx(tx2, 0, 1) | tx3 = self.create_tx(tx2, 0, 1) | ||||
tx4 = self.create_tx(tx3, 0, 1) | tx4 = self.create_tx(tx3, 0, 1) | ||||
b57p2 = self.update_block("57p2", [tx, tx1, tx2, tx3, tx4]) | b57p2 = self.update_block("57p2", [tx, tx1, tx2, tx3, tx4]) | ||||
# b56p2 - copy b57p2, duplicate two non-consecutive tx's | # b56p2 - copy b57p2, duplicate two non-consecutive tx's | ||||
self.log.info( | self.log.info( | ||||
"Reject a block with two duplicate transactions in the Merkle Tree (but with a valid Merkle Root)") | "Reject a block with two duplicate transactions in the Merkle Tree (but" | ||||
" with a valid Merkle Root)" | |||||
) | |||||
self.move_tip(55) | self.move_tip(55) | ||||
b56p2 = copy.deepcopy(b57p2) | b56p2 = copy.deepcopy(b57p2) | ||||
self.blocks["b56p2"] = b56p2 | self.blocks["b56p2"] = b56p2 | ||||
assert_equal(len(b56p2.vtx), 6) | assert_equal(len(b56p2.vtx), 6) | ||||
b56p2 = self.update_block("b56p2", b56p2.vtx[4:6], reorder=False) | b56p2 = self.update_block("b56p2", b56p2.vtx[4:6], reorder=False) | ||||
assert_equal(b56p2.hash, b57p2.hash) | assert_equal(b56p2.hash, b57p2.hash) | ||||
self.send_blocks([b56p2], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-duplicate', reconnect=True) | [b56p2], success=False, reject_reason="bad-txns-duplicate", reconnect=True | ||||
) | |||||
self.move_tip("57p2") | self.move_tip("57p2") | ||||
self.send_blocks([b57p2], True) | self.send_blocks([b57p2], True) | ||||
self.move_tip(57) | self.move_tip(57) | ||||
# The tip is not updated because 57p2 seen first | # The tip is not updated because 57p2 seen first | ||||
self.send_blocks([b57], False) | self.send_blocks([b57], False) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
# Test a few invalid tx types | # Test a few invalid tx types | ||||
# | # | ||||
# -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 () | # -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 () | ||||
# \-> ??? (17) | # \-> ??? (17) | ||||
# | # | ||||
# tx with prevout.n out of range | # tx with prevout.n out of range | ||||
self.log.info( | self.log.info("Reject a block with a transaction with prevout.n out of range") | ||||
"Reject a block with a transaction with prevout.n out of range") | |||||
self.move_tip(57) | self.move_tip(57) | ||||
b58 = self.next_block(58, spend=out[17]) | b58 = self.next_block(58, spend=out[17]) | ||||
tx = CTransaction() | tx = CTransaction() | ||||
assert len(out[17].vout) < 42 | assert len(out[17].vout) < 42 | ||||
tx.vin.append( | tx.vin.append( | ||||
CTxIn(COutPoint(out[17].sha256, 42), CScript([OP_TRUE]), 0xffffffff)) | CTxIn(COutPoint(out[17].sha256, 42), CScript([OP_TRUE]), 0xFFFFFFFF) | ||||
) | |||||
tx.vout.append(CTxOut(0, b"")) | tx.vout.append(CTxOut(0, b"")) | ||||
pad_tx(tx) | pad_tx(tx) | ||||
tx.calc_sha256() | tx.calc_sha256() | ||||
b58 = self.update_block(58, [tx]) | b58 = self.update_block(58, [tx]) | ||||
self.send_blocks([b58], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | [b58], | ||||
success=False, | |||||
reject_reason="bad-txns-inputs-missingorspent", | |||||
reconnect=True, | |||||
) | |||||
# tx with output value > input value | # tx with output value > input value | ||||
self.log.info( | self.log.info("Reject a block with a transaction with outputs > inputs") | ||||
"Reject a block with a transaction with outputs > inputs") | |||||
self.move_tip(57) | self.move_tip(57) | ||||
b59 = self.next_block(59) | b59 = self.next_block(59) | ||||
tx = self.create_and_sign_transaction(out[17], 51 * COIN) | tx = self.create_and_sign_transaction(out[17], 51 * COIN) | ||||
b59 = self.update_block(59, [tx]) | b59 = self.update_block(59, [tx]) | ||||
self.send_blocks([b59], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-in-belowout', reconnect=True) | [b59], success=False, reject_reason="bad-txns-in-belowout", reconnect=True | ||||
) | |||||
# reset to good chain | # reset to good chain | ||||
self.move_tip(57) | self.move_tip(57) | ||||
b60 = self.next_block(60) | b60 = self.next_block(60) | ||||
self.send_blocks([b60], True) | self.send_blocks([b60], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
# Test BIP30 (reject duplicate) | # Test BIP30 (reject duplicate) | ||||
# | # | ||||
# -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 () | # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 () | ||||
# \-> b61 () | # \-> b61 () | ||||
# | # | ||||
# Blocks are not allowed to contain a transaction whose id matches that of an earlier, | # Blocks are not allowed to contain a transaction whose id matches that of an earlier, | ||||
# not-fully-spent transaction in the same chain. To test, make identical coinbases; | # not-fully-spent transaction in the same chain. To test, make identical coinbases; | ||||
# the second one should be rejected. See also CVE-2012-1909. | # the second one should be rejected. See also CVE-2012-1909. | ||||
# | # | ||||
self.log.info( | self.log.info( | ||||
"Reject a block with a transaction with a duplicate hash of a previous transaction (BIP30)") | "Reject a block with a transaction with a duplicate hash of a previous" | ||||
" transaction (BIP30)" | |||||
) | |||||
self.move_tip(60) | self.move_tip(60) | ||||
b61 = self.next_block(61) | b61 = self.next_block(61) | ||||
b61.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG | b61.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG | ||||
b61.vtx[0].rehash() | b61.vtx[0].rehash() | ||||
b61 = self.update_block(61, []) | b61 = self.update_block(61, []) | ||||
assert_equal(duplicate_tx.serialize(), b61.vtx[0].serialize()) | assert_equal(duplicate_tx.serialize(), b61.vtx[0].serialize()) | ||||
self.send_blocks([b61], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-BIP30', reconnect=True) | [b61], success=False, reject_reason="bad-txns-BIP30", reconnect=True | ||||
) | |||||
# Test BIP30 (allow duplicate if spent) | # Test BIP30 (allow duplicate if spent) | ||||
# | # | ||||
# -> b57 (16) -> b60 () | # -> b57 (16) -> b60 () | ||||
# \-> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () | # \-> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () | ||||
# | # | ||||
self.move_tip(57) | self.move_tip(57) | ||||
b_spend_dup_cb = self.next_block('spend_dup_cb') | b_spend_dup_cb = self.next_block("spend_dup_cb") | ||||
tx = CTransaction() | tx = CTransaction() | ||||
tx.vin.append(CTxIn(COutPoint(duplicate_tx.sha256, 0))) | tx.vin.append(CTxIn(COutPoint(duplicate_tx.sha256, 0))) | ||||
tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) | tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) | ||||
self.sign_tx(tx, duplicate_tx) | self.sign_tx(tx, duplicate_tx) | ||||
tx.rehash() | tx.rehash() | ||||
b_spend_dup_cb = self.update_block('spend_dup_cb', [tx]) | b_spend_dup_cb = self.update_block("spend_dup_cb", [tx]) | ||||
b_dup_2 = self.next_block('dup_2') | b_dup_2 = self.next_block("dup_2") | ||||
b_dup_2.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG | b_dup_2.vtx[0].vin[0].scriptSig = DUPLICATE_COINBASE_SCRIPT_SIG | ||||
b_dup_2.vtx[0].rehash() | b_dup_2.vtx[0].rehash() | ||||
b_dup_2 = self.update_block('dup_2', []) | b_dup_2 = self.update_block("dup_2", []) | ||||
assert_equal(duplicate_tx.serialize(), b_dup_2.vtx[0].serialize()) | assert_equal(duplicate_tx.serialize(), b_dup_2.vtx[0].serialize()) | ||||
assert_equal( | assert_equal( | ||||
self.nodes[0].gettxout( | self.nodes[0].gettxout(txid=duplicate_tx.hash, n=0)["confirmations"], 119 | ||||
txid=duplicate_tx.hash, | ) | ||||
n=0)['confirmations'], | |||||
119) | |||||
self.send_blocks([b_spend_dup_cb, b_dup_2], success=True) | self.send_blocks([b_spend_dup_cb, b_dup_2], success=True) | ||||
# The duplicate has less confirmations | # The duplicate has less confirmations | ||||
assert_equal( | assert_equal( | ||||
self.nodes[0].gettxout( | self.nodes[0].gettxout(txid=duplicate_tx.hash, n=0)["confirmations"], 1 | ||||
txid=duplicate_tx.hash, | ) | ||||
n=0)['confirmations'], | |||||
1) | |||||
# Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests) | # Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests) | ||||
# | # | ||||
# -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () | # -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () | ||||
# \-> b62 (18) | # \-> b62 (18) | ||||
# | # | ||||
self.log.info( | self.log.info("Reject a block with a transaction with a nonfinal locktime") | ||||
"Reject a block with a transaction with a nonfinal locktime") | self.move_tip("dup_2") | ||||
self.move_tip('dup_2') | |||||
b62 = self.next_block(62) | b62 = self.next_block(62) | ||||
tx = CTransaction() | tx = CTransaction() | ||||
tx.nLockTime = 0xffffffff # this locktime is non-final | tx.nLockTime = 0xFFFFFFFF # this locktime is non-final | ||||
# don't set nSequence | # don't set nSequence | ||||
tx.vin.append(CTxIn(COutPoint(out[18].sha256, 0))) | tx.vin.append(CTxIn(COutPoint(out[18].sha256, 0))) | ||||
tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) | tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) | ||||
assert tx.vin[0].nSequence < 0xffffffff | assert tx.vin[0].nSequence < 0xFFFFFFFF | ||||
tx.calc_sha256() | tx.calc_sha256() | ||||
b62 = self.update_block(62, [tx]) | b62 = self.update_block(62, [tx]) | ||||
self.send_blocks( | self.send_blocks( | ||||
[b62], | [b62], success=False, reject_reason="bad-txns-nonfinal", reconnect=True | ||||
success=False, | ) | ||||
reject_reason='bad-txns-nonfinal', | |||||
reconnect=True) | |||||
# Test a non-final coinbase is also rejected | # Test a non-final coinbase is also rejected | ||||
# | # | ||||
# -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () | # -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () | ||||
# \-> b63 (-) | # \-> b63 (-) | ||||
# | # | ||||
self.log.info( | self.log.info( | ||||
"Reject a block with a coinbase transaction with a nonfinal locktime") | "Reject a block with a coinbase transaction with a nonfinal locktime" | ||||
self.move_tip('dup_2') | ) | ||||
self.move_tip("dup_2") | |||||
b63 = self.next_block(63) | b63 = self.next_block(63) | ||||
b63.vtx[0].nLockTime = 0xffffffff | b63.vtx[0].nLockTime = 0xFFFFFFFF | ||||
b63.vtx[0].vin[0].nSequence = 0xDEADBEEF | b63.vtx[0].vin[0].nSequence = 0xDEADBEEF | ||||
b63.vtx[0].rehash() | b63.vtx[0].rehash() | ||||
b63 = self.update_block(63, []) | b63 = self.update_block(63, []) | ||||
self.send_blocks( | self.send_blocks( | ||||
[b63], | [b63], success=False, reject_reason="bad-txns-nonfinal", reconnect=True | ||||
success=False, | ) | ||||
reject_reason='bad-txns-nonfinal', | |||||
reconnect=True) | |||||
# This checks that a block with a bloated VARINT between the block_header and the array of tx such that | # This checks that a block with a bloated VARINT between the block_header and the array of tx such that | ||||
# the block is > LEGACY_MAX_BLOCK_SIZE with the bloated varint, but <= LEGACY_MAX_BLOCK_SIZE without the bloated varint, | # the block is > LEGACY_MAX_BLOCK_SIZE with the bloated varint, but <= LEGACY_MAX_BLOCK_SIZE without the bloated varint, | ||||
# does not cause a subsequent, identical block with canonical encoding to be rejected. The test does not | # does not cause a subsequent, identical block with canonical encoding to be rejected. The test does not | ||||
# care whether the bloated block is accepted or rejected; it only cares that the second block is accepted. | # care whether the bloated block is accepted or rejected; it only cares that the second block is accepted. | ||||
# | # | ||||
# What matters is that the receiving node should not reject the bloated block, and then reject the canonical | # What matters is that the receiving node should not reject the bloated block, and then reject the canonical | ||||
# block on the basis that it's the same as an already-rejected block (which would be a consensus failure.) | # block on the basis that it's the same as an already-rejected block (which would be a consensus failure.) | ||||
# | # | ||||
# -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () -> b64 (18) | # -> b_spend_dup_cb (b_dup_cb) -> b_dup_2 () -> b64 (18) | ||||
# \ | # \ | ||||
# b64a (18) | # b64a (18) | ||||
# b64a is a bloated block (non-canonical varint) | # b64a is a bloated block (non-canonical varint) | ||||
# b64 is a good block (same as b64 but w/ canonical varint) | # b64 is a good block (same as b64 but w/ canonical varint) | ||||
# | # | ||||
self.log.info( | self.log.info( | ||||
"Accept a valid block even if a bloated version of the block has previously been sent") | "Accept a valid block even if a bloated version of the block has previously" | ||||
self.move_tip('dup_2') | " been sent" | ||||
) | |||||
self.move_tip("dup_2") | |||||
regular_block = self.next_block("64a", spend=out[18]) | regular_block = self.next_block("64a", spend=out[18]) | ||||
# make it a "broken_block," with non-canonical serialization | # make it a "broken_block," with non-canonical serialization | ||||
b64a = CBrokenBlock(regular_block) | b64a = CBrokenBlock(regular_block) | ||||
b64a.initialize(regular_block) | b64a.initialize(regular_block) | ||||
self.blocks["64a"] = b64a | self.blocks["64a"] = b64a | ||||
self.tip = b64a | self.tip = b64a | ||||
tx = CTransaction() | tx = CTransaction() | ||||
# use canonical serialization to calculate size | # use canonical serialization to calculate size | ||||
script_length = LEGACY_MAX_BLOCK_SIZE - \ | script_length = LEGACY_MAX_BLOCK_SIZE - len(b64a.normal_serialize()) - 69 | ||||
len(b64a.normal_serialize()) - 69 | script_output = CScript([b"\x00" * script_length]) | ||||
script_output = CScript([b'\x00' * script_length]) | |||||
tx.vout.append(CTxOut(0, script_output)) | tx.vout.append(CTxOut(0, script_output)) | ||||
tx.vin.append(CTxIn(COutPoint(b64a.vtx[1].sha256, 0))) | tx.vin.append(CTxIn(COutPoint(b64a.vtx[1].sha256, 0))) | ||||
b64a = self.update_block("64a", [tx]) | b64a = self.update_block("64a", [tx]) | ||||
assert_equal(len(b64a.serialize()), LEGACY_MAX_BLOCK_SIZE + 8) | assert_equal(len(b64a.serialize()), LEGACY_MAX_BLOCK_SIZE + 8) | ||||
self.send_blocks([b64a], success=False, | self.send_blocks( | ||||
reject_reason='non-canonical ReadCompactSize()') | [b64a], success=False, reject_reason="non-canonical ReadCompactSize()" | ||||
) | |||||
# bitcoind doesn't disconnect us for sending a bloated block, but if we subsequently | # bitcoind doesn't disconnect us for sending a bloated block, but if we subsequently | ||||
# resend the header message, it won't send us the getdata message again. Just | # resend the header message, it won't send us the getdata message again. Just | ||||
# disconnect and reconnect and then call send_blocks. | # disconnect and reconnect and then call send_blocks. | ||||
# TODO: improve this test to be less dependent on P2P DOS behaviour. | # TODO: improve this test to be less dependent on P2P DOS behaviour. | ||||
node.disconnect_p2ps() | node.disconnect_p2ps() | ||||
self.reconnect_p2p() | self.reconnect_p2p() | ||||
self.move_tip('dup_2') | self.move_tip("dup_2") | ||||
b64 = CBlock(b64a) | b64 = CBlock(b64a) | ||||
b64.vtx = copy.deepcopy(b64a.vtx) | b64.vtx = copy.deepcopy(b64a.vtx) | ||||
assert_equal(b64.hash, b64a.hash) | assert_equal(b64.hash, b64a.hash) | ||||
assert_equal(len(b64.serialize()), LEGACY_MAX_BLOCK_SIZE) | assert_equal(len(b64.serialize()), LEGACY_MAX_BLOCK_SIZE) | ||||
self.blocks[64] = b64 | self.blocks[64] = b64 | ||||
b64 = self.update_block(64, []) | b64 = self.update_block(64, []) | ||||
self.send_blocks([b64], True) | self.send_blocks([b64], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
# Spend an output created in the block itself | # Spend an output created in the block itself | ||||
# | # | ||||
# -> b_dup_2 () -> b64 (18) -> b65 (19) | # -> b_dup_2 () -> b64 (18) -> b65 (19) | ||||
# | # | ||||
self.log.info( | self.log.info( | ||||
"Accept a block with a transaction spending an output created in the same block") | "Accept a block with a transaction spending an output created in the same" | ||||
" block" | |||||
) | |||||
self.move_tip(64) | self.move_tip(64) | ||||
b65 = self.next_block(65) | b65 = self.next_block(65) | ||||
tx1 = self.create_and_sign_transaction(out[19], out[19].vout[0].nValue) | tx1 = self.create_and_sign_transaction(out[19], out[19].vout[0].nValue) | ||||
tx2 = self.create_and_sign_transaction(tx1, 0) | tx2 = self.create_and_sign_transaction(tx1, 0) | ||||
b65 = self.update_block(65, [tx1, tx2]) | b65 = self.update_block(65, [tx1, tx2]) | ||||
self.send_blocks([b65], True) | self.send_blocks([b65], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
# Attempt to double-spend a transaction created in a block | # Attempt to double-spend a transaction created in a block | ||||
# | # | ||||
# -> b64 (18) -> b65 (19) | # -> b64 (18) -> b65 (19) | ||||
# \-> b67 (20) | # \-> b67 (20) | ||||
# | # | ||||
# | # | ||||
self.log.info( | self.log.info( | ||||
"Reject a block with a transaction double spending a transaction created in the same block") | "Reject a block with a transaction double spending a transaction created in" | ||||
" the same block" | |||||
) | |||||
self.move_tip(65) | self.move_tip(65) | ||||
b67 = self.next_block(67) | b67 = self.next_block(67) | ||||
tx1 = self.create_and_sign_transaction(out[20], out[20].vout[0].nValue) | tx1 = self.create_and_sign_transaction(out[20], out[20].vout[0].nValue) | ||||
tx2 = self.create_and_sign_transaction(tx1, 1) | tx2 = self.create_and_sign_transaction(tx1, 1) | ||||
tx3 = self.create_and_sign_transaction(tx1, 2) | tx3 = self.create_and_sign_transaction(tx1, 2) | ||||
b67 = self.update_block(67, [tx1, tx2, tx3]) | b67 = self.update_block(67, [tx1, tx2, tx3]) | ||||
self.send_blocks([b67], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | [b67], | ||||
success=False, | |||||
reject_reason="bad-txns-inputs-missingorspent", | |||||
reconnect=True, | |||||
) | |||||
# More tests of block subsidy | # More tests of block subsidy | ||||
# | # | ||||
# -> b64 (18) -> b65 (19) -> b69 (20) | # -> b64 (18) -> b65 (19) -> b69 (20) | ||||
# \-> b68 (20) | # \-> b68 (20) | ||||
# | # | ||||
# b68 - coinbase with an extra 10 satoshis, | # b68 - coinbase with an extra 10 satoshis, | ||||
# creates a tx that has 9 satoshis from out[20] go to fees | # creates a tx that has 9 satoshis from out[20] go to fees | ||||
# this fails because the coinbase is trying to claim 1 satoshi too much in fees | # this fails because the coinbase is trying to claim 1 satoshi too much in fees | ||||
# | # | ||||
# b69 - coinbase with extra 10 satoshis, and a tx that gives a 10 satoshi fee | # b69 - coinbase with extra 10 satoshis, and a tx that gives a 10 satoshi fee | ||||
# this succeeds | # this succeeds | ||||
# | # | ||||
self.log.info( | self.log.info( | ||||
"Reject a block trying to claim too much subsidy in the coinbase transaction") | "Reject a block trying to claim too much subsidy in the coinbase" | ||||
" transaction" | |||||
) | |||||
self.move_tip(65) | self.move_tip(65) | ||||
b68 = self.next_block(68, additional_coinbase_value=10) | b68 = self.next_block(68, additional_coinbase_value=10) | ||||
tx = self.create_and_sign_transaction( | tx = self.create_and_sign_transaction(out[20], out[20].vout[0].nValue - 9) | ||||
out[20], out[20].vout[0].nValue - 9) | |||||
b68 = self.update_block(68, [tx]) | b68 = self.update_block(68, [tx]) | ||||
self.send_blocks([b68], success=False, | self.send_blocks( | ||||
reject_reason='bad-cb-amount', reconnect=True) | [b68], success=False, reject_reason="bad-cb-amount", reconnect=True | ||||
) | |||||
self.log.info( | self.log.info( | ||||
"Accept a block claiming the correct subsidy in the coinbase transaction") | "Accept a block claiming the correct subsidy in the coinbase transaction" | ||||
) | |||||
self.move_tip(65) | self.move_tip(65) | ||||
b69 = self.next_block(69, additional_coinbase_value=10) | b69 = self.next_block(69, additional_coinbase_value=10) | ||||
tx = self.create_and_sign_transaction( | tx = self.create_and_sign_transaction(out[20], out[20].vout[0].nValue - 10) | ||||
out[20], out[20].vout[0].nValue - 10) | |||||
self.update_block(69, [tx]) | self.update_block(69, [tx]) | ||||
self.send_blocks([b69], True) | self.send_blocks([b69], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
# Test spending the outpoint of a non-existent transaction | # Test spending the outpoint of a non-existent transaction | ||||
# | # | ||||
# -> b65 (19) -> b69 (20) | # -> b65 (19) -> b69 (20) | ||||
# \-> b70 (21) | # \-> b70 (21) | ||||
# | # | ||||
self.log.info( | self.log.info( | ||||
"Reject a block containing a transaction spending from a non-existent input") | "Reject a block containing a transaction spending from a non-existent input" | ||||
) | |||||
self.move_tip(69) | self.move_tip(69) | ||||
b70 = self.next_block(70, spend=out[21]) | b70 = self.next_block(70, spend=out[21]) | ||||
bogus_tx = CTransaction() | bogus_tx = CTransaction() | ||||
bogus_tx.sha256 = uint256_from_str( | bogus_tx.sha256 = uint256_from_str( | ||||
b"23c70ed7c0506e9178fc1a987f40a33946d4ad4c962b5ae3a52546da53af0c5c") | b"23c70ed7c0506e9178fc1a987f40a33946d4ad4c962b5ae3a52546da53af0c5c" | ||||
) | |||||
tx = CTransaction() | tx = CTransaction() | ||||
tx.vin.append(CTxIn(COutPoint(bogus_tx.sha256, 0), b"", 0xffffffff)) | tx.vin.append(CTxIn(COutPoint(bogus_tx.sha256, 0), b"", 0xFFFFFFFF)) | ||||
tx.vout.append(CTxOut(1, b"")) | tx.vout.append(CTxOut(1, b"")) | ||||
pad_tx(tx) | pad_tx(tx) | ||||
b70 = self.update_block(70, [tx]) | b70 = self.update_block(70, [tx]) | ||||
self.send_blocks([b70], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | [b70], | ||||
success=False, | |||||
reject_reason="bad-txns-inputs-missingorspent", | |||||
reconnect=True, | |||||
) | |||||
# Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks) | # Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks) | ||||
# | # | ||||
# -> b65 (19) -> b69 (20) -> b72 (21) | # -> b65 (19) -> b69 (20) -> b72 (21) | ||||
# \-> b71 (21) | # \-> b71 (21) | ||||
# | # | ||||
# b72 is a good block. | # b72 is a good block. | ||||
# b71 is a copy of 72, but re-adds one of its transactions. However, | # b71 is a copy of 72, but re-adds one of its transactions. However, | ||||
# it has the same hash as b72. | # it has the same hash as b72. | ||||
self.log.info( | self.log.info( | ||||
"Reject a block containing a duplicate transaction but with the same Merkle root (Merkle tree malleability") | "Reject a block containing a duplicate transaction but with the same Merkle" | ||||
" root (Merkle tree malleability" | |||||
) | |||||
self.move_tip(69) | self.move_tip(69) | ||||
b72 = self.next_block(72) | b72 = self.next_block(72) | ||||
tx1 = self.create_and_sign_transaction(out[21], 2) | tx1 = self.create_and_sign_transaction(out[21], 2) | ||||
tx2 = self.create_and_sign_transaction(tx1, 1) | tx2 = self.create_and_sign_transaction(tx1, 1) | ||||
b72 = self.update_block(72, [tx1, tx2]) # now tip is 72 | b72 = self.update_block(72, [tx1, tx2]) # now tip is 72 | ||||
b71 = copy.deepcopy(b72) | b71 = copy.deepcopy(b72) | ||||
# add duplicate last transaction | # add duplicate last transaction | ||||
b71.vtx.append(b72.vtx[-1]) | b71.vtx.append(b72.vtx[-1]) | ||||
# b71 builds off b69 | # b71 builds off b69 | ||||
self.block_heights[b71.sha256] = self.block_heights[b69.sha256] + 1 | self.block_heights[b71.sha256] = self.block_heights[b69.sha256] + 1 | ||||
self.blocks[71] = b71 | self.blocks[71] = b71 | ||||
assert_equal(len(b71.vtx), 4) | assert_equal(len(b71.vtx), 4) | ||||
assert_equal(len(b72.vtx), 3) | assert_equal(len(b72.vtx), 3) | ||||
assert_equal(b72.sha256, b71.sha256) | assert_equal(b72.sha256, b71.sha256) | ||||
self.move_tip(71) | self.move_tip(71) | ||||
self.send_blocks([b71], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-duplicate', reconnect=True) | [b71], success=False, reject_reason="bad-txns-duplicate", reconnect=True | ||||
) | |||||
self.move_tip(72) | self.move_tip(72) | ||||
self.send_blocks([b72], True) | self.send_blocks([b72], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
b75 = self.next_block(75) | b75 = self.next_block(75) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
b76 = self.next_block(76) | b76 = self.next_block(76) | ||||
▲ Show 20 Lines • Show All 59 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
assert_equal(len(mempool), 2) | assert_equal(len(mempool), 2) | ||||
assert tx78.hash in mempool | assert tx78.hash in mempool | ||||
assert tx79.hash in mempool | assert tx79.hash in mempool | ||||
# Test invalid opcodes in dead execution paths. | # Test invalid opcodes in dead execution paths. | ||||
# | # | ||||
# -> b81 (26) -> b82 (27) -> b83 (28) | # -> b81 (26) -> b82 (27) -> b83 (28) | ||||
# | # | ||||
self.log.info( | self.log.info("Accept a block with invalid opcodes in dead execution paths") | ||||
"Accept a block with invalid opcodes in dead execution paths") | |||||
b83 = self.next_block(83) | b83 = self.next_block(83) | ||||
op_codes = [OP_IF, OP_INVALIDOPCODE, OP_ELSE, OP_TRUE, OP_ENDIF] | op_codes = [OP_IF, OP_INVALIDOPCODE, OP_ELSE, OP_TRUE, OP_ENDIF] | ||||
script = CScript(op_codes) | script = CScript(op_codes) | ||||
tx1 = self.create_and_sign_transaction( | tx1 = self.create_and_sign_transaction(out[28], out[28].vout[0].nValue, script) | ||||
out[28], out[28].vout[0].nValue, script) | |||||
tx2 = self.create_and_sign_transaction(tx1, 0, CScript([OP_TRUE])) | tx2 = self.create_and_sign_transaction(tx1, 0, CScript([OP_TRUE])) | ||||
tx2.vin[0].scriptSig = CScript([OP_FALSE]) | tx2.vin[0].scriptSig = CScript([OP_FALSE]) | ||||
tx2.rehash() | tx2.rehash() | ||||
b83 = self.update_block(83, [tx1, tx2]) | b83 = self.update_block(83, [tx1, tx2]) | ||||
self.send_blocks([b83], True) | self.send_blocks([b83], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | |||||
b88 = self.next_block(88, spend=out[31]) | b88 = self.next_block(88, spend=out[31]) | ||||
self.send_blocks([b88], True) | self.send_blocks([b88], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
# trying to spend the OP_RETURN output is rejected | # trying to spend the OP_RETURN output is rejected | ||||
b89a = self.next_block("89a", spend=out[32]) | b89a = self.next_block("89a", spend=out[32]) | ||||
tx = self.create_tx(tx1, 0, 0, CScript([OP_TRUE])) | tx = self.create_tx(tx1, 0, 0, CScript([OP_TRUE])) | ||||
b89a = self.update_block("89a", [tx]) | b89a = self.update_block("89a", [tx]) | ||||
self.send_blocks([b89a], success=False, | self.send_blocks( | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | [b89a], | ||||
success=False, | |||||
reject_reason="bad-txns-inputs-missingorspent", | |||||
reconnect=True, | |||||
) | |||||
self.log.info( | self.log.info("Test a re-org of one week's worth of blocks (1088 blocks)") | ||||
"Test a re-org of one week's worth of blocks (1088 blocks)") | |||||
self.move_tip(88) | self.move_tip(88) | ||||
LARGE_REORG_SIZE = 1088 | LARGE_REORG_SIZE = 1088 | ||||
blocks = [] | blocks = [] | ||||
spend = out[32] | spend = out[32] | ||||
for i in range(89, LARGE_REORG_SIZE + 89): | for i in range(89, LARGE_REORG_SIZE + 89): | ||||
b = self.next_block(i, spend) | b = self.next_block(i, spend) | ||||
tx = CTransaction() | tx = CTransaction() | ||||
script_length = LEGACY_MAX_BLOCK_SIZE - len(b.serialize()) - 69 | script_length = LEGACY_MAX_BLOCK_SIZE - len(b.serialize()) - 69 | ||||
script_output = CScript([b'\x00' * script_length]) | script_output = CScript([b"\x00" * script_length]) | ||||
tx.vout.append(CTxOut(0, script_output)) | tx.vout.append(CTxOut(0, script_output)) | ||||
tx.vin.append(CTxIn(COutPoint(b.vtx[1].sha256, 0))) | tx.vin.append(CTxIn(COutPoint(b.vtx[1].sha256, 0))) | ||||
b = self.update_block(i, [tx]) | b = self.update_block(i, [tx]) | ||||
assert_equal(len(b.serialize()), LEGACY_MAX_BLOCK_SIZE) | assert_equal(len(b.serialize()), LEGACY_MAX_BLOCK_SIZE) | ||||
blocks.append(b) | blocks.append(b) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
spend = self.get_spendable_output() | spend = self.get_spendable_output() | ||||
Show All 14 Lines | |||||
# ... and re-org back to the first chain | # ... and re-org back to the first chain | ||||
self.move_tip(chain1_tip) | self.move_tip(chain1_tip) | ||||
block = self.next_block(chain1_tip + 1) | block = self.next_block(chain1_tip + 1) | ||||
self.send_blocks([block], False, force_send=True) | self.send_blocks([block], False, force_send=True) | ||||
block = self.next_block(chain1_tip + 2) | block = self.next_block(chain1_tip + 2) | ||||
self.send_blocks([block], True, timeout=2440) | self.send_blocks([block], True, timeout=2440) | ||||
self.log.info("Reject a block with an invalid block header version") | self.log.info("Reject a block with an invalid block header version") | ||||
b_v1 = self.next_block('b_v1', version=1) | b_v1 = self.next_block("b_v1", version=1) | ||||
self.send_blocks( | self.send_blocks( | ||||
[b_v1], | [b_v1], | ||||
success=False, | success=False, | ||||
force_send=True, | force_send=True, | ||||
reject_reason='bad-version(0x00000001)', | reject_reason="bad-version(0x00000001)", | ||||
reconnect=True) | reconnect=True, | ||||
) | |||||
self.move_tip(chain1_tip + 2) | self.move_tip(chain1_tip + 2) | ||||
b_cb34 = self.next_block('b_cb34') | b_cb34 = self.next_block("b_cb34") | ||||
b_cb34.vtx[0].vin[0].scriptSig = b_cb34.vtx[0].vin[0].scriptSig[:-1] | b_cb34.vtx[0].vin[0].scriptSig = b_cb34.vtx[0].vin[0].scriptSig[:-1] | ||||
b_cb34.vtx[0].rehash() | b_cb34.vtx[0].rehash() | ||||
b_cb34.hashMerkleRoot = b_cb34.calc_merkle_root() | b_cb34.hashMerkleRoot = b_cb34.calc_merkle_root() | ||||
b_cb34.solve() | b_cb34.solve() | ||||
self.send_blocks( | self.send_blocks( | ||||
[b_cb34], | [b_cb34], success=False, reject_reason="bad-cb-height", reconnect=True | ||||
success=False, | ) | ||||
reject_reason='bad-cb-height', | |||||
reconnect=True) | |||||
# Helper methods | # Helper methods | ||||
################ | ################ | ||||
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])): | ||||
return create_tx_with_script( | return create_tx_with_script(spend_tx, n, amount=value, script_pub_key=script) | ||||
spend_tx, n, amount=value, script_pub_key=script) | |||||
# sign a transaction, using the key we know about | # sign a transaction, using the key we know about | ||||
# this signs input 0 in tx, which is assumed to be spending output n in | # this signs input 0 in tx, which is assumed to be spending output n in | ||||
# spend_tx | # spend_tx | ||||
def sign_tx(self, tx, spend_tx): | def sign_tx(self, tx, spend_tx): | ||||
scriptPubKey = bytearray(spend_tx.vout[0].scriptPubKey) | scriptPubKey = bytearray(spend_tx.vout[0].scriptPubKey) | ||||
if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend | if scriptPubKey[0] == OP_TRUE: # an anyone-can-spend | ||||
tx.vin[0].scriptSig = CScript() | tx.vin[0].scriptSig = CScript() | ||||
return | return | ||||
sighash = SignatureHashForkId( | sighash = SignatureHashForkId( | ||||
spend_tx.vout[0].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[0].nValue) | spend_tx.vout[0].scriptPubKey, | ||||
tx, | |||||
0, | |||||
SIGHASH_ALL | SIGHASH_FORKID, | |||||
spend_tx.vout[0].nValue, | |||||
) | |||||
tx.vin[0].scriptSig = CScript( | tx.vin[0].scriptSig = CScript( | ||||
[self.coinbase_key.sign_ecdsa(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))]) | [ | ||||
self.coinbase_key.sign_ecdsa(sighash) | |||||
+ bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) | |||||
] | |||||
) | |||||
def create_and_sign_transaction( | def create_and_sign_transaction(self, spend_tx, value, script=CScript([OP_TRUE])): | ||||
self, spend_tx, value, script=CScript([OP_TRUE])): | |||||
tx = self.create_tx(spend_tx, 0, value, script) | tx = self.create_tx(spend_tx, 0, value, script) | ||||
self.sign_tx(tx, spend_tx) | self.sign_tx(tx, spend_tx) | ||||
tx.rehash() | tx.rehash() | ||||
return tx | return tx | ||||
def next_block(self, number, spend=None, additional_coinbase_value=0, | def next_block( | ||||
script=CScript([OP_TRUE]), *, version=4): | self, | ||||
number, | |||||
spend=None, | |||||
additional_coinbase_value=0, | |||||
script=CScript([OP_TRUE]), | |||||
*, | |||||
version=4, | |||||
): | |||||
if self.tip is None: | if self.tip is None: | ||||
base_block_hash = self.genesis_hash | base_block_hash = self.genesis_hash | ||||
block_time = int(time.time()) + 1 | block_time = int(time.time()) + 1 | ||||
else: | else: | ||||
base_block_hash = self.tip.sha256 | base_block_hash = self.tip.sha256 | ||||
block_time = self.tip.nTime + 1 | block_time = self.tip.nTime + 1 | ||||
# First create the coinbase | # First create the coinbase | ||||
height = self.block_heights[base_block_hash] + 1 | height = self.block_heights[base_block_hash] + 1 | ||||
coinbase = create_coinbase(height, self.coinbase_pubkey) | coinbase = create_coinbase(height, self.coinbase_pubkey) | ||||
coinbase.vout[0].nValue += additional_coinbase_value | coinbase.vout[0].nValue += additional_coinbase_value | ||||
coinbase.rehash() | coinbase.rehash() | ||||
if spend is None: | if spend is None: | ||||
block = create_block( | block = create_block(base_block_hash, coinbase, block_time, version=version) | ||||
base_block_hash, | |||||
coinbase, | |||||
block_time, | |||||
version=version) | |||||
else: | else: | ||||
# all but one satoshi to fees | # all but one satoshi to fees | ||||
coinbase.vout[0].nValue += spend.vout[0].nValue - 1 | coinbase.vout[0].nValue += spend.vout[0].nValue - 1 | ||||
coinbase.rehash() | coinbase.rehash() | ||||
block = create_block( | block = create_block(base_block_hash, coinbase, block_time, version=version) | ||||
base_block_hash, | |||||
coinbase, | |||||
block_time, | |||||
version=version) | |||||
# spend 1 satoshi | # spend 1 satoshi | ||||
tx = self.create_tx(spend, 0, 1, script) | tx = self.create_tx(spend, 0, 1, script) | ||||
self.sign_tx(tx, spend) | self.sign_tx(tx, spend) | ||||
self.add_transactions_to_block(block, [tx]) | self.add_transactions_to_block(block, [tx]) | ||||
block.hashMerkleRoot = block.calc_merkle_root() | block.hashMerkleRoot = block.calc_merkle_root() | ||||
# Block is created. Find a valid nonce. | # Block is created. Find a valid nonce. | ||||
block.solve() | block.solve() | ||||
self.tip = block | self.tip = block | ||||
▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | |||||
def reconnect_p2p(self, timeout=60): | 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(timeout=timeout) | self.bootstrap_p2p(timeout=timeout) | ||||
def send_blocks(self, blocks, success=True, reject_reason=None, | def send_blocks( | ||||
force_send=False, reconnect=False, timeout=60): | self, | ||||
blocks, | |||||
success=True, | |||||
reject_reason=None, | |||||
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.helper_peer.send_blocks_and_test(blocks, self.nodes[0], success=success, | """ | ||||
reject_reason=reject_reason, force_send=force_send, timeout=timeout, expect_disconnect=reconnect) | self.helper_peer.send_blocks_and_test( | ||||
blocks, | |||||
self.nodes[0], | |||||
success=success, | |||||
reject_reason=reject_reason, | |||||
force_send=force_send, | |||||
timeout=timeout, | |||||
expect_disconnect=reconnect, | |||||
) | |||||
if reconnect: | if reconnect: | ||||
self.reconnect_p2p(timeout=timeout) | self.reconnect_p2p(timeout=timeout) | ||||
if __name__ == '__main__': | if __name__ == "__main__": | ||||
FullBlockTest().main() | FullBlockTest().main() |