Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-p2p-fullblocktest.py
Show All 20 Lines | from test_framework.blocktools import ( | ||||
create_transaction, | create_transaction, | ||||
make_conform_to_ctor, | make_conform_to_ctor, | ||||
) | ) | ||||
from test_framework.cdefs import ( | from test_framework.cdefs import ( | ||||
MAX_BLOCK_SIGOPS_PER_MB, | MAX_BLOCK_SIGOPS_PER_MB, | ||||
MAX_TX_SIGOPS_COUNT, | MAX_TX_SIGOPS_COUNT, | ||||
ONE_MEGABYTE, | ONE_MEGABYTE, | ||||
) | ) | ||||
from test_framework.comptool import RejectResult, TestInstance, TestManager | |||||
from test_framework.key import CECKey | from test_framework.key import CECKey | ||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
COutPoint, | COutPoint, | ||||
CTransaction, | CTransaction, | ||||
CTxIn, | CTxIn, | ||||
CTxOut, | CTxOut, | ||||
ser_compact_size, | ser_compact_size, | ||||
ToHex, | ToHex, | ||||
) | ) | ||||
from test_framework.mininode import network_thread_start | from test_framework.mininode import network_thread_start, P2PDataStore | ||||
from test_framework.script import ( | from test_framework.script import ( | ||||
CScript, | CScript, | ||||
hash160, | hash160, | ||||
OP_2DUP, | OP_2DUP, | ||||
OP_CHECKSIG, | OP_CHECKSIG, | ||||
OP_CHECKSIGVERIFY, | OP_CHECKSIGVERIFY, | ||||
OP_EQUAL, | OP_EQUAL, | ||||
OP_HASH160, | OP_HASH160, | ||||
OP_RETURN, | OP_RETURN, | ||||
OP_TRUE, | OP_TRUE, | ||||
SIGHASH_ALL, | SIGHASH_ALL, | ||||
SIGHASH_FORKID, | SIGHASH_FORKID, | ||||
SignatureHashForkId, | SignatureHashForkId, | ||||
) | ) | ||||
from test_framework.test_framework import ComparisonTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import assert_equal | from test_framework.util import assert_equal | ||||
REPLAY_PROTECTION_START_TIME = 2000000000 | REPLAY_PROTECTION_START_TIME = 2000000000 | ||||
class PreviousSpendableOutput(): | class PreviousSpendableOutput(): | ||||
def __init__(self, tx=CTransaction(), n=-1): | def __init__(self, tx=CTransaction(), n=-1): | ||||
self.tx = tx | self.tx = tx | ||||
self.n = n # the output we're spending | # the output we're spending | ||||
self.n = n | |||||
class FullBlockTest(ComparisonTestFramework): | class FullBlockTest(BitcoinTestFramework): | ||||
# Can either run this test as 1 node with expected answers, or two and compare them. | |||||
# Change the "outcome" variable from each TestInstance object to only do | |||||
# the comparison. | |||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 1 | self.num_nodes = 1 | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
self.block_heights = {} | self.block_heights = {} | ||||
self.tip = None | self.tip = None | ||||
self.blocks = {} | self.blocks = {} | ||||
self.excessive_block_size = 100 * ONE_MEGABYTE | self.excessive_block_size = 100 * ONE_MEGABYTE | ||||
self.extra_args = [['-whitelist=127.0.0.1', | self.extra_args = [['-whitelist=127.0.0.1', | ||||
"-replayprotectionactivationtime={}".format( | "-replayprotectionactivationtime={}".format( | ||||
REPLAY_PROTECTION_START_TIME), | REPLAY_PROTECTION_START_TIME), | ||||
"-excessiveblocksize={}".format(self.excessive_block_size)]] | "-excessiveblocksize={}".format(self.excessive_block_size)]] | ||||
def add_options(self, parser): | def add_options(self, parser): | ||||
super().add_options(parser) | super().add_options(parser) | ||||
parser.add_argument( | parser.add_argument( | ||||
"--runbarelyexpensive", dest="runbarelyexpensive", default=True) | "--runbarelyexpensive", dest="runbarelyexpensive", default=True) | ||||
def run_test(self): | |||||
self.test = TestManager(self, self.options.tmpdir) | |||||
self.test.add_all_connections(self.nodes) | |||||
network_thread_start() | |||||
# Set the blocksize to 2MB as initial condition | |||||
self.nodes[0].setexcessiveblock(self.excessive_block_size) | |||||
self.test.run() | |||||
def add_transactions_to_block(self, block, tx_list): | def add_transactions_to_block(self, block, tx_list): | ||||
[tx.rehash() for tx in tx_list] | [tx.rehash() for tx in tx_list] | ||||
block.vtx.extend(tx_list) | block.vtx.extend(tx_list) | ||||
# this is a little handier to use than the version in blocktools.py | # this is a little handier to use than the version in blocktools.py | ||||
def create_tx(self, spend, value, script=CScript([OP_TRUE])): | def create_tx(self, spend, value, script=CScript([OP_TRUE])): | ||||
tx = create_transaction(spend.tx, spend.n, b"", value, script) | tx = create_transaction(spend.tx, spend.n, b"", value, script) | ||||
return tx | return tx | ||||
▲ Show 20 Lines • Show All 99 Lines • ▼ Show 20 Lines | def next_block(self, number, spend=None, script=CScript([OP_TRUE]), block_size=0, extra_sigops=0): | ||||
# Do PoW, which is cheap on regnet | # Do PoW, which is cheap on regnet | ||||
block.solve() | block.solve() | ||||
self.tip = block | self.tip = block | ||||
self.block_heights[block.sha256] = height | self.block_heights[block.sha256] = height | ||||
assert number not in self.blocks | assert number not in self.blocks | ||||
self.blocks[number] = block | self.blocks[number] = block | ||||
return block | return block | ||||
def get_tests(self): | def run_test(self): | ||||
node = self.nodes[0] | node = self.nodes[0] | ||||
node.add_p2p_connection(P2PDataStore()) | |||||
network_thread_start() | |||||
node.p2p.wait_for_verack() | |||||
# Set the blocksize to 2MB as initial condition | |||||
node.setexcessiveblock(self.excessive_block_size) | |||||
self.genesis_hash = int(node.getbestblockhash(), 16) | self.genesis_hash = int(node.getbestblockhash(), 16) | ||||
self.block_heights[self.genesis_hash] = 0 | self.block_heights[self.genesis_hash] = 0 | ||||
spendable_outputs = [] | spendable_outputs = [] | ||||
# save the current tip so it can be spent by a later block | # save the current tip so it can be spent by a later block | ||||
def save_spendable_output(): | def save_spendable_output(): | ||||
spendable_outputs.append(self.tip) | spendable_outputs.append(self.tip) | ||||
# get an output that we previously marked as spendable | # get an output that we previously marked as spendable | ||||
def get_spendable_output(): | def get_spendable_output(): | ||||
return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) | return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0) | ||||
# returns a test case that asserts that the current tip was accepted | |||||
def accepted(): | |||||
return TestInstance([[self.tip, True]]) | |||||
# returns a test case that asserts that the current tip was rejected | |||||
def rejected(reject=None): | |||||
if reject is None: | |||||
return TestInstance([[self.tip, False]]) | |||||
else: | |||||
return TestInstance([[self.tip, reject]]) | |||||
# move the tip back to a previous block | # move the tip back to a previous block | ||||
def tip(number): | def tip(number): | ||||
self.tip = self.blocks[number] | self.tip = self.blocks[number] | ||||
# adds transactions to the block and updates state | # adds transactions to the block and updates state | ||||
def update_block(block_number, new_transactions): | def update_block(block_number, new_transactions): | ||||
block = self.blocks[block_number] | block = self.blocks[block_number] | ||||
self.add_transactions_to_block(block, new_transactions) | self.add_transactions_to_block(block, new_transactions) | ||||
Show All 11 Lines | def run_test(self): | ||||
return block | return block | ||||
# shorthand for functions | # shorthand for functions | ||||
block = self.next_block | block = self.next_block | ||||
# Create a new block | # Create a new block | ||||
block(0) | block(0) | ||||
save_spendable_output() | save_spendable_output() | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
# Now we need that block to mature so we can spend the coinbase. | # Now we need that block to mature so we can spend the coinbase. | ||||
test = TestInstance(sync_every_block=False) | maturity_blocks = [] | ||||
for i in range(99): | for i in range(99): | ||||
block(5000 + i) | block(5000 + i) | ||||
test.blocks_and_transactions.append([self.tip, True]) | maturity_blocks.append(self.tip) | ||||
save_spendable_output() | save_spendable_output() | ||||
yield test | node.p2p.send_blocks_and_test(maturity_blocks, node) | ||||
# collect spendable outputs now to avoid cluttering the code later on | # collect spendable outputs now to avoid cluttering the code later on | ||||
out = [] | out = [] | ||||
for i in range(100): | for i in range(100): | ||||
out.append(get_spendable_output()) | out.append(get_spendable_output()) | ||||
# Let's build some blocks and test them. | # Let's build some blocks and test them. | ||||
for i in range(16): | for i in range(16): | ||||
n = i + 1 | n = i + 1 | ||||
block(n, spend=out[i], block_size=n * ONE_MEGABYTE) | block(n, spend=out[i], block_size=n * ONE_MEGABYTE) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
# block of maximal size | # block of maximal size | ||||
block(17, spend=out[16], block_size=self.excessive_block_size) | block(17, spend=out[16], block_size=self.excessive_block_size) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
# Reject oversized blocks with bad-blk-length error | # Reject oversized blocks with bad-blk-length error | ||||
block(18, spend=out[17], block_size=self.excessive_block_size + 1) | block(18, spend=out[17], block_size=self.excessive_block_size + 1) | ||||
yield rejected(RejectResult(16, b'bad-blk-length')) | node.p2p.send_blocks_and_test( | ||||
[self.tip], node, success=False, reject_reason='bad-blk-length') | |||||
# Rewind bad block. | # Rewind bad block. | ||||
tip(17) | tip(17) | ||||
# Accept many sigops | # Accept many sigops | ||||
lots_of_checksigs = CScript( | lots_of_checksigs = CScript( | ||||
[OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB) | [OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB) | ||||
block(19, spend=out[17], script=lots_of_checksigs, | block(19, spend=out[17], script=lots_of_checksigs, | ||||
block_size=ONE_MEGABYTE) | block_size=ONE_MEGABYTE) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
block(20, spend=out[18], script=lots_of_checksigs, | block(20, spend=out[18], script=lots_of_checksigs, | ||||
block_size=ONE_MEGABYTE, extra_sigops=1) | block_size=ONE_MEGABYTE, extra_sigops=1) | ||||
yield rejected(RejectResult(16, b'bad-blk-sigops')) | node.p2p.send_blocks_and_test( | ||||
[self.tip], node, success=False, reject_reason='bad-blk-sigops') | |||||
# Rewind bad block | # Rewind bad block | ||||
tip(19) | tip(19) | ||||
# Accept 40k sigops per block > 1MB and <= 2MB | # Accept 40k sigops per block > 1MB and <= 2MB | ||||
block(21, spend=out[18], script=lots_of_checksigs, | block(21, spend=out[18], script=lots_of_checksigs, | ||||
extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=ONE_MEGABYTE + 1) | extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=ONE_MEGABYTE + 1) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
# Accept 40k sigops per block > 1MB and <= 2MB | # Accept 40k sigops per block > 1MB and <= 2MB | ||||
block(22, spend=out[19], script=lots_of_checksigs, | block(22, spend=out[19], script=lots_of_checksigs, | ||||
extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE) | extra_sigops=MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
# Reject more than 40k sigops per block > 1MB and <= 2MB. | # Reject more than 40k sigops per block > 1MB and <= 2MB. | ||||
block(23, spend=out[20], script=lots_of_checksigs, | block(23, spend=out[20], script=lots_of_checksigs, | ||||
extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=ONE_MEGABYTE + 1) | extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=ONE_MEGABYTE + 1) | ||||
yield rejected(RejectResult(16, b'bad-blk-sigops')) | node.p2p.send_blocks_and_test( | ||||
[self.tip], node, success=False, reject_reason='bad-blk-sigops') | |||||
# Rewind bad block | # Rewind bad block | ||||
tip(22) | tip(22) | ||||
# Reject more than 40k sigops per block > 1MB and <= 2MB. | # Reject more than 40k sigops per block > 1MB and <= 2MB. | ||||
block(24, spend=out[20], script=lots_of_checksigs, | block(24, spend=out[20], script=lots_of_checksigs, | ||||
extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE) | extra_sigops=MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE) | ||||
yield rejected(RejectResult(16, b'bad-blk-sigops')) | node.p2p.send_blocks_and_test( | ||||
[self.tip], node, success=False, reject_reason='bad-blk-sigops') | |||||
# Rewind bad block | # Rewind bad block | ||||
tip(22) | tip(22) | ||||
# Accept 60k sigops per block > 2MB and <= 3MB | # Accept 60k sigops per block > 2MB and <= 3MB | ||||
block(25, spend=out[20], script=lots_of_checksigs, extra_sigops=2 * | block(25, spend=out[20], script=lots_of_checksigs, extra_sigops=2 * | ||||
MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE + 1) | MAX_BLOCK_SIGOPS_PER_MB, block_size=2 * ONE_MEGABYTE + 1) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
# Accept 60k sigops per block > 2MB and <= 3MB | # Accept 60k sigops per block > 2MB and <= 3MB | ||||
block(26, spend=out[21], script=lots_of_checksigs, | block(26, spend=out[21], script=lots_of_checksigs, | ||||
extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=3 * ONE_MEGABYTE) | extra_sigops=2 * MAX_BLOCK_SIGOPS_PER_MB, block_size=3 * ONE_MEGABYTE) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
# Reject more than 40k sigops per block > 1MB and <= 2MB. | # Reject more than 40k sigops per block > 1MB and <= 2MB. | ||||
block(27, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * | block(27, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * | ||||
MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE + 1) | MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=2 * ONE_MEGABYTE + 1) | ||||
yield rejected(RejectResult(16, b'bad-blk-sigops')) | node.p2p.send_blocks_and_test( | ||||
[self.tip], node, success=False, reject_reason='bad-blk-sigops') | |||||
# Rewind bad block | # Rewind bad block | ||||
tip(26) | tip(26) | ||||
# Reject more than 40k sigops per block > 1MB and <= 2MB. | # Reject more than 40k sigops per block > 1MB and <= 2MB. | ||||
block(28, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * | block(28, spend=out[22], script=lots_of_checksigs, extra_sigops=2 * | ||||
MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=3 * ONE_MEGABYTE) | MAX_BLOCK_SIGOPS_PER_MB + 1, block_size=3 * ONE_MEGABYTE) | ||||
yield rejected(RejectResult(16, b'bad-blk-sigops')) | node.p2p.send_blocks_and_test( | ||||
[self.tip], node, success=False, reject_reason='bad-blk-sigops') | |||||
# Rewind bad block | # Rewind bad block | ||||
tip(26) | tip(26) | ||||
# Too many sigops in one txn | # Too many sigops in one txn | ||||
too_many_tx_checksigs = CScript( | too_many_tx_checksigs = CScript( | ||||
[OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB + 1)) | [OP_CHECKSIG] * (MAX_BLOCK_SIGOPS_PER_MB + 1)) | ||||
block( | block( | ||||
29, spend=out[22], script=too_many_tx_checksigs, block_size=ONE_MEGABYTE + 1) | 29, spend=out[22], script=too_many_tx_checksigs, block_size=ONE_MEGABYTE + 1) | ||||
yield rejected(RejectResult(16, b'bad-txn-sigops')) | node.p2p.send_blocks_and_test( | ||||
[self.tip], node, success=False, reject_reason='bad-txn-sigops') | |||||
# Rewind bad block | # Rewind bad block | ||||
tip(26) | tip(26) | ||||
# Generate a key pair to test P2SH sigops count | # Generate a key pair to test P2SH sigops count | ||||
private_key = CECKey() | private_key = CECKey() | ||||
private_key.set_secretbytes(b"fatstacks") | private_key.set_secretbytes(b"fatstacks") | ||||
public_key = private_key.get_pubkey() | public_key = private_key.get_pubkey() | ||||
# P2SH | # P2SH | ||||
# Build the redeem script, hash it, use hash to create the p2sh script | # Build the redeem script, hash it, use hash to create the p2sh script | ||||
redeem_script = CScript( | redeem_script = CScript( | ||||
[public_key] + [OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) | [public_key] + [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 p2sh transaction | # Create a p2sh transaction | ||||
p2sh_tx = self.create_tx(out[22], 1, p2sh_script) | p2sh_tx = self.create_tx(out[22], 1, p2sh_script) | ||||
# Add the transaction to the block | # Add the transaction to the block | ||||
block(30) | block(30) | ||||
update_block(30, [p2sh_tx]) | update_block(30, [p2sh_tx]) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
# Creates a new transaction using the p2sh transaction included in the | # Creates a new transaction using the p2sh transaction included in the | ||||
# last block | # last block | ||||
def spend_p2sh_tx(output_script=CScript([OP_TRUE])): | def spend_p2sh_tx(output_script=CScript([OP_TRUE])): | ||||
# Create the transaction | # Create the transaction | ||||
spent_p2sh_tx = CTransaction() | spent_p2sh_tx = CTransaction() | ||||
spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b'')) | spent_p2sh_tx.vin.append(CTxIn(COutPoint(p2sh_tx.sha256, 0), b'')) | ||||
spent_p2sh_tx.vout.append(CTxOut(1, output_script)) | spent_p2sh_tx.vout.append(CTxOut(1, output_script)) | ||||
# Sign the transaction using the redeem script | # Sign the transaction using the redeem script | ||||
sighash = SignatureHashForkId( | sighash = SignatureHashForkId( | ||||
redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx.vout[0].nValue) | redeem_script, spent_p2sh_tx, 0, SIGHASH_ALL | SIGHASH_FORKID, p2sh_tx.vout[0].nValue) | ||||
sig = private_key.sign(sighash) + \ | sig = private_key.sign(sighash) + \ | ||||
bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) | bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) | ||||
spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script]) | spent_p2sh_tx.vin[0].scriptSig = CScript([sig, redeem_script]) | ||||
spent_p2sh_tx.rehash() | spent_p2sh_tx.rehash() | ||||
return spent_p2sh_tx | return spent_p2sh_tx | ||||
# Sigops p2sh limit | # Sigops p2sh limit | ||||
p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - \ | p2sh_sigops_limit = MAX_BLOCK_SIGOPS_PER_MB - \ | ||||
redeem_script.GetSigOpCount(True) | redeem_script.GetSigOpCount(True) | ||||
# Too many sigops in one p2sh txn | # Too many sigops in one p2sh txn | ||||
too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1)) | too_many_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit + 1)) | ||||
block(31, spend=out[23], block_size=ONE_MEGABYTE + 1) | block(31, spend=out[23], block_size=ONE_MEGABYTE + 1) | ||||
update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)]) | update_block(31, [spend_p2sh_tx(too_many_p2sh_sigops)]) | ||||
yield rejected(RejectResult(16, b'bad-txn-sigops')) | node.p2p.send_blocks_and_test( | ||||
[self.tip], node, success=False, reject_reason='bad-txn-sigops') | |||||
# Rewind bad block | # Rewind bad block | ||||
tip(30) | tip(30) | ||||
# Max sigops in one p2sh txn | # Max sigops in one p2sh txn | ||||
max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit)) | max_p2sh_sigops = CScript([OP_CHECKSIG] * (p2sh_sigops_limit)) | ||||
block(32, spend=out[23], block_size=ONE_MEGABYTE + 1) | block(32, spend=out[23], block_size=ONE_MEGABYTE + 1) | ||||
update_block(32, [spend_p2sh_tx(max_p2sh_sigops)]) | update_block(32, [spend_p2sh_tx(max_p2sh_sigops)]) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
# Submit a very large block via RPC | # Submit a very large block via RPC | ||||
large_block = block( | large_block = block( | ||||
33, spend=out[24], block_size=self.excessive_block_size) | 33, spend=out[24], block_size=self.excessive_block_size) | ||||
node.submitblock(ToHex(large_block)) | node.submitblock(ToHex(large_block)) | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
FullBlockTest().main() | FullBlockTest().main() |