Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-p2p-fullblocktest.py
Show All 14 Lines | |||||
from test_framework.util import * | from test_framework.util import * | ||||
from test_framework.comptool import TestManager, TestInstance, RejectResult | from test_framework.comptool import TestManager, TestInstance, RejectResult | ||||
from test_framework.blocktools import * | from test_framework.blocktools import * | ||||
import time | import time | ||||
from test_framework.key import CECKey | from test_framework.key import CECKey | ||||
from test_framework.script import * | from test_framework.script import * | ||||
from test_framework.cdefs import (ONE_MEGABYTE, LEGACY_MAX_BLOCK_SIZE, | from test_framework.cdefs import (ONE_MEGABYTE, LEGACY_MAX_BLOCK_SIZE, | ||||
MAX_BLOCK_SIGOPS_PER_MB, MAX_TX_SIGOPS_COUNT) | MAX_BLOCK_SIGOPS_PER_MB, MAX_TX_SIGOPS_COUNT) | ||||
from collections import deque | |||||
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 | self.n = n # the output we're spending | ||||
Show All 34 Lines | class FullBlockTest(ComparisonTestFramework): | ||||
# Can either run this test as 1 node with expected answers, or two and compare them. | # 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 | # Change the "outcome" variable from each TestInstance object to only do | ||||
# the comparison. | # 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.coinbase_key = CECKey() | |||||
self.coinbase_key.set_secretbytes(b"fatstacks") | |||||
self.coinbase_pubkey = self.coinbase_key.get_pubkey() | |||||
self.tip = None | self.tip = None | ||||
self.blocks = {} | self.blocks = {} | ||||
self.excessive_block_size = 64 * ONE_MEGABYTE | self.excessive_block_size = 100 * ONE_MEGABYTE | ||||
self.extra_args = [['-norelaypriority', | self.extra_args = [['-norelaypriority', | ||||
'-whitelist=127.0.0.1', | '-whitelist=127.0.0.1', | ||||
'-limitancestorcount=9999', | '-limitancestorcount=9999', | ||||
'-limitancestorsize=9999', | '-limitancestorsize=9999', | ||||
'-limitdescendantcount=9999', | '-limitdescendantcount=9999', | ||||
'-limitdescendantsize=9999', | '-limitdescendantsize=9999', | ||||
'-maxmempool=999', | '-maxmempool=999', | ||||
"-excessiveblocksize=%d" | "-excessiveblocksize=%d" | ||||
Show All 17 Lines | 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])): | ||||
tx = create_transaction(spend_tx, n, b"", value, script) | tx = create_transaction(spend_tx, n, b"", value, script) | ||||
return tx | return tx | ||||
# sign a transaction, using the key we know about | def next_block(self, number, spend=None, script=CScript([OP_TRUE]), block_size=0, extra_sigops=0): | ||||
# 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): | |||||
scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey) | |||||
if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend | |||||
tx.vin[0].scriptSig = CScript() | |||||
return | |||||
sighash = SignatureHashForkId( | |||||
spend_tx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend_tx.vout[n].nValue) | |||||
tx.vin[0].scriptSig = CScript( | |||||
[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])): | |||||
tx = self.create_tx(spend_tx, n, value, script) | |||||
self.sign_tx(tx, spend_tx, n) | |||||
tx.rehash() | |||||
return tx | |||||
def next_block(self, number, spend=None, additional_coinbase_value=0, script=None, extra_sigops=0, block_size=0, solve=True): | |||||
""" | |||||
Create a block on top of self.tip, and advance self.tip to point to the new block | |||||
if spend is specified, then 1 satoshi will be spent from that to an anyone-can-spend | |||||
output, and rest will go to fees. | |||||
""" | |||||
if self.tip == None: | if self.tip == 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) | ||||
coinbase.vout[0].nValue += additional_coinbase_value | |||||
if (spend != None): | |||||
coinbase.vout[0].nValue += spend.tx.vout[ | |||||
spend.n].nValue - 1 # all but one satoshi to fees | |||||
coinbase.rehash() | coinbase.rehash() | ||||
if spend == None: | |||||
# We need to have something to spend to fill the block. | |||||
assert_equal(block_size, 0) | |||||
block = create_block(base_block_hash, coinbase, block_time) | block = create_block(base_block_hash, coinbase, block_time) | ||||
spendable_output = None | |||||
if (spend != None): | |||||
tx = CTransaction() | |||||
# no signature yet | |||||
tx.vin.append( | |||||
CTxIn(COutPoint(spend.tx.sha256, spend.n), b"", 0xffffffff)) | |||||
# We put some random data into the first transaction of the chain | |||||
# to randomize ids | |||||
tx.vout.append( | |||||
CTxOut(0, CScript([random.randint(0, 255), OP_DROP, OP_TRUE]))) | |||||
if script == None: | |||||
tx.vout.append(CTxOut(1, CScript([OP_TRUE]))) | |||||
else: | else: | ||||
# all but one satoshi to fees | |||||
coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 | |||||
coinbase.rehash() | |||||
block = create_block(base_block_hash, coinbase, block_time) | |||||
# spend 1 satoshi | |||||
tx = CTransaction() | |||||
tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n))) | |||||
# Make sure we have plenty engough to spend going forward. | |||||
spendable_outputs = deque([]) | |||||
def add_spendable_outputs(tx): | |||||
for i in range(8): | |||||
tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) | |||||
spendable_outputs.append(PreviousSpendableOutput(tx, i)) | |||||
add_spendable_outputs(tx) | |||||
# Make it the same format as transaction added for padding and save the size. | |||||
# It's missing the padding output, so we add a constant to account for it. | |||||
tx.rehash() | |||||
base_tx_size = len(tx.serialize()) + 18 | |||||
# If a specific script is required, add it. | |||||
if script != None: | |||||
tx.vout.append(CTxOut(1, script)) | tx.vout.append(CTxOut(1, script)) | ||||
spendable_output = PreviousSpendableOutput(tx, 0) | |||||
# Now sign it if necessary | # Put some random data into the first transaction of the chain to randomize ids. | ||||
scriptSig = b"" | tx.vout.append( | ||||
scriptPubKey = bytearray(spend.tx.vout[spend.n].scriptPubKey) | CTxOut(0, CScript([random.randint(0, 256), OP_RETURN]))) | ||||
if (scriptPubKey[0] == OP_TRUE): # looks like an anyone-can-spend | |||||
scriptSig = CScript([OP_TRUE]) | # Add the transaction to the block | ||||
else: | |||||
# We have to actually sign it | |||||
sighash = SignatureHashForkId( | |||||
spend.tx.vout[spend.n].scriptPubKey, tx, 0, SIGHASH_ALL | SIGHASH_FORKID, spend.tx.vout[spend.n].nValue) | |||||
scriptSig = CScript( | |||||
[self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID]))]) | |||||
tx.vin[0].scriptSig = scriptSig | |||||
# Now add the transaction to the block | |||||
self.add_transactions_to_block(block, [tx]) | self.add_transactions_to_block(block, [tx]) | ||||
block.hashMerkleRoot = block.calc_merkle_root() | |||||
if spendable_output != None and block_size > 0: | # If we have a block size requirement, just fill | ||||
while len(block.serialize()) < block_size: | # the block until we get there | ||||
current_block_size = len(block.serialize()) | |||||
while current_block_size < block_size: | |||||
# We will add a new transaction. That means the size of | |||||
# the field enumerating how many transaction go in the block | |||||
# may change. | |||||
current_block_size -= len(ser_compact_size(len(block.vtx))) | |||||
current_block_size += len(ser_compact_size(len(block.vtx) + 1)) | |||||
# Create the new transaction | |||||
tx = CTransaction() | tx = CTransaction() | ||||
script_length = block_size - len(block.serialize()) - 79 | # Spend from one of the spendable outputs | ||||
spend = spendable_outputs.popleft() | |||||
tx.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n))) | |||||
# Add spendable outputs | |||||
add_spendable_outputs(tx) | |||||
# Add padding to fill the block. | |||||
script_length = block_size - current_block_size - base_tx_size | |||||
if script_length > 510000: | if script_length > 510000: | ||||
script_length = 500000 | script_length = 500000 | ||||
tx_sigops = min( | tx_sigops = min(extra_sigops, script_length, | ||||
extra_sigops, script_length, MAX_TX_SIGOPS_COUNT) | MAX_TX_SIGOPS_COUNT) | ||||
extra_sigops -= tx_sigops | extra_sigops -= tx_sigops | ||||
script_pad_len = script_length - tx_sigops | script_pad_len = script_length - tx_sigops | ||||
script_output = CScript( | script_output = CScript( | ||||
[b'\x00' * script_pad_len] + [OP_CHECKSIG] * tx_sigops) | [b'\x00' * script_pad_len] + [OP_CHECKSIG] * tx_sigops) | ||||
tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) | |||||
tx.vout.append(CTxOut(0, script_output)) | tx.vout.append(CTxOut(0, script_output)) | ||||
tx.vin.append( | |||||
CTxIn(COutPoint(spendable_output.tx.sha256, spendable_output.n))) | # Add the tx to the list of transactions to be included | ||||
spendable_output = PreviousSpendableOutput(tx, 0) | # in the block. | ||||
self.add_transactions_to_block(block, [tx]) | self.add_transactions_to_block(block, [tx]) | ||||
current_block_size += len(tx.serialize()) | |||||
# Now that we added a bunch of transaction, we need to recompute | |||||
# the merkle root. | |||||
block.hashMerkleRoot = block.calc_merkle_root() | block.hashMerkleRoot = block.calc_merkle_root() | ||||
# Make sure the math above worked out to produce the correct block size | |||||
# (the math will fail if there are too many transactions in the block) | # Check that the block size is what's expected | ||||
if block_size > 0: | |||||
assert_equal(len(block.serialize()), block_size) | assert_equal(len(block.serialize()), block_size) | ||||
# Make sure all the requested sigops have been included | |||||
assert_equal(extra_sigops, 0) | # Do PoW, which is cheap on regnet | ||||
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 | ||||
def get_tests(self): | def get_tests(self): | ||||
self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) | self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) | ||||
▲ Show 20 Lines • Show All 74 Lines • ▼ Show 20 Lines | def get_tests(self): | ||||
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')) | yield rejected(RejectResult(16, b'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 - 1)) | [OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB) | ||||
block( | block(19, spend=out[17], script=lots_of_checksigs, | ||||
19, spend=out[17], script=lots_of_checksigs, block_size=ONE_MEGABYTE) | block_size=ONE_MEGABYTE) | ||||
yield accepted() | yield accepted() | ||||
too_many_blk_checksigs = CScript( | block(20, spend=out[18], script=lots_of_checksigs, | ||||
[OP_CHECKSIG] * MAX_BLOCK_SIGOPS_PER_MB) | block_size=ONE_MEGABYTE, extra_sigops=1) | ||||
block( | |||||
20, spend=out[18], script=too_many_blk_checksigs, block_size=ONE_MEGABYTE) | |||||
yield rejected(RejectResult(16, b'bad-blk-sigops')) | yield rejected(RejectResult(16, b'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) | ||||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | def get_tests(self): | ||||
[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')) | yield rejected(RejectResult(16, b'bad-txn-sigops')) | ||||
# Rewind bad block | # Rewind bad block | ||||
tip(26) | tip(26) | ||||
# Generate a key pair to test P2SH sigops count | |||||
private_key = CECKey() | |||||
private_key.set_secretbytes(b"fatstacks") | |||||
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([self.coinbase_pubkey] + [ | redeem_script = CScript( | ||||
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_and_sign_transaction( | p2sh_tx = self.create_tx(out[22].tx, out[22].n, 1, p2sh_script) | ||||
out[22].tx, out[22].n, 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() | yield accepted() | ||||
# 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 = self.coinbase_key.sign(sighash) + bytes( | sig = private_key.sign(sighash) + \ | ||||
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 | ||||
▲ Show 20 Lines • Show All 94 Lines • Show Last 20 Lines |