Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-transaction-ordering.py
Show All 10 Lines | |||||
import random | import random | ||||
import time | import time | ||||
from test_framework.blocktools import ( | from test_framework.blocktools import ( | ||||
create_block, | create_block, | ||||
create_coinbase, | create_coinbase, | ||||
make_conform_to_ctor, | make_conform_to_ctor, | ||||
) | ) | ||||
from test_framework.comptool import RejectResult, TestInstance, TestManager | |||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
COutPoint, | COutPoint, | ||||
CTransaction, | CTransaction, | ||||
CTxIn, | CTxIn, | ||||
CTxOut, | CTxOut, | ||||
) | ) | ||||
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, | ||||
OP_RETURN, | OP_RETURN, | ||||
OP_TRUE, | OP_TRUE, | ||||
) | ) | ||||
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 | ||||
# far into the future | # far into the future | ||||
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 | self.n = n # the output we're spending | ||||
class TransactionOrderingTest(ComparisonTestFramework): | class TransactionOrderingTest(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.extra_args = [['-whitelist=127.0.0.1', | self.extra_args = [['-whitelist=127.0.0.1', | ||||
'-relaypriority=0', | '-relaypriority=0', | ||||
"-replayprotectionactivationtime={}".format(REPLAY_PROTECTION_START_TIME)]] | "-replayprotectionactivationtime={}".format(REPLAY_PROTECTION_START_TIME)]] | ||||
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.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) | ||||
def next_block(self, number, spend=None, tx_count=0): | def next_block(self, number, spend=None, tx_count=0): | ||||
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 | ||||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | def next_block(self, number, spend=None, tx_count=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() | |||||
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 9 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(17): | for i in range(17): | ||||
n = i + 1 | n = i + 1 | ||||
block(n) | node.p2p.send_blocks_and_test([block(n)], node) | ||||
yield accepted() | |||||
block(5556) | node.p2p.send_blocks_and_test([block(5556)], node) | ||||
yield accepted() | |||||
# Block with regular ordering are now rejected. | # Block with regular ordering are now rejected. | ||||
block(5557, out[17], tx_count=16) | node.p2p.send_blocks_and_test([block( | ||||
yield rejected(RejectResult(16, b'tx-ordering')) | 5557, out[17], tx_count=16)], node, success=False, reject_reason='tx-ordering') | ||||
# Rewind bad block. | # Rewind bad block. | ||||
tip(5556) | tip(5556) | ||||
# After we activate the Nov 15, 2018 HF, transaction order is enforced. | # After we activate the Nov 15, 2018 HF, transaction order is enforced. | ||||
def ordered_block(block_number, spend): | def ordered_block(block_number, spend): | ||||
b = block(block_number, spend=spend, tx_count=16) | b = block(block_number, spend=spend, tx_count=16) | ||||
make_conform_to_ctor(b) | make_conform_to_ctor(b) | ||||
update_block(block_number) | update_block(block_number) | ||||
return b | return b | ||||
# Now that the fork activated, we need to order transaction per txid. | # Now that the fork activated, we need to order transaction per txid. | ||||
ordered_block(4445, out[17]) | node.p2p.send_blocks_and_test([ordered_block(4445, out[17])], node) | ||||
yield accepted() | node.p2p.send_blocks_and_test([ordered_block(4446, out[18])], node) | ||||
ordered_block(4446, out[18]) | |||||
yield accepted() | |||||
# Generate a block with a duplicated transaction. | # Generate a block with a duplicated transaction. | ||||
double_tx_block = ordered_block(4447, out[19]) | double_tx_block = ordered_block(4447, out[19]) | ||||
assert_equal(len(double_tx_block.vtx), 16) | assert_equal(len(double_tx_block.vtx), 16) | ||||
double_tx_block.vtx = double_tx_block.vtx[:8] + \ | double_tx_block.vtx = double_tx_block.vtx[:8] + \ | ||||
[double_tx_block.vtx[8]] + double_tx_block.vtx[8:] | [double_tx_block.vtx[8]] + double_tx_block.vtx[8:] | ||||
update_block(4447) | update_block(4447) | ||||
yield rejected(RejectResult(16, b'bad-txns-duplicate')) | node.p2p.send_blocks_and_test( | ||||
[self.tip], node, success=False, reject_reason='bad-txns-duplicate') | |||||
# Rewind bad block. | # Rewind bad block. | ||||
tip(4446) | tip(4446) | ||||
# Check over two blocks. | # Check over two blocks. | ||||
proper_block = ordered_block(4448, out[20]) | proper_block = ordered_block(4448, out[20]) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
replay_tx_block = ordered_block(4449, out[21]) | replay_tx_block = ordered_block(4449, out[21]) | ||||
assert_equal(len(replay_tx_block.vtx), 16) | assert_equal(len(replay_tx_block.vtx), 16) | ||||
replay_tx_block.vtx.append(proper_block.vtx[5]) | replay_tx_block.vtx.append(proper_block.vtx[5]) | ||||
replay_tx_block.vtx = [replay_tx_block.vtx[0]] + \ | replay_tx_block.vtx = [replay_tx_block.vtx[0]] + \ | ||||
sorted(replay_tx_block.vtx[1:], key=lambda tx: tx.get_id()) | sorted(replay_tx_block.vtx[1:], key=lambda tx: tx.get_id()) | ||||
update_block(4449) | update_block(4449) | ||||
yield rejected(RejectResult(16, b'bad-txns-BIP30')) | node.p2p.send_blocks_and_test( | ||||
[self.tip], node, success=False, reject_reason='bad-txns-BIP30') | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
TransactionOrderingTest().main() | TransactionOrderingTest().main() |