Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-mempool-coherence-on-activations.py
Show First 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | |||||
# 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 | ||||
EXTRA_ARG = f"-replayprotectionactivationtime={ACTIVATION_TIME}" | EXTRA_ARG = f"-replayprotectionactivationtime={ACTIVATION_TIME}" | ||||
# simulation starts before activation | # simulation starts before activation | ||||
FIRST_BLOCK_TIME = ACTIVATION_TIME - 86400 | FIRST_BLOCK_TIME = ACTIVATION_TIME - 86400 | ||||
# Expected RPC error when trying to send an activation specific spend txn. | # Expected RPC error when trying to send an activation specific spend txn. | ||||
RPC_EXPECTED_ERROR = "mandatory-script-verify-flag-failed (Signature must be zero for failed CHECK(MULTI)SIG operation)" | RPC_EXPECTED_ERROR = ( | ||||
"mandatory-script-verify-flag-failed (Signature must be zero for failed" | |||||
" CHECK(MULTI)SIG operation)" | |||||
) | |||||
def create_fund_and_activation_specific_spending_tx(spend, pre_fork_only): | def create_fund_and_activation_specific_spending_tx(spend, pre_fork_only): | ||||
# Creates 2 transactions: | # Creates 2 transactions: | ||||
# 1) txfund: create outputs to be used by txspend. Must be valid pre-fork. | # 1) txfund: create outputs to be used by txspend. Must be valid pre-fork. | ||||
# 2) txspend: spending transaction that is specific to the activation | # 2) txspend: spending transaction that is specific to the activation | ||||
# being used and can be pre-fork-only or post-fork-only, depending on the | # being used and can be pre-fork-only or post-fork-only, depending on the | ||||
# function parameter. | # function parameter. | ||||
# This specific implementation uses the replay protection mechanism to | # This specific implementation uses the replay protection mechanism to | ||||
# create transactions that are only valid before or after the fork. | # create transactions that are only valid before or after the fork. | ||||
# Generate a key pair to test | # Generate a key pair to test | ||||
private_key = ECKey() | private_key = ECKey() | ||||
private_key.generate() | private_key.generate() | ||||
public_key = private_key.get_pubkey().get_bytes() | public_key = private_key.get_pubkey().get_bytes() | ||||
# Fund transaction | # Fund transaction | ||||
script = CScript([public_key, OP_CHECKSIG]) | script = CScript([public_key, OP_CHECKSIG]) | ||||
txfund = create_tx_with_script( | txfund = create_tx_with_script( | ||||
spend.tx, spend.n, b'', amount=50 * COIN, script_pub_key=script) | spend.tx, spend.n, b"", amount=50 * COIN, script_pub_key=script | ||||
) | |||||
txfund.rehash() | txfund.rehash() | ||||
# Activation specific spending tx | # Activation specific spending tx | ||||
txspend = CTransaction() | txspend = CTransaction() | ||||
txspend.vout.append(CTxOut(50 * COIN - 1000, CScript([OP_TRUE]))) | txspend.vout.append(CTxOut(50 * COIN - 1000, CScript([OP_TRUE]))) | ||||
txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b'')) | txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b"")) | ||||
# Sign the transaction | # Sign the transaction | ||||
# Use forkvalues that create pre-fork-only or post-fork-only | # Use forkvalues that create pre-fork-only or post-fork-only | ||||
# transactions. | # transactions. | ||||
forkvalue = 0 if pre_fork_only else 0xffdead | forkvalue = 0 if pre_fork_only else 0xFFDEAD | ||||
sighashtype = (forkvalue << 8) | SIGHASH_ALL | SIGHASH_FORKID | sighashtype = (forkvalue << 8) | SIGHASH_ALL | SIGHASH_FORKID | ||||
sighash = SignatureHashForkId( | sighash = SignatureHashForkId(script, txspend, 0, sighashtype, 50 * COIN) | ||||
script, txspend, 0, sighashtype, 50 * COIN) | sig = private_key.sign_ecdsa(sighash) + bytes( | ||||
sig = private_key.sign_ecdsa(sighash) + \ | bytearray([SIGHASH_ALL | SIGHASH_FORKID]) | ||||
bytes(bytearray([SIGHASH_ALL | SIGHASH_FORKID])) | ) | ||||
txspend.vin[0].scriptSig = CScript([sig]) | txspend.vin[0].scriptSig = CScript([sig]) | ||||
txspend.rehash() | txspend.rehash() | ||||
return txfund, txspend | return txfund, txspend | ||||
def create_fund_and_pre_fork_only_tx(spend): | def create_fund_and_pre_fork_only_tx(spend): | ||||
return create_fund_and_activation_specific_spending_tx( | return create_fund_and_activation_specific_spending_tx(spend, pre_fork_only=True) | ||||
spend, pre_fork_only=True) | |||||
def create_fund_and_post_fork_only_tx(spend): | def create_fund_and_post_fork_only_tx(spend): | ||||
return create_fund_and_activation_specific_spending_tx( | return create_fund_and_activation_specific_spending_tx(spend, pre_fork_only=False) | ||||
spend, pre_fork_only=False) | |||||
# ---Mempool coherence on activations test--- | # ---Mempool coherence on activations test--- | ||||
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(BitcoinTestFramework): | 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 = [[ | self.extra_args = [ | ||||
'-whitelist=noban@127.0.0.1', | [ | ||||
"-whitelist=noban@127.0.0.1", | |||||
EXTRA_ARG, | EXTRA_ARG, | ||||
'-acceptnonstdtxn=1', | "-acceptnonstdtxn=1", | ||||
'-automaticunparking=1', | "-automaticunparking=1", | ||||
]] | ] | ||||
] | |||||
def next_block(self, number): | def next_block(self, number): | ||||
if self.tip is None: | if self.tip is 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 | ||||
Show All 34 Lines | def run_test(self): | ||||
block.vtx.extend(new_transactions) | block.vtx.extend(new_transactions) | ||||
old_sha256 = block.sha256 | old_sha256 = block.sha256 | ||||
make_conform_to_ctor(block) | make_conform_to_ctor(block) | ||||
block.hashMerkleRoot = block.calc_merkle_root() | block.hashMerkleRoot = block.calc_merkle_root() | ||||
block.solve() | block.solve() | ||||
# Update the internal state just like in next_block | # Update the internal state just like in next_block | ||||
self.tip = block | self.tip = block | ||||
if block.sha256 != old_sha256: | if block.sha256 != old_sha256: | ||||
self.block_heights[ | self.block_heights[block.sha256] = self.block_heights[old_sha256] | ||||
block.sha256] = self.block_heights[old_sha256] | |||||
del self.block_heights[old_sha256] | del self.block_heights[old_sha256] | ||||
self.blocks[block_number] = block | self.blocks[block_number] = block | ||||
return block | return block | ||||
# send a txn to the mempool and check it was accepted | # send a txn to the mempool and check it was accepted | ||||
def send_transaction_to_mempool(tx): | def send_transaction_to_mempool(tx): | ||||
tx_id = node.sendrawtransaction(ToHex(tx)) | tx_id = node.sendrawtransaction(ToHex(tx)) | ||||
assert tx_id in node.getrawmempool() | assert tx_id in node.getrawmempool() | ||||
# checks the mempool has exactly the same txns as in the provided list | # checks the mempool has exactly the same txns as in the provided list | ||||
def check_mempool_equal(txns): | def check_mempool_equal(txns): | ||||
assert set(node.getrawmempool()) == {tx.hash for tx in txns} | assert set(node.getrawmempool()) == {tx.hash for tx in txns} | ||||
# Create an always-valid chained transaction. It spends a | # Create an always-valid chained transaction. It spends a | ||||
# scriptPub=OP_TRUE coin into another. Returns the transaction and its | # scriptPub=OP_TRUE coin into another. Returns the transaction and its | ||||
# spendable output for further chaining. | # spendable output for further chaining. | ||||
def create_always_valid_chained_tx(spend): | def create_always_valid_chained_tx(spend): | ||||
tx = create_tx_with_script( | tx = create_tx_with_script( | ||||
spend.tx, spend.n, b'', amount=spend.tx.vout[0].nValue - 1000, script_pub_key=CScript([OP_TRUE])) | spend.tx, | ||||
spend.n, | |||||
b"", | |||||
amount=spend.tx.vout[0].nValue - 1000, | |||||
script_pub_key=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 | ||||
# Create a new block | # Create a new block | ||||
block(0) | block(0) | ||||
Show All 28 Lines | def run_test(self): | ||||
bfork.nTime = ACTIVATION_TIME - 1 | bfork.nTime = ACTIVATION_TIME - 1 | ||||
update_block(5555, [txfund0, txfund1, txfund2, txfund3]) | update_block(5555, [txfund0, txfund1, txfund2, txfund3]) | ||||
peer.send_blocks_and_test([self.tip], node) | peer.send_blocks_and_test([self.tip], node) | ||||
for i in range(5): | for i in range(5): | ||||
peer.send_blocks_and_test([block(5200 + i)], node) | peer.send_blocks_and_test([block(5200 + i)], node) | ||||
# Check we are just before the activation time | # Check we are just before the activation time | ||||
assert_equal( | assert_equal(node.getblockchaininfo()["mediantime"], ACTIVATION_TIME - 1) | ||||
node.getblockchaininfo()['mediantime'], | |||||
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. | ||||
send_transaction_to_mempool(tx_pre0) | send_transaction_to_mempool(tx_pre0) | ||||
send_transaction_to_mempool(tx_pre1) | send_transaction_to_mempool(tx_pre1) | ||||
tx_chain0, last_chained_output = create_always_valid_chained_tx(out[4]) | tx_chain0, last_chained_output = create_always_valid_chained_tx(out[4]) | ||||
tx_chain1, last_chained_output = create_always_valid_chained_tx( | tx_chain1, last_chained_output = create_always_valid_chained_tx( | ||||
last_chained_output) | last_chained_output | ||||
) | |||||
send_transaction_to_mempool(tx_chain0) | send_transaction_to_mempool(tx_chain0) | ||||
send_transaction_to_mempool(tx_chain1) | send_transaction_to_mempool(tx_chain1) | ||||
assert_raises_rpc_error(-26, RPC_EXPECTED_ERROR, | assert_raises_rpc_error( | ||||
node.sendrawtransaction, ToHex(tx_post0)) | -26, RPC_EXPECTED_ERROR, node.sendrawtransaction, ToHex(tx_post0) | ||||
assert_raises_rpc_error(-26, RPC_EXPECTED_ERROR, | ) | ||||
node.sendrawtransaction, ToHex(tx_post1)) | assert_raises_rpc_error( | ||||
-26, RPC_EXPECTED_ERROR, 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]) | ||||
peer.send_blocks_and_test([self.tip], node) | peer.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( | ||||
node.sendrawtransaction, ToHex(tx_pre1)) | -26, RPC_EXPECTED_ERROR, 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]) | ||||
peer.send_blocks_and_test([self.tip], node) | peer.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. | ||||
▲ Show 20 Lines • Show All 50 Lines • ▼ Show 20 Lines | def run_test(self): | ||||
self.tip = self.blocks[5204] | self.tip = self.blocks[5204] | ||||
reorg_blocks = [] | reorg_blocks = [] | ||||
for i in range(3): | for i in range(3): | ||||
reorg_blocks.append(block(5900 + i)) | reorg_blocks.append(block(5900 + i)) | ||||
# Perform the reorg | # Perform the reorg | ||||
peer.send_blocks_and_test(reorg_blocks, node) | peer.send_blocks_and_test(reorg_blocks, node) | ||||
# reorg finishes after the fork | # reorg finishes after the fork | ||||
assert_equal( | assert_equal(node.getblockchaininfo()["mediantime"], ACTIVATION_TIME + 2) | ||||
node.getblockchaininfo()['mediantime'], | |||||
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() |