Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-replay-protection.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2015-2016 The Bitcoin Core developers | # Copyright (c) 2015-2016 The Bitcoin Core developers | ||||
# Copyright (c) 2017 The Bitcoin developers | # Copyright (c) 2017 The Bitcoin developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
""" | """ | ||||
This test checks activation of UAHF and the different consensus | This test checks activation of UAHF and the different consensus | ||||
markblundeberg: Can add a comment up here that the replay protection "upgrade" serves as a good generic example… | |||||
related to this activation. | related to this activation. | ||||
It is derived from the much more complex p2p-fullblocktest. | It is derived from the much more complex p2p-fullblocktest. | ||||
""" | """ | ||||
from test_framework.test_framework import ComparisonTestFramework | from test_framework.test_framework import ComparisonTestFramework | ||||
from test_framework.util import assert_equal, assert_raises_rpc_error | from test_framework.util import assert_equal, assert_raises_rpc_error | ||||
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 * | ||||
▲ Show 20 Lines • Show All 148 Lines • ▼ Show 20 Lines | def get_tests(self): | ||||
script, txspend, 0, sighashtype, 50 * COIN) | script, txspend, 0, sighashtype, 50 * COIN) | ||||
sig = private_key.sign(sighash) + \ | sig = private_key.sign(sighash) + \ | ||||
bytes(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] | ||||
# Creates a chainable tx, spending a scriptPub=OP_TRUE coin into | |||||
# another | |||||
def create_chaining_tx(spend): | |||||
if isinstance(spend, CTransaction): | |||||
# After the first iteration, we start calling this function | |||||
tx = create_transaction( | |||||
spend, 0, b'', spend.vout[0].nValue - 1000, CScript([OP_TRUE])) | |||||
else: | |||||
tx = create_transaction( | |||||
spend.tx, spend.n, b'', 50 * COIN, CScript([OP_TRUE])) | |||||
tx.rehash() | |||||
return tx | |||||
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 set(node.getrawmempool())) | assert(tx_id in set(node.getrawmempool())) | ||||
return tx_id | return tx_id | ||||
# Before the fork, no replay protection required to get in the mempool. | # Before the fork, no replay protection required to get in the mempool. | ||||
txns = create_fund_and_spend_tx(out[0]) | txns = create_fund_and_spend_tx(out[0]) | ||||
send_transaction_to_mempool(txns[0]) | send_transaction_to_mempool(txns[0]) | ||||
Show All 25 Lines | def get_tests(self): | ||||
yield accepted() | yield accepted() | ||||
for i in range(5): | for i in range(5): | ||||
block(5100 + i) | block(5100 + i) | ||||
test.blocks_and_transactions.append([self.tip, True]) | test.blocks_and_transactions.append([self.tip, True]) | ||||
yield test | 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'], | |||||
REPLAY_PROTECTION_START_TIME - 1) | REPLAY_PROTECTION_START_TIME - 1) | ||||
# We are just before the fork, replay protected txns still are rejected | # We are just before the fork, replay protected txns still are rejected | ||||
assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, | assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, | ||||
node.sendrawtransaction, ToHex(replay_txns[1])) | node.sendrawtransaction, ToHex(replay_txns[1])) | ||||
block(3) | block(3) | ||||
update_block(3, replay_txns) | update_block(3, replay_txns) | ||||
yield rejected(RejectResult(16, b'blk-bad-inputs')) | yield rejected(RejectResult(16, b'blk-bad-inputs')) | ||||
# Rewind bad block | # Rewind bad block | ||||
tip(5104) | tip(5104) | ||||
# Send some non replay protected txns in the mempool to check | # Send some non replay protected txns in the mempool to check | ||||
# they get cleaned at activation. | # they get cleaned at activation. | ||||
txns = create_fund_and_spend_tx(out[2]) | txns = create_fund_and_spend_tx(out[2]) | ||||
send_transaction_to_mempool(txns[0]) | send_transaction_to_mempool(txns[0]) | ||||
tx_id = send_transaction_to_mempool(txns[1]) | tx_id = send_transaction_to_mempool(txns[1]) | ||||
# Activate the replay protection | # Send two always valid txns (pre and post-fork) to the mempool. The | ||||
# first one will be mined in the activation block, the 2nd will be kept | |||||
# in the mempool through the fork boundary | |||||
always_valid_txns_ids = [] | |||||
first_chained_tx = create_chaining_tx(out[3]) | |||||
always_valid_txns_ids.append( | |||||
send_transaction_to_mempool(first_chained_tx)) | |||||
last_chained_tx = create_chaining_tx(first_chained_tx) | |||||
always_valid_txns_ids.append( | |||||
send_transaction_to_mempool(last_chained_tx)) | |||||
# Activate the replay protection while also mining the first chained tx | |||||
block(5556) | block(5556) | ||||
update_block(5556, [first_chained_tx]) | |||||
yield accepted() | yield accepted() | ||||
# Check we just activated the replay protection | # Check we just activated the replay protection | ||||
assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], | assert_equal( | ||||
node.getblockheader(node.getbestblockhash())['mediantime'], | |||||
REPLAY_PROTECTION_START_TIME) | REPLAY_PROTECTION_START_TIME) | ||||
# Non replay protected transactions are not valid anymore, | # Non replay protected transactions are not valid anymore, | ||||
# so they should be removed from the mempool. | # so they should be removed from the mempool. | ||||
assert(tx_id not in set(node.getrawmempool())) | assert(tx_id not in set(node.getrawmempool())) | ||||
# The 2nd chained transaction is still in the mempool after the fork | |||||
# activating block | |||||
assert(always_valid_txns_ids[1] in node.getrawmempool()) | |||||
# Good old transactions are now invalid. | # Good old transactions are now invalid. | ||||
send_transaction_to_mempool(txns[0]) | send_transaction_to_mempool(txns[0]) | ||||
assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, | assert_raises_rpc_error(-26, RPC_INVALID_SIGNATURE_ERROR, | ||||
node.sendrawtransaction, ToHex(txns[1])) | node.sendrawtransaction, ToHex(txns[1])) | ||||
# They also cannot be mined | # They also cannot be mined | ||||
block(4) | block(4) | ||||
Show All 21 Lines | def get_tests(self): | ||||
found_id1 = True | found_id1 = True | ||||
assert(found_id0 and found_id1) | assert(found_id0 and found_id1) | ||||
# And the mempool is still in good shape. | # And the mempool is still in good shape. | ||||
assert(replay_tx0_id in set(node.getrawmempool())) | assert(replay_tx0_id in set(node.getrawmempool())) | ||||
assert(replay_tx1_id in set(node.getrawmempool())) | assert(replay_tx1_id in set(node.getrawmempool())) | ||||
# They also can also be mined | # They also can also be mined, along with the 2nd chained tx | ||||
b5 = block(5) | b5 = block(5) | ||||
update_block(5, replay_txns) | update_block(5, replay_txns + [last_chained_tx]) | ||||
yield accepted() | yield accepted() | ||||
# Now we add a chain of 5 unconfirmed txns | |||||
for _ in range(5): | |||||
last_chained_tx = create_chaining_tx(last_chained_tx) | |||||
always_valid_txns_ids.append( | |||||
send_transaction_to_mempool(last_chained_tx)) | |||||
assert( | |||||
set(always_valid_txns_ids[2:]).issubset(set(node.getrawmempool()))) | |||||
# Ok, now we check if a reorg work properly accross the activation. | # Ok, now we check if a reorg work properly accross the activation. | ||||
postforkblockid = node.getbestblockhash() | postforkblockid = node.getbestblockhash() | ||||
node.invalidateblock(postforkblockid) | node.invalidateblock(postforkblockid) | ||||
assert(replay_tx0_id in set(node.getrawmempool())) | assert(replay_tx0_id in set(node.getrawmempool())) | ||||
assert(replay_tx1_id in set(node.getrawmempool())) | assert(replay_tx1_id in set(node.getrawmempool())) | ||||
# The 2nd chained txn, which was sent post fork, returned to the | |||||
# mempool | |||||
assert( | |||||
set(always_valid_txns_ids[1:]).issubset(set(node.getrawmempool()))) | |||||
# Deactivating replay protection. | # Deactivating replay protection. | ||||
forkblockid = node.getbestblockhash() | forkblockid = node.getbestblockhash() | ||||
node.invalidateblock(forkblockid) | node.invalidateblock(forkblockid) | ||||
assert(replay_tx0_id not in set(node.getrawmempool())) | assert(replay_tx0_id in set(node.getrawmempool())) | ||||
assert(replay_tx1_id not in set(node.getrawmempool())) | assert(replay_tx1_id not in set(node.getrawmempool())) | ||||
# Now all chained txns are in the mempool, even those created after the | |||||
# fork activated | |||||
assert(set(always_valid_txns_ids).issubset(set(node.getrawmempool()))) | |||||
# Check that we also do it properly on deeper reorg. | # Check that we also do it properly on deeper reorg. | ||||
node.reconsiderblock(forkblockid) | node.reconsiderblock(forkblockid) | ||||
node.reconsiderblock(postforkblockid) | node.reconsiderblock(postforkblockid) | ||||
node.invalidateblock(forkblockid) | node.invalidateblock(forkblockid) | ||||
assert(replay_tx0_id not in set(node.getrawmempool())) | assert(replay_tx0_id in set(node.getrawmempool())) | ||||
assert(replay_tx1_id not in set(node.getrawmempool())) | assert(replay_tx1_id not in set(node.getrawmempool())) | ||||
assert(set(always_valid_txns_ids).issubset(set(node.getrawmempool()))) | |||||
# Check that we do it properly on a real reorg to a different chain but | |||||
# still crossing the fork boundary | |||||
node.reconsiderblock(forkblockid) | |||||
tip(5104) | |||||
block(8) | |||||
block(9) | |||||
block(10) | |||||
yield TestInstance([[self.blocks[8], True], [self.blocks[9], True], [self.blocks[10], True]], False) | |||||
assert(set(always_valid_txns_ids).issubset(set(node.getrawmempool()))) | |||||
markblundebergAuthorUnsubmitted Done Inline ActionsAlso: assert(replay_tx0_id in set(node.getrawmempool())) assert(replay_tx1_id in set(node.getrawmempool())) (since the reorg finishes after activation, even the post-activation txes get put back in mempool) Also be good to put a pre-activation signature transaction into the last pre-activation block, i.e., update_block(5556, ... above. During this reorg, it will get put into disconnectpool and then it will be rejected during updateMempoolForReorg. markblundeberg: Also:
assert(replay_tx0_id in set(node.getrawmempool()))
assert(replay_tx1_id… | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
ReplayProtectionTest().main() | ReplayProtectionTest().main() |
Can add a comment up here that the replay protection "upgrade" serves as a good generic example of flag-based mempool reprocessing that occurs during actual upgrades.
(Also, is "UAHF" intentional here? Normally it refers to 2017 August but I suppose also if people don't upgrade their ABC client and let this activate, then they get another "UAHF" for free. :-) )