Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-mempool-coherence-on-activations.py
Show All 19 Lines | |||||
""" | """ | ||||
from test_framework.blocktools import ( | from test_framework.blocktools import ( | ||||
create_block, | create_block, | ||||
create_coinbase, | create_coinbase, | ||||
create_transaction, | create_transaction, | ||||
make_conform_to_ctor, | make_conform_to_ctor, | ||||
) | ) | ||||
from test_framework.comptool import TestInstance, TestManager | |||||
from test_framework.key import CECKey | from test_framework.key import CECKey | ||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
COIN, | COIN, | ||||
COutPoint, | COutPoint, | ||||
CTransaction, | CTransaction, | ||||
CTxIn, | CTxIn, | ||||
CTxOut, | CTxOut, | ||||
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, | ||||
OP_CHECKSIG, | OP_CHECKSIG, | ||||
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, assert_raises_rpc_error | from test_framework.util import assert_equal, assert_raises_rpc_error | ||||
# ---Code specific to the activation used for this test--- | # ---Code specific to the activation used for this test--- | ||||
# It might change depending on the activation code currently existing in the | # It might change depending on the activation code currently existing in the | ||||
# client software. We use the replay protection activation for this test. | # client software. We use the replay protection activation for this test. | ||||
ACTIVATION_TIME = 2000000000 | ACTIVATION_TIME = 2000000000 | ||||
▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | |||||
class PreviousSpendableOutput(object): | class PreviousSpendableOutput(object): | ||||
def __init__(self, tx=CTransaction(), n=-1): | def __init__(self, tx=CTransaction(), n=-1): | ||||
self.tx = tx | self.tx = tx | ||||
self.n = n | self.n = n | ||||
class MempoolCoherenceOnActivationsTest(ComparisonTestFramework): | class MempoolCoherenceOnActivationsTest(BitcoinTestFramework): | ||||
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', | ||||
EXTRA_ARG]] | EXTRA_ARG]] | ||||
def run_test(self): | |||||
self.test = TestManager(self, self.options.tmpdir) | |||||
self.test.add_all_connections(self.nodes) | |||||
network_thread_start() | |||||
self.nodes[0].setmocktime(ACTIVATION_TIME) | |||||
self.test.run() | |||||
def next_block(self, number): | def next_block(self, number): | ||||
if self.tip == None: | if self.tip == None: | ||||
base_block_hash = self.genesis_hash | base_block_hash = self.genesis_hash | ||||
block_time = FIRST_BLOCK_TIME | block_time = FIRST_BLOCK_TIME | ||||
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) | coinbase = create_coinbase(height) | ||||
coinbase.rehash() | coinbase.rehash() | ||||
block = create_block(base_block_hash, coinbase, block_time) | block = create_block(base_block_hash, coinbase, block_time) | ||||
# 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): | ||||
self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16) | node = self.nodes[0] | ||||
node.add_p2p_connection(P2PDataStore()) | |||||
network_thread_start() | |||||
node.p2p.wait_for_verack() | |||||
node.setmocktime(ACTIVATION_TIME) | |||||
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] | ||||
block.vtx.extend(new_transactions) | block.vtx.extend(new_transactions) | ||||
Show All 25 Lines | def run_test(self): | ||||
def create_always_valid_chained_tx(spend): | def create_always_valid_chained_tx(spend): | ||||
tx = create_transaction( | tx = create_transaction( | ||||
spend.tx, spend.n, b'', spend.tx.vout[0].nValue - 1000, CScript([OP_TRUE])) | spend.tx, spend.n, b'', spend.tx.vout[0].nValue - 1000, CScript([OP_TRUE])) | ||||
tx.rehash() | tx.rehash() | ||||
return tx, PreviousSpendableOutput(tx, 0) | return tx, PreviousSpendableOutput(tx, 0) | ||||
# shorthand | # shorthand | ||||
block = self.next_block | block = self.next_block | ||||
node = self.nodes[0] | |||||
# 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(110): | for i in range(110): | ||||
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()) | ||||
# Create 2 pre-fork-only txns (tx_pre0, tx_pre1). Fund txns are valid | # Create 2 pre-fork-only txns (tx_pre0, tx_pre1). Fund txns are valid | ||||
# pre-fork, so we can mine them right away. | # pre-fork, so we can mine them right away. | ||||
txfund0, tx_pre0 = create_fund_and_pre_fork_only_tx(out[0]) | txfund0, tx_pre0 = create_fund_and_pre_fork_only_tx(out[0]) | ||||
txfund1, tx_pre1 = create_fund_and_pre_fork_only_tx(out[1]) | txfund1, tx_pre1 = create_fund_and_pre_fork_only_tx(out[1]) | ||||
# Create 2 post-fork-only txns (tx_post0, tx_post1). Fund txns are | # Create 2 post-fork-only txns (tx_post0, tx_post1). Fund txns are | ||||
# valid pre-fork, so we can mine them right away. | # valid pre-fork, so we can mine them right away. | ||||
txfund2, tx_post0 = create_fund_and_post_fork_only_tx(out[2]) | txfund2, tx_post0 = create_fund_and_post_fork_only_tx(out[2]) | ||||
txfund3, tx_post1 = create_fund_and_post_fork_only_tx(out[3]) | txfund3, tx_post1 = create_fund_and_post_fork_only_tx(out[3]) | ||||
# Create blocks to activate the fork. Mine all funding transactions. | # Create blocks to activate the fork. Mine all funding transactions. | ||||
bfork = block(5555) | bfork = block(5555) | ||||
bfork.nTime = ACTIVATION_TIME - 1 | bfork.nTime = ACTIVATION_TIME - 1 | ||||
update_block(5555, [txfund0, txfund1, txfund2, txfund3]) | update_block(5555, [txfund0, txfund1, txfund2, txfund3]) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
for i in range(5): | for i in range(5): | ||||
block(5200 + i) | node.p2p.send_blocks_and_test([block(5200 + i)], node) | ||||
test.blocks_and_transactions.append([self.tip, True]) | |||||
yield test | |||||
# Check we are just before the activation time | # Check we are just before the activation time | ||||
assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], | assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], | ||||
ACTIVATION_TIME - 1) | ACTIVATION_TIME - 1) | ||||
# We are just before the fork. Pre-fork-only and always-valid chained | # We are just before the fork. Pre-fork-only and always-valid chained | ||||
# txns (tx_chain0, tx_chain1) are valid, post-fork-only txns are | # txns (tx_chain0, tx_chain1) are valid, post-fork-only txns are | ||||
# rejected. | # rejected. | ||||
Show All 9 Lines | def run_test(self): | ||||
assert_raises_rpc_error(-26, RPC_EXPECTED_ERROR, | assert_raises_rpc_error(-26, RPC_EXPECTED_ERROR, | ||||
node.sendrawtransaction, ToHex(tx_post1)) | node.sendrawtransaction, ToHex(tx_post1)) | ||||
check_mempool_equal([tx_chain0, tx_chain1, tx_pre0, tx_pre1]) | check_mempool_equal([tx_chain0, tx_chain1, tx_pre0, tx_pre1]) | ||||
# Activate the fork. Mine the 1st always-valid chained txn and a | # Activate the fork. Mine the 1st always-valid chained txn and a | ||||
# pre-fork-only txn. | # pre-fork-only txn. | ||||
block(5556) | block(5556) | ||||
update_block(5556, [tx_chain0, tx_pre0]) | update_block(5556, [tx_chain0, tx_pre0]) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
forkblockid = node.getbestblockhash() | forkblockid = node.getbestblockhash() | ||||
# Check we just activated the fork | # Check we just activated the fork | ||||
assert_equal(node.getblockheader(forkblockid)['mediantime'], | assert_equal(node.getblockheader(forkblockid)['mediantime'], | ||||
ACTIVATION_TIME) | ACTIVATION_TIME) | ||||
# Check mempool coherence when activating the fork. Pre-fork-only txns | # Check mempool coherence when activating the fork. Pre-fork-only txns | ||||
# were evicted from the mempool, while always-valid txns remain. | # were evicted from the mempool, while always-valid txns remain. | ||||
# Evicted: tx_pre1 | # Evicted: tx_pre1 | ||||
check_mempool_equal([tx_chain1]) | check_mempool_equal([tx_chain1]) | ||||
# Post-fork-only and always-valid txns are accepted, pre-fork-only txn | # Post-fork-only and always-valid txns are accepted, pre-fork-only txn | ||||
# are rejected. | # are rejected. | ||||
send_transaction_to_mempool(tx_post0) | send_transaction_to_mempool(tx_post0) | ||||
send_transaction_to_mempool(tx_post1) | send_transaction_to_mempool(tx_post1) | ||||
tx_chain2, _ = create_always_valid_chained_tx(last_chained_output) | tx_chain2, _ = create_always_valid_chained_tx(last_chained_output) | ||||
send_transaction_to_mempool(tx_chain2) | send_transaction_to_mempool(tx_chain2) | ||||
assert_raises_rpc_error(-26, RPC_EXPECTED_ERROR, | assert_raises_rpc_error(-26, RPC_EXPECTED_ERROR, | ||||
node.sendrawtransaction, ToHex(tx_pre1)) | node.sendrawtransaction, ToHex(tx_pre1)) | ||||
check_mempool_equal([tx_chain1, tx_chain2, tx_post0, tx_post1]) | check_mempool_equal([tx_chain1, tx_chain2, tx_post0, tx_post1]) | ||||
# Mine the 2nd always-valid chained txn and a post-fork-only txn. | # Mine the 2nd always-valid chained txn and a post-fork-only txn. | ||||
block(5557) | block(5557) | ||||
update_block(5557, [tx_chain1, tx_post0]) | update_block(5557, [tx_chain1, tx_post0]) | ||||
yield accepted() | node.p2p.send_blocks_and_test([self.tip], node) | ||||
postforkblockid = node.getbestblockhash() | postforkblockid = node.getbestblockhash() | ||||
# The mempool contains the 3rd chained txn and a post-fork-only txn. | # The mempool contains the 3rd chained txn and a post-fork-only txn. | ||||
check_mempool_equal([tx_chain2, tx_post1]) | check_mempool_equal([tx_chain2, tx_post1]) | ||||
# In the following we will testing block disconnections and reorgs. | # In the following we will testing block disconnections and reorgs. | ||||
# - tx_chain2 will always be retained in the mempool since it is always | # - tx_chain2 will always be retained in the mempool since it is always | ||||
# valid. Its continued presence shows that we are never simply | # valid. Its continued presence shows that we are never simply | ||||
# clearing the entire mempool. | # clearing the entire mempool. | ||||
Show All 37 Lines | def run_test(self): | ||||
# post-fork-only transactions are not unnecessarily discarded from | # post-fork-only transactions are not unnecessarily discarded from | ||||
# the mempool in such a reorg. Pre-fork-only transactions however can | # the mempool in such a reorg. Pre-fork-only transactions however can | ||||
# get lost. | # get lost. | ||||
# Set up a longer competing chain that doesn't confirm any of our txns. | # Set up a longer competing chain that doesn't confirm any of our txns. | ||||
# This starts after 5204, so it contains neither the forkblockid nor | # This starts after 5204, so it contains neither the forkblockid nor | ||||
# the postforkblockid from above. | # the postforkblockid from above. | ||||
tip(5204) | tip(5204) | ||||
test = TestInstance(sync_every_block=False) | reorg_blocks = [] | ||||
for i in range(3): | for i in range(3): | ||||
block(5900 + i) | reorg_blocks.append(block(5900 + i)) | ||||
test.blocks_and_transactions.append([self.tip, True]) | |||||
# Perform the reorg | # Perform the reorg | ||||
yield test | node.p2p.send_blocks_and_test(reorg_blocks, node) | ||||
# reorg finishes after the fork | # reorg finishes after the fork | ||||
assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], | assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], | ||||
ACTIVATION_TIME+2) | ACTIVATION_TIME+2) | ||||
# In old mempool: tx_chain2, tx_post1 | # In old mempool: tx_chain2, tx_post1 | ||||
# Recovered from blocks: tx_chain0, tx_chain1, tx_post0 | # Recovered from blocks: tx_chain0, tx_chain1, tx_post0 | ||||
# Lost from blocks: tx_pre0 | # Lost from blocks: tx_pre0 | ||||
# Retained from old mempool: tx_chain2, tx_post1 | # Retained from old mempool: tx_chain2, tx_post1 | ||||
# Evicted from old mempool: NONE | # Evicted from old mempool: NONE | ||||
check_mempool_equal( | check_mempool_equal( | ||||
[tx_chain0, tx_chain1, tx_chain2, tx_post0, tx_post1]) | [tx_chain0, tx_chain1, tx_chain2, tx_post0, tx_post1]) | ||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
MempoolCoherenceOnActivationsTest().main() | MempoolCoherenceOnActivationsTest().main() |