Changeset View
Changeset View
Standalone View
Standalone View
test/functional/feature_block_sigops.py
Show First 20 Lines • Show All 111 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
# b31 - b35 - check sigops of OP_CHECKMULTISIG / OP_CHECKMULTISIGVERIFY / OP_CHECKSIGVERIFY | # b31 - b35 - check sigops of OP_CHECKMULTISIG / OP_CHECKMULTISIGVERIFY / OP_CHECKSIGVERIFY | ||||
# | # | ||||
# setup -> ... b15 (5) -> b31 (8) -> b33 (9) -> b35 (10) | # setup -> ... b15 (5) -> b31 (8) -> b33 (9) -> b35 (10) | ||||
# \-> b36 (11) | # \-> b36 (11) | ||||
# \-> b34 (10) | # \-> b34 (10) | ||||
# \-> b32 (9) | # \-> b32 (9) | ||||
# | # | ||||
# MULTISIG: each op code counts as 20 sigops. To create the edge case, pack another 19 sigops at the end. | # MULTISIG: each op code counts as 20 sigops. To create the edge case, | ||||
# pack another 19 sigops at the end. | |||||
self.log.info( | self.log.info( | ||||
"Accept a block with the max number of OP_CHECKMULTISIG sigops") | "Accept a block with the max number of OP_CHECKMULTISIG sigops") | ||||
lots_of_multisigs = CScript( | lots_of_multisigs = CScript( | ||||
[OP_CHECKMULTISIG] * ((MAX_BLOCK_SIGOPS_PER_MB - 1) // 20) + [OP_CHECKSIG] * 19) | [OP_CHECKMULTISIG] * ((MAX_BLOCK_SIGOPS_PER_MB - 1) // 20) + [OP_CHECKSIG] * 19) | ||||
b31 = self.next_block(31, spend=out[8], script=lots_of_multisigs) | b31 = self.next_block(31, spend=out[8], script=lots_of_multisigs) | ||||
assert_equal(get_legacy_sigopcount_block(b31), MAX_BLOCK_SIGOPS_PER_MB) | assert_equal(get_legacy_sigopcount_block(b31), MAX_BLOCK_SIGOPS_PER_MB) | ||||
self.sync_blocks([b31], True) | self.sync_blocks([b31], True) | ||||
self.save_spendable_output() | self.save_spendable_output() | ||||
▲ Show 20 Lines • Show All 181 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
# The purpose of the test is to make sure those sigops are counted. | # The purpose of the test is to make sure those sigops are counted. | ||||
# | # | ||||
# script is a bytearray of size 20,526 | # script is a bytearray of size 20,526 | ||||
# | # | ||||
# bytearray[0-19,998] : OP_CHECKSIG | # bytearray[0-19,998] : OP_CHECKSIG | ||||
# bytearray[19,999] : OP_PUSHDATA4 | # bytearray[19,999] : OP_PUSHDATA4 | ||||
# bytearray[20,000-20,003]: 521 (max_script_element_size+1, in little-endian format) | # bytearray[20,000-20,003]: 521 (max_script_element_size+1, in little-endian format) | ||||
# bytearray[20,004-20,525]: unread data (script_element) | # bytearray[20,004-20,525]: unread data (script_element) | ||||
# bytearray[20,526] : OP_CHECKSIG (this puts us over the limit) | # bytearray[20,526] : OP_CHECKSIG (this puts us over the limit) | ||||
self.log.info( | self.log.info( | ||||
"Reject a block containing too many sigops after a large script element") | "Reject a block containing too many sigops after a large script element") | ||||
self.move_tip(72) | self.move_tip(72) | ||||
b73 = self.next_block(73) | b73 = self.next_block(73) | ||||
size = MAX_BLOCK_SIGOPS_PER_MB - 1 + MAX_SCRIPT_ELEMENT_SIZE + 1 + 5 + 1 | size = MAX_BLOCK_SIGOPS_PER_MB - 1 + MAX_SCRIPT_ELEMENT_SIZE + 1 + 5 + 1 | ||||
a = bytearray([OP_CHECKSIG] * size) | a = bytearray([OP_CHECKSIG] * size) | ||||
a[MAX_BLOCK_SIGOPS_PER_MB - 1] = int("4e", 16) # OP_PUSHDATA4 | a[MAX_BLOCK_SIGOPS_PER_MB - 1] = int("4e", 16) # OP_PUSHDATA4 | ||||
Show All 14 Lines | def run_test(self): | ||||
# but sigops after the element are not counted. | # but sigops after the element are not counted. | ||||
# | # | ||||
# The invalid script element is that the push_data indicates that | # The invalid script element is that the push_data indicates that | ||||
# there will be a large amount of data (0xffffff bytes), but we only | # there will be a large amount of data (0xffffff bytes), but we only | ||||
# provide a much smaller number. These bytes are CHECKSIGS so they would | # provide a much smaller number. These bytes are CHECKSIGS so they would | ||||
# cause b75 to fail for excessive sigops, if those bytes were counted. | # cause b75 to fail for excessive sigops, if those bytes were counted. | ||||
# | # | ||||
# b74 fails because we put MAX_BLOCK_SIGOPS_PER_MB+1 before the element | # b74 fails because we put MAX_BLOCK_SIGOPS_PER_MB+1 before the element | ||||
# b75 succeeds because we put MAX_BLOCK_SIGOPS_PER_MB before the element | # b75 succeeds because we put MAX_BLOCK_SIGOPS_PER_MB before the | ||||
# element | |||||
self.log.info( | self.log.info( | ||||
"Check sigops are counted correctly after an invalid script element") | "Check sigops are counted correctly after an invalid script element") | ||||
self.move_tip(72) | self.move_tip(72) | ||||
b74 = self.next_block(74) | b74 = self.next_block(74) | ||||
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 | ||||
Show All 15 Lines | def run_test(self): | ||||
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], 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], 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() | ||||
# 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(spend_tx, n, amount=value, script_pub_key=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): | 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(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, value, script=CScript([OP_TRUE])): | def create_and_sign_transaction( | ||||
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, 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 | ||||
▲ Show 20 Lines • Show All 68 Lines • ▼ Show 20 Lines | class FullBlockSigOpsTest(BitcoinTestFramework): | ||||
def reconnect_p2p(self): | def reconnect_p2p(self): | ||||
"""Tear down and bootstrap the P2P connection to the node. | """Tear down and bootstrap the P2P connection to the node. | ||||
The node gets disconnected several times in this test. This helper | The node gets disconnected several times in this test. This helper | ||||
method reconnects the p2p and restarts the network thread.""" | method reconnects the p2p and restarts the network thread.""" | ||||
self.nodes[0].disconnect_p2ps() | self.nodes[0].disconnect_p2ps() | ||||
self.bootstrap_p2p() | self.bootstrap_p2p() | ||||
def sync_blocks(self, blocks, success=True, reject_reason=None, request_block=True, reconnect=False, timeout=60): | def sync_blocks(self, blocks, success=True, reject_reason=None, | ||||
request_block=True, reconnect=False, timeout=60): | |||||
"""Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. | """Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. | ||||
Call with success = False if the tip shouldn't advance to the most recent block.""" | Call with success = False if the tip shouldn't advance to the most recent block.""" | ||||
self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, | self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, | ||||
reject_reason=reject_reason, request_block=request_block, timeout=timeout, expect_disconnect=reconnect) | reject_reason=reject_reason, request_block=request_block, timeout=timeout, expect_disconnect=reconnect) | ||||
if reconnect: | if reconnect: | ||||
self.reconnect_p2p() | self.reconnect_p2p() | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
FullBlockSigOpsTest().main() | FullBlockSigOpsTest().main() |