Changeset View
Changeset View
Standalone View
Standalone View
test/functional/feature_block.py
Show First 20 Lines • Show All 49 Lines • ▼ Show 20 Lines | from test_framework.script import ( | ||||
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 | ||||
class PreviousSpendableOutput(): | |||||
def __init__(self, tx=CTransaction(), n=-1): | |||||
self.tx = tx | |||||
self.n = n # the output we're spending | |||||
# 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) | ||||
self.hashMerkleRoot = self.calc_merkle_root() | self.hashMerkleRoot = self.calc_merkle_root() | ||||
▲ Show 20 Lines • Show All 63 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
# | # | ||||
# 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 priority. | # Nothing should happen at this point. We saw b2 first so it takes priority. | ||||
self.log.info("Don't reorg to a chain of the same length") | self.log.info("Don't reorg to a chain of the same length") | ||||
self.move_tip(1) | self.move_tip(1) | ||||
b3 = self.next_block(3, spend=out[1]) | b3 = self.next_block(3, spend=out[1]) | ||||
txout_b3 = PreviousSpendableOutput(b3.vtx[1], 0) | txout_b3 = b3.vtx[1] | ||||
self.sync_blocks([b3], False) | self.sync_blocks([b3], False) | ||||
# Now we add another block to make the alternative chain longer. | # Now we add another block to make the alternative chain longer. | ||||
# | # | ||||
# genesis -> b1 (0) -> b2 (1) | # genesis -> b1 (0) -> b2 (1) | ||||
# \-> b3 (1) -> b4 (2) | # \-> b3 (1) -> b4 (2) | ||||
self.log.info("Reorg to a longer chain") | self.log.info("Reorg to a longer chain") | ||||
b4 = self.next_block(4, spend=out[2]) | b4 = self.next_block(4, spend=out[2]) | ||||
▲ Show 20 Lines • Show All 268 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
# \-> b38 (11/37) | # \-> b38 (11/37) | ||||
# | # | ||||
# save 37's spendable output, but then double-spend out11 to invalidate the block | # save 37's spendable output, but then double-spend out11 to invalidate 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 = PreviousSpendableOutput(b37.vtx[1], 0) | txout_b37 = b37.vtx[1] | ||||
tx = self.create_and_sign_transaction(out[11].tx, out[11].n, 0) | tx = self.create_and_sign_transaction(out[11], 0) | ||||
b37 = self.update_block(37, [tx]) | b37 = self.update_block(37, [tx]) | ||||
self.sync_blocks([b37], success=False, | self.sync_blocks([b37], success=False, | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | ||||
# attempt to spend b37's first non-coinbase tx, at which point b37 was still considered valid | # attempt to spend b37's first non-coinbase tx, at which point b37 was 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.sync_blocks([b38], success=False, | self.sync_blocks([b38], success=False, | ||||
Show All 20 Lines | def run_test(self): | ||||
redeem_script = CScript([self.coinbase_pubkey] + [ | redeem_script = CScript([self.coinbase_pubkey] + [ | ||||
OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) | OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) | ||||
redeem_script_hash = hash160(redeem_script) | redeem_script_hash = hash160(redeem_script) | ||||
p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) | p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) | ||||
# Create a transaction that spends one satoshi to the p2sh_script, the rest to OP_TRUE | # Create a transaction that spends one satoshi to the p2sh_script, the rest to OP_TRUE | ||||
# This must be signed because it is spending a coinbase | # This must be signed because it is spending a coinbase | ||||
spend = out[11] | spend = out[11] | ||||
tx = self.create_tx(spend.tx, spend.n, 1, p2sh_script) | tx = self.create_tx(spend, 0, 1, p2sh_script) | ||||
tx.vout.append( | tx.vout.append(CTxOut(spend.vout[0].nValue - 1, CScript([OP_TRUE]))) | ||||
CTxOut(spend.tx.vout[spend.n].nValue - 1, CScript([OP_TRUE]))) | self.sign_tx(tx, spend) | ||||
self.sign_tx(tx, spend.tx, spend.n) | |||||
tx.rehash() | tx.rehash() | ||||
b39 = self.update_block(39, [tx]) | b39 = self.update_block(39, [tx]) | ||||
b39_outputs += 1 | b39_outputs += 1 | ||||
# Until block is full, add tx's with 1 satoshi to p2sh_script, the rest | # Until block is full, add tx's with 1 satoshi to p2sh_script, the rest | ||||
# to OP_TRUE | # to OP_TRUE | ||||
tx_new = None | tx_new = None | ||||
tx_last = tx | tx_last = tx | ||||
▲ Show 20 Lines • Show All 111 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
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.sync_blocks([b44], True) | self.sync_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].tx, out[15].n, 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() | ||||
▲ Show 20 Lines • Show All 114 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
# | # | ||||
# b56p2 copies b57p2 but adds both tx3 and tx4. The purpose of the test is to make sure the code catches | # b56p2 copies b57p2 but adds both tx3 and tx4. The purpose of the test is to make sure the code catches | ||||
# duplicate txns that are not next to one another with the "bad-txns-duplicate" error (which indicates | # duplicate txns that are not next to one another with the "bad-txns-duplicate" error (which indicates | ||||
# that the error was caught early, avoiding a DOS vulnerability.) | # that the error was caught early, avoiding a DOS vulnerability.) | ||||
# b57 - a good block with 2 txs, don't submit until end | # b57 - a good block with 2 txs, don't submit until end | ||||
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].tx, out[16].n, 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.sync_blocks([b56], success=False, | self.sync_blocks([b56], success=False, | ||||
reject_reason='bad-txns-duplicate', reconnect=True) | 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].tx, out[16].n, 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( | ||||
Show All 22 Lines | def run_test(self): | ||||
# | # | ||||
# 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].tx.vout) < 42 | assert(len(out[17].vout) < 42) | ||||
tx.vin.append( | tx.vin.append( | ||||
CTxIn(COutPoint(out[17].tx.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.sync_blocks([b58], success=False, | self.sync_blocks([b58], success=False, | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | 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].tx, out[17].n, 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.sync_blocks([b59], success=False, | self.sync_blocks([b59], success=False, | ||||
reject_reason='bad-txns-in-belowout', reconnect=True) | 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, spend=out[17]) | b60 = self.next_block(60, spend=out[17]) | ||||
self.sync_blocks([b60], True) | self.sync_blocks([b60], True) | ||||
Show All 26 Lines | def run_test(self): | ||||
# \-> 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(60) | self.move_tip(60) | ||||
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 | ||||
assert out[18].n < len(out[18].tx.vout) | |||||
# don't set nSequence | # don't set nSequence | ||||
tx.vin.append(CTxIn(COutPoint(out[18].tx.sha256, out[18].n))) | 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.sync_blocks([b62], success=False, | self.sync_blocks([b62], success=False, | ||||
reject_reason='bad-txns-nonfinal') | reject_reason='bad-txns-nonfinal') | ||||
# Test a non-final coinbase is also rejected | # Test a non-final coinbase is also rejected | ||||
▲ Show 20 Lines • Show All 69 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
# Spend an output created in the block itself | # Spend an output created in the block itself | ||||
# | # | ||||
# -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) | # -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> 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( | tx1 = self.create_and_sign_transaction(out[19], out[19].vout[0].nValue) | ||||
out[19].tx, out[19].n, out[19].tx.vout[0].nValue) | tx2 = self.create_and_sign_transaction(tx1, 0) | ||||
tx2 = self.create_and_sign_transaction(tx1, 0, 0) | |||||
b65 = self.update_block(65, [tx1, tx2]) | b65 = self.update_block(65, [tx1, tx2]) | ||||
self.sync_blocks([b65], True) | self.sync_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 | ||||
# | # | ||||
# -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) | # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> 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( | tx1 = self.create_and_sign_transaction(out[20], out[20].vout[0].nValue) | ||||
out[20].tx, out[20].n, out[20].tx.vout[0].nValue) | tx2 = self.create_and_sign_transaction(tx1, 1) | ||||
tx2 = self.create_and_sign_transaction(tx1, 0, 1) | tx3 = self.create_and_sign_transaction(tx1, 2) | ||||
tx3 = self.create_and_sign_transaction(tx1, 0, 2) | |||||
b67 = self.update_block(67, [tx1, tx2, tx3]) | b67 = self.update_block(67, [tx1, tx2, tx3]) | ||||
self.sync_blocks([b67], success=False, | self.sync_blocks([b67], success=False, | ||||
reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | reject_reason='bad-txns-inputs-missingorspent', reconnect=True) | ||||
# More tests of block subsidy | # More tests of block subsidy | ||||
# | # | ||||
# -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) | # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> 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].tx, out[20].n, out[20].tx.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.sync_blocks([b68], success=False, | self.sync_blocks([b68], success=False, | ||||
reject_reason='bad-cb-amount', reconnect=True) | 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].tx, out[20].n, out[20].tx.vout[0].nValue - 10) | out[20], out[20].vout[0].nValue - 10) | ||||
self.update_block(69, [tx]) | self.update_block(69, [tx]) | ||||
self.sync_blocks([b69], True) | self.sync_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 | ||||
# | # | ||||
# -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) | # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) | ||||
# \-> b70 (21) | # \-> b70 (21) | ||||
Show All 19 Lines | def run_test(self): | ||||
# \-> 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, it has the same hash as b72. | # b71 is a copy of 72, but re-adds one of its transactions. However, 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].tx, out[21].n, 2) | tx1 = self.create_and_sign_transaction(out[21], 2) | ||||
tx2 = self.create_and_sign_transaction(tx1, 0, 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 | ||||
Show All 34 Lines | def run_test(self): | ||||
a[MAX_BLOCK_SIGOPS_PER_MB - 1] = int("4e", 16) # OP_PUSHDATA4 | a[MAX_BLOCK_SIGOPS_PER_MB - 1] = int("4e", 16) # OP_PUSHDATA4 | ||||
element_size = MAX_SCRIPT_ELEMENT_SIZE + 1 | element_size = MAX_SCRIPT_ELEMENT_SIZE + 1 | ||||
a[MAX_BLOCK_SIGOPS_PER_MB] = element_size % 256 | a[MAX_BLOCK_SIGOPS_PER_MB] = element_size % 256 | ||||
a[MAX_BLOCK_SIGOPS_PER_MB + 1] = element_size // 256 | a[MAX_BLOCK_SIGOPS_PER_MB + 1] = element_size // 256 | ||||
a[MAX_BLOCK_SIGOPS_PER_MB + 2] = 0 | a[MAX_BLOCK_SIGOPS_PER_MB + 2] = 0 | ||||
a[MAX_BLOCK_SIGOPS_PER_MB + 3] = 0 | a[MAX_BLOCK_SIGOPS_PER_MB + 3] = 0 | ||||
tx = self.create_and_sign_transaction(out[22].tx, 0, 1, CScript(a)) | tx = self.create_and_sign_transaction(out[22], 1, CScript(a)) | ||||
b73 = self.update_block(73, [tx]) | b73 = self.update_block(73, [tx]) | ||||
assert_equal(get_legacy_sigopcount_block( | assert_equal(get_legacy_sigopcount_block( | ||||
b73), MAX_BLOCK_SIGOPS_PER_MB + 1) | b73), MAX_BLOCK_SIGOPS_PER_MB + 1) | ||||
self.sync_blocks([b73], success=False, | self.sync_blocks([b73], success=False, | ||||
reject_reason='bad-blk-sigops', reconnect=True) | reject_reason='bad-blk-sigops', reconnect=True) | ||||
# b74/75 - if we push an invalid script element, all prevous sigops are counted, | # b74/75 - if we push an invalid script element, all prevous sigops are counted, | ||||
# but sigops after the element are not counted. | # but sigops after the element are not counted. | ||||
Show All 12 Lines | def run_test(self): | ||||
size = MAX_BLOCK_SIGOPS_PER_MB - 1 + \ | size = MAX_BLOCK_SIGOPS_PER_MB - 1 + \ | ||||
MAX_SCRIPT_ELEMENT_SIZE + 42 # total = 20,561 | MAX_SCRIPT_ELEMENT_SIZE + 42 # total = 20,561 | ||||
a = bytearray([OP_CHECKSIG] * size) | a = bytearray([OP_CHECKSIG] * size) | ||||
a[MAX_BLOCK_SIGOPS_PER_MB] = 0x4e | a[MAX_BLOCK_SIGOPS_PER_MB] = 0x4e | ||||
a[MAX_BLOCK_SIGOPS_PER_MB + 1] = 0xfe | a[MAX_BLOCK_SIGOPS_PER_MB + 1] = 0xfe | ||||
a[MAX_BLOCK_SIGOPS_PER_MB + 2] = 0xff | a[MAX_BLOCK_SIGOPS_PER_MB + 2] = 0xff | ||||
a[MAX_BLOCK_SIGOPS_PER_MB + 3] = 0xff | a[MAX_BLOCK_SIGOPS_PER_MB + 3] = 0xff | ||||
a[MAX_BLOCK_SIGOPS_PER_MB + 4] = 0xff | a[MAX_BLOCK_SIGOPS_PER_MB + 4] = 0xff | ||||
tx = self.create_and_sign_transaction(out[22].tx, 0, 1, CScript(a)) | tx = self.create_and_sign_transaction(out[22], 1, CScript(a)) | ||||
b74 = self.update_block(74, [tx]) | b74 = self.update_block(74, [tx]) | ||||
self.sync_blocks([b74], success=False, | self.sync_blocks([b74], success=False, | ||||
reject_reason='bad-blk-sigops', reconnect=True) | reject_reason='bad-blk-sigops', reconnect=True) | ||||
self.move_tip(72) | self.move_tip(72) | ||||
b75 = self.next_block(75) | b75 = self.next_block(75) | ||||
size = MAX_BLOCK_SIGOPS_PER_MB - 1 + MAX_SCRIPT_ELEMENT_SIZE + 42 | size = MAX_BLOCK_SIGOPS_PER_MB - 1 + MAX_SCRIPT_ELEMENT_SIZE + 42 | ||||
a = bytearray([OP_CHECKSIG] * size) | a = bytearray([OP_CHECKSIG] * size) | ||||
a[MAX_BLOCK_SIGOPS_PER_MB - 1] = 0x4e | a[MAX_BLOCK_SIGOPS_PER_MB - 1] = 0x4e | ||||
a[MAX_BLOCK_SIGOPS_PER_MB] = 0xff | a[MAX_BLOCK_SIGOPS_PER_MB] = 0xff | ||||
a[MAX_BLOCK_SIGOPS_PER_MB + 1] = 0xff | a[MAX_BLOCK_SIGOPS_PER_MB + 1] = 0xff | ||||
a[MAX_BLOCK_SIGOPS_PER_MB + 2] = 0xff | a[MAX_BLOCK_SIGOPS_PER_MB + 2] = 0xff | ||||
a[MAX_BLOCK_SIGOPS_PER_MB + 3] = 0xff | a[MAX_BLOCK_SIGOPS_PER_MB + 3] = 0xff | ||||
tx = self.create_and_sign_transaction(out[22].tx, 0, 1, CScript(a)) | tx = self.create_and_sign_transaction(out[22], 1, CScript(a)) | ||||
b75 = self.update_block(75, [tx]) | b75 = self.update_block(75, [tx]) | ||||
self.sync_blocks([b75], True) | self.sync_blocks([b75], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
# Check that if we push an element filled with CHECKSIGs, they are not counted | # Check that if we push an element filled with CHECKSIGs, they are not counted | ||||
self.move_tip(75) | self.move_tip(75) | ||||
b76 = self.next_block(76) | b76 = self.next_block(76) | ||||
size = MAX_BLOCK_SIGOPS_PER_MB - 1 + MAX_SCRIPT_ELEMENT_SIZE + 1 + 5 | size = MAX_BLOCK_SIGOPS_PER_MB - 1 + MAX_SCRIPT_ELEMENT_SIZE + 1 + 5 | ||||
a = bytearray([OP_CHECKSIG] * size) | a = bytearray([OP_CHECKSIG] * size) | ||||
# PUSHDATA4, but leave the following bytes as just checksigs | # PUSHDATA4, but leave the following bytes as just checksigs | ||||
a[MAX_BLOCK_SIGOPS_PER_MB - 1] = 0x4e | a[MAX_BLOCK_SIGOPS_PER_MB - 1] = 0x4e | ||||
tx = self.create_and_sign_transaction(out[23].tx, 0, 1, CScript(a)) | tx = self.create_and_sign_transaction(out[23], 1, CScript(a)) | ||||
b76 = self.update_block(76, [tx]) | b76 = self.update_block(76, [tx]) | ||||
self.sync_blocks([b76], True) | self.sync_blocks([b76], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
# Test transaction resurrection | # Test transaction resurrection | ||||
# | # | ||||
# -> b77 (24) -> b78 (25) -> b79 (26) | # -> b77 (24) -> b78 (25) -> b79 (26) | ||||
# \-> b80 (25) -> b81 (26) -> b82 (27) | # \-> b80 (25) -> b81 (26) -> b82 (27) | ||||
# | # | ||||
# b78 creates a tx, which is spent in b79. After b82, both should be in mempool | # b78 creates a tx, which is spent in b79. After b82, both should be in mempool | ||||
# | # | ||||
# The tx'es must be unsigned and pass the node's mempool policy. It is unsigned for the | # The tx'es must be unsigned and pass the node's mempool policy. It is unsigned for the | ||||
# rather obscure reason that the Python signature code does not distinguish between | # rather obscure reason that the Python signature code does not distinguish between | ||||
# Low-S and High-S values (whereas the bitcoin code has custom code which does so); | # Low-S and High-S values (whereas the bitcoin code has custom code which does so); | ||||
# as a result of which, the odds are 50% that the python code will use the right | # as a result of which, the odds are 50% that the python code will use the right | ||||
# value and the transaction will be accepted into the mempool. Until we modify the | # value and the transaction will be accepted into the mempool. Until we modify the | ||||
# test framework to support low-S signing, we are out of luck. | # test framework to support low-S signing, we are out of luck. | ||||
# | # | ||||
# To get around this issue, we construct transactions which are not signed and which | # To get around this issue, we construct transactions which are not signed and which | ||||
# spend to OP_TRUE. If the standard-ness rules change, this test would need to be | # spend to OP_TRUE. If the standard-ness rules change, this test would need to be | ||||
# updated. (Perhaps to spend to a P2SH OP_TRUE script) | # updated. (Perhaps to spend to a P2SH OP_TRUE script) | ||||
self.log.info("Test transaction resurrection during a re-org") | self.log.info("Test transaction resurrection during a re-org") | ||||
self.move_tip(76) | self.move_tip(76) | ||||
b77 = self.next_block(77) | b77 = self.next_block(77) | ||||
tx77 = self.create_and_sign_transaction( | tx77 = self.create_and_sign_transaction(out[24], 10 * COIN) | ||||
out[24].tx, out[24].n, 10 * COIN) | |||||
b77 = self.update_block(77, [tx77]) | b77 = self.update_block(77, [tx77]) | ||||
self.sync_blocks([b77], True) | self.sync_blocks([b77], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
b78 = self.next_block(78) | b78 = self.next_block(78) | ||||
tx78 = self.create_tx(tx77, 0, 9 * COIN) | tx78 = self.create_tx(tx77, 0, 9 * COIN) | ||||
b78 = self.update_block(78, [tx78]) | b78 = self.update_block(78, [tx78]) | ||||
self.sync_blocks([b78], True) | self.sync_blocks([b78], True) | ||||
Show All 33 Lines | |||||
# -> 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].tx, out[28].n, out[28].tx.vout[0].nValue, script) | out[28], out[28].vout[0].nValue, script) | ||||
tx2 = self.create_and_sign_transaction(tx1, 0, 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.sync_blocks([b83], True) | self.sync_blocks([b83], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
# Reorg on/off blocks that have OP_RETURN in them (and try to spend them) | # Reorg on/off blocks that have OP_RETURN in them (and try to spend them) | ||||
# | # | ||||
# -> b81 (26) -> b82 (27) -> b83 (28) -> b84 (29) -> b87 (30) -> b88 (31) | # -> b81 (26) -> b82 (27) -> b83 (28) -> b84 (29) -> b87 (30) -> b88 (31) | ||||
# \-> b85 (29) -> b86 (30) \-> b89a (32) | # \-> b85 (29) -> b86 (30) \-> b89a (32) | ||||
# | # | ||||
self.log.info("Test re-orging blocks with OP_RETURN in them") | self.log.info("Test re-orging blocks with OP_RETURN in them") | ||||
b84 = self.next_block(84) | b84 = self.next_block(84) | ||||
tx1 = self.create_tx(out[29].tx, out[29].n, 0, CScript([OP_RETURN])) | tx1 = self.create_tx(out[29], 0, 0, CScript([OP_RETURN])) | ||||
vout_offset = len(tx1.vout) | vout_offset = len(tx1.vout) | ||||
tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) | tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) | ||||
tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) | tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) | ||||
tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) | tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) | ||||
tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) | tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) | ||||
tx1.calc_sha256() | tx1.calc_sha256() | ||||
self.sign_tx(tx1, out[29].tx, out[29].n) | self.sign_tx(tx1, out[29]) | ||||
tx1.rehash() | tx1.rehash() | ||||
tx2 = self.create_tx(tx1, vout_offset, 0, CScript([OP_RETURN])) | tx2 = self.create_tx(tx1, vout_offset, 0, CScript([OP_RETURN])) | ||||
tx2.vout.append(CTxOut(0, CScript([OP_RETURN]))) | tx2.vout.append(CTxOut(0, CScript([OP_RETURN]))) | ||||
tx3 = self.create_tx(tx1, vout_offset + 1, 0, CScript([OP_RETURN])) | tx3 = self.create_tx(tx1, vout_offset + 1, 0, CScript([OP_RETURN])) | ||||
tx3.vout.append(CTxOut(0, CScript([OP_TRUE]))) | tx3.vout.append(CTxOut(0, CScript([OP_TRUE]))) | ||||
tx4 = self.create_tx(tx1, vout_offset + 2, 0, CScript([OP_TRUE])) | tx4 = self.create_tx(tx1, vout_offset + 2, 0, CScript([OP_TRUE])) | ||||
tx4.vout.append(CTxOut(0, CScript([OP_RETURN]))) | tx4.vout.append(CTxOut(0, CScript([OP_RETURN]))) | ||||
tx5 = self.create_tx(tx1, vout_offset + 3, 0, CScript([OP_RETURN])) | tx5 = self.create_tx(tx1, vout_offset + 3, 0, CScript([OP_RETURN])) | ||||
▲ Show 20 Lines • Show All 70 Lines • ▼ Show 20 Lines | |||||
################ | ################ | ||||
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(spend_tx, n, b"", value, script) | return create_tx_with_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 spend_tx | # this signs input 0 in tx, which is assumed to be spending output n in spend_tx | ||||
def sign_tx(self, tx, spend_tx, n): | def sign_tx(self, tx, spend_tx): | ||||
scriptPubKey = bytearray(spend_tx.vout[n].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[n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[n].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(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))]) | [self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))]) | ||||
def create_and_sign_transaction(self, spend_tx, n, value, script=CScript([OP_TRUE])): | def create_and_sign_transaction(self, spend_tx, value, script=CScript([OP_TRUE])): | ||||
tx = self.create_tx(spend_tx, n, value, script) | tx = self.create_tx(spend_tx, 0, value, script) | ||||
self.sign_tx(tx, spend_tx, n) | 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, script=CScript([OP_TRUE]), solve=True): | def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), solve=True): | ||||
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(base_block_hash, coinbase, block_time) | block = create_block(base_block_hash, coinbase, block_time) | ||||
else: | else: | ||||
# all but one satoshi to fees | # all but one satoshi to fees | ||||
coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 | coinbase.vout[0].nValue += spend.vout[0].nValue - 1 | ||||
coinbase.rehash() | coinbase.rehash() | ||||
block = create_block(base_block_hash, coinbase, block_time) | block = create_block(base_block_hash, coinbase, block_time) | ||||
# spend 1 satoshi | # spend 1 satoshi | ||||
tx = create_tx_with_script(spend.tx, spend.n, b"", 1, script) | tx = self.create_tx(spend, 0, 1, script) | ||||
self.sign_tx(tx, spend.tx, spend.n) | 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() | ||||
if solve: | if solve: | ||||
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 | ||||
# 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(self): | def save_spendable_output(self): | ||||
self.log.debug("saving spendable output {}".format(self.tip.vtx[0])) | self.log.debug("saving spendable output {}".format(self.tip.vtx[0])) | ||||
self.spendable_outputs.append(self.tip) | self.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(self): | def get_spendable_output(self): | ||||
self.log.debug("getting spendable output {}".format( | self.log.debug("getting spendable output {}".format( | ||||
self.spendable_outputs[0].vtx[0])) | self.spendable_outputs[0].vtx[0])) | ||||
return PreviousSpendableOutput(self.spendable_outputs.pop(0).vtx[0], 0) | return self.spendable_outputs.pop(0).vtx[0] | ||||
# move the tip back to a previous block | # move the tip back to a previous block | ||||
def move_tip(self, number): | def move_tip(self, number): | ||||
self.tip = self.blocks[number] | self.tip = self.blocks[number] | ||||
# adds transactions to the block and updates state | # adds transactions to the block and updates state | ||||
def update_block(self, block_number, new_transactions, reorder=True): | def update_block(self, block_number, new_transactions, reorder=True): | ||||
block = self.blocks[block_number] | block = self.blocks[block_number] | ||||
▲ Show 20 Lines • Show All 48 Lines • Show Last 20 Lines |