Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-segwit-recovery.py
- This file was moved from test/functional/abc-segwit-recovery-activation.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-2019 The Bitcoin developers | # Copyright (c) 2017-2019 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 the SCRIPT_ALLOW_SEGWIT_RECOVERY flag | This test checks that blocks containing segwit recovery transactions will be accepted, | ||||
that segwit recovery transactions are rejected from mempool acceptance (even with | |||||
-acceptnonstdtxn=1), and that segwit recovery transactions don't result in bans. | |||||
""" | """ | ||||
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.comptool import TestInstance, TestManager | ||||
from test_framework.messages import ( | from test_framework.messages import ( | ||||
COIN, | COIN, | ||||
COutPoint, | COutPoint, | ||||
CTransaction, | CTransaction, | ||||
CTxIn, | CTxIn, | ||||
CTxOut, | CTxOut, | ||||
msg_tx, | msg_tx, | ||||
ToHex, | ToHex, | ||||
) | ) | ||||
from test_framework.mininode import ( | from test_framework.mininode import ( | ||||
mininode_lock, | mininode_lock, | ||||
network_thread_start, | network_thread_start, | ||||
P2PInterface, | P2PInterface, | ||||
) | ) | ||||
from test_framework.script import ( | from test_framework.script import ( | ||||
CScript, | CScript, | ||||
hash160, | hash160, | ||||
OP_EQUAL, | OP_EQUAL, | ||||
OP_HASH160, | OP_HASH160, | ||||
OP_TRUE, | OP_TRUE, | ||||
) | ) | ||||
from test_framework.test_framework import BitcoinTestFramework | from test_framework.test_framework import BitcoinTestFramework | ||||
from test_framework.util import ( | from test_framework.util import ( | ||||
assert_equal, | |||||
assert_raises_rpc_error, | assert_raises_rpc_error, | ||||
sync_blocks, | sync_blocks, | ||||
) | ) | ||||
# far into the future | TEST_TIME = int(time.time()) | ||||
GREAT_WALL_START_TIME = 2000000000 | |||||
# First blocks (initial coinbases, pre-fork test blocks) happen 1 day before. | |||||
FIRST_BLOCK_TIME = GREAT_WALL_START_TIME - 86400 | |||||
# Error due to non clean stack | # Error due to non clean stack | ||||
CLEANSTACK_ERROR = b'non-mandatory-script-verify-flag (Script did not clean its stack)' | CLEANSTACK_ERROR = b'non-mandatory-script-verify-flag (Script did not clean its stack)' | ||||
RPC_CLEANSTACK_ERROR = "64: " + \ | RPC_CLEANSTACK_ERROR = "64: " + \ | ||||
CLEANSTACK_ERROR.decode("utf-8") | CLEANSTACK_ERROR.decode("utf-8") | ||||
EVAL_FALSE_ERROR = b'non-mandatory-script-verify-flag (Script evaluated without error but finished with a false/empty top stack elem' | EVAL_FALSE_ERROR = b'non-mandatory-script-verify-flag (Script evaluated without error but finished with a false/empty top stack elem' | ||||
RPC_EVAL_FALSE_ERROR = "64: " + \ | RPC_EVAL_FALSE_ERROR = "64: " + \ | ||||
EVAL_FALSE_ERROR.decode("utf-8") | EVAL_FALSE_ERROR.decode("utf-8") | ||||
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 SegwitRecoveryActivationTest(BitcoinTestFramework): | class SegwitRecoveryTest(BitcoinTestFramework): | ||||
def set_test_params(self): | def set_test_params(self): | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
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 = {} | ||||
# We have 2 nodes: | # We have 2 nodes: | ||||
# 1) node_nonstd (nodes[0]) accepts non-standard txns. It's used to | # 1) node_nonstd (nodes[0]) accepts non-standard txns. It does not | ||||
# test the activation itself via TestManager. | # accept Segwit recovery transactions, since it is included in | ||||
# standard flags, and transactions that violate these flags are | |||||
# never accepted into the mempool. | |||||
# 2) node_std (nodes[1]) doesn't accept non-standard txns and | # 2) node_std (nodes[1]) doesn't accept non-standard txns and | ||||
# doesn't have us whitelisted. It's used to test for bans, as we | # doesn't have us whitelisted. It's used to test for bans, as we | ||||
# connect directly to it via mininode and send a segwit spending | # connect directly to it via mininode and send a segwit spending | ||||
# txn. This transaction is non-standard and, before activation, | # txn. This transaction is non-standard. We check that sending | ||||
# also invalid. We check, before and after activation, that | # this transaction doesn't result in a ban. | ||||
# sending this transaction doesn't result in a ban. | |||||
# Nodes are connected to each other, so node_std receives blocks and | # Nodes are connected to each other, so node_std receives blocks and | ||||
# transactions that node_nonstd has accepted. Since we are checking | # transactions that node_nonstd has accepted. Since we are checking | ||||
# that segwit spending txn are not resulting in bans, node_nonstd | # that segwit spending txn are not resulting in bans, node_nonstd | ||||
# doesn't get banned when forwarding this kind of transactions to | # doesn't get banned when forwarding this kind of transactions to | ||||
# node_std. | # node_std. | ||||
self.extra_args = [['-whitelist=127.0.0.1', | self.extra_args = [['-whitelist=127.0.0.1', | ||||
"-acceptnonstdtxn", | "-acceptnonstdtxn"], | ||||
"-greatwallactivationtime={}".format( | ["-acceptnonstdtxn=0"]] | ||||
GREAT_WALL_START_TIME), | |||||
"-replayprotectionactivationtime={}".format( | |||||
2 * GREAT_WALL_START_TIME)], | |||||
["-acceptnonstdtxn=0", | |||||
"-greatwallactivationtime={}".format( | |||||
GREAT_WALL_START_TIME), | |||||
"-replayprotectionactivationtime={}".format( | |||||
2 * GREAT_WALL_START_TIME)]] | |||||
def run_test(self): | def run_test(self): | ||||
# Move the mocktime up to activation | # Move the mocktime up to activation | ||||
for node in self.nodes: | for node in self.nodes: | ||||
node.setmocktime(GREAT_WALL_START_TIME) | node.setmocktime(TEST_TIME) | ||||
test = TestManager(self, self.options.tmpdir) | test = TestManager(self, self.options.tmpdir) | ||||
# TestManager only connects to node_nonstd (nodes[0]) | # TestManager only connects to node_nonstd (nodes[0]) | ||||
test.add_all_connections([self.nodes[0]]) | test.add_all_connections([self.nodes[0]]) | ||||
# We connect directly to node_std (nodes[1]) | # We connect directly to node_std (nodes[1]) | ||||
self.nodes[1].add_p2p_connection(P2PInterface()) | self.nodes[1].add_p2p_connection(P2PInterface()) | ||||
network_thread_start() | network_thread_start() | ||||
test.run() | 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 = TEST_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) | ||||
▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | def get_tests(self): | ||||
def check_mempool_equal(node, txns): | def check_mempool_equal(node, txns): | ||||
assert set(node.getrawmempool()) == set(tx.hash for tx in txns) | assert set(node.getrawmempool()) == set(tx.hash for tx in txns) | ||||
# Returns 2 transactions: | # Returns 2 transactions: | ||||
# 1) txfund: create outputs in segwit addresses | # 1) txfund: create outputs in segwit addresses | ||||
# 2) txspend: spends outputs from segwit addresses | # 2) txspend: spends outputs from segwit addresses | ||||
def create_segwit_fund_and_spend_tx(spend, case0=False): | def create_segwit_fund_and_spend_tx(spend, case0=False): | ||||
if not case0: | if not case0: | ||||
# To make sure we'll be able to recover coins sent to segwit addresses, | |||||
# we test using historical recoveries from btc.com: | |||||
# Spending from a P2SH-P2WPKH coin, | # Spending from a P2SH-P2WPKH coin, | ||||
# txhash:a45698363249312f8d3d93676aa714be59b0bd758e62fa054fb1ea6218480691 | # txhash:a45698363249312f8d3d93676aa714be59b0bd758e62fa054fb1ea6218480691 | ||||
redeem_script0 = bytearray.fromhex( | redeem_script0 = bytearray.fromhex( | ||||
'0014fcf9969ce1c98a135ed293719721fb69f0b686cb') | '0014fcf9969ce1c98a135ed293719721fb69f0b686cb') | ||||
# Spending from a P2SH-P2WSH coin, | # Spending from a P2SH-P2WSH coin, | ||||
# txhash:6b536caf727ccd02c395a1d00b752098ec96e8ec46c96bee8582be6b5060fa2f | # txhash:6b536caf727ccd02c395a1d00b752098ec96e8ec46c96bee8582be6b5060fa2f | ||||
redeem_script1 = bytearray.fromhex( | redeem_script1 = bytearray.fromhex( | ||||
'0020fc8b08ed636cb23afcb425ff260b3abd03380a2333b54cfa5d51ac52d803baf4') | '0020fc8b08ed636cb23afcb425ff260b3abd03380a2333b54cfa5d51ac52d803baf4') | ||||
▲ Show 20 Lines • Show All 74 Lines • ▼ Show 20 Lines | def get_tests(self): | ||||
for i in range(100): | for i in range(100): | ||||
out.append(get_spendable_output()) | out.append(get_spendable_output()) | ||||
# Create segwit funding and spending transactions | # Create segwit funding and spending transactions | ||||
txfund, txspend = create_segwit_fund_and_spend_tx(out[0]) | txfund, txspend = create_segwit_fund_and_spend_tx(out[0]) | ||||
txfund_case0, txspend_case0 = create_segwit_fund_and_spend_tx( | txfund_case0, txspend_case0 = create_segwit_fund_and_spend_tx( | ||||
out[1], True) | out[1], True) | ||||
# Create blocks to get closer to activate the fork. | |||||
# Mine txfund, as it can't go into node_std mempool because it's | # Mine txfund, as it can't go into node_std mempool because it's | ||||
# nonstandard. | # nonstandard. | ||||
b = block(5555) | b = block(5555) | ||||
b.nTime = GREAT_WALL_START_TIME - 1 | |||||
update_block(5555, [txfund, txfund_case0]) | update_block(5555, [txfund, txfund_case0]) | ||||
yield accepted() | yield accepted() | ||||
for i in range(5): | |||||
block(5100 + i) | |||||
test.blocks_and_transactions.append([self.tip, True]) | |||||
yield test | |||||
# Since the TestManager is not connected to node_std, we must check | # Since the TestManager is not connected to node_std, we must check | ||||
# both nodes are synchronized before continuing. | # both nodes are synchronized before continuing. | ||||
sync_blocks(self.nodes) | sync_blocks(self.nodes) | ||||
# Check we are just before the activation time | |||||
assert_equal(node_nonstd.getblockheader( | |||||
node_nonstd.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME - 1) | |||||
assert_equal(node_std.getblockheader( | |||||
node_std.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME - 1) | |||||
# Before the fork, segwit spending txns are rejected. | |||||
assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | |||||
node_nonstd.sendrawtransaction, ToHex(txspend)) | |||||
assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | |||||
node_std.sendrawtransaction, ToHex(txspend)) | |||||
assert_raises_rpc_error(-26, RPC_EVAL_FALSE_ERROR, | |||||
node_nonstd.sendrawtransaction, ToHex(txspend_case0)) | |||||
assert_raises_rpc_error(-26, RPC_EVAL_FALSE_ERROR, | |||||
node_std.sendrawtransaction, ToHex(txspend_case0)) | |||||
# Blocks containing segwit spending txns are rejected as well. | |||||
block(2) | |||||
update_block(2, [txspend, txspend_case0]) | |||||
yield rejected(RejectResult(16, b'blk-bad-inputs')) | |||||
# Rewind bad block | |||||
tip(5104) | |||||
# Check that non-upgraded nodes checking for standardness are not | |||||
# banning nodes sending segwit spending txns. | |||||
check_for_no_ban_on_rejected_tx(txspend, 64, CLEANSTACK_ERROR) | |||||
check_for_no_ban_on_rejected_tx(txspend_case0, 64, EVAL_FALSE_ERROR) | |||||
# Activate the fork in both nodes! | |||||
forkblock = block(5556) | |||||
yield accepted() | |||||
sync_blocks(self.nodes) | |||||
# Check we just activated the fork | |||||
assert_equal(node_nonstd.getblockheader( | |||||
node_nonstd.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME) | |||||
assert_equal(node_std.getblockheader( | |||||
node_std.getbestblockhash())['mediantime'], GREAT_WALL_START_TIME) | |||||
# Check that upgraded nodes checking for standardness are not banning | # Check that upgraded nodes checking for standardness are not banning | ||||
# nodes sending segwit spending txns. | # nodes sending segwit spending txns. | ||||
check_for_no_ban_on_rejected_tx(txspend, 64, CLEANSTACK_ERROR) | check_for_no_ban_on_rejected_tx(txspend, 64, CLEANSTACK_ERROR) | ||||
check_for_no_ban_on_rejected_tx(txspend_case0, 64, EVAL_FALSE_ERROR) | check_for_no_ban_on_rejected_tx(txspend_case0, 64, EVAL_FALSE_ERROR) | ||||
# Segwit spending txns are accepted in the mempool of nodes not checking | # Segwit recovery txns are never accepted into the mempool, | ||||
# for standardness, but rejected in nodes that check. | # as they are included in standard flags. | ||||
node_nonstd.sendrawtransaction(ToHex(txspend)) | assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | ||||
node_nonstd.sendrawtransaction(ToHex(txspend_case0)) | node_nonstd.sendrawtransaction, ToHex(txspend)) | ||||
check_mempool_equal(node_nonstd, [txspend, txspend_case0]) | assert_raises_rpc_error(-26, RPC_EVAL_FALSE_ERROR, | ||||
node_nonstd.sendrawtransaction, ToHex(txspend_case0)) | |||||
assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | ||||
node_std.sendrawtransaction, ToHex(txspend)) | node_std.sendrawtransaction, ToHex(txspend)) | ||||
assert_raises_rpc_error(-26, RPC_EVAL_FALSE_ERROR, | assert_raises_rpc_error(-26, RPC_EVAL_FALSE_ERROR, | ||||
node_std.sendrawtransaction, ToHex(txspend_case0)) | node_std.sendrawtransaction, ToHex(txspend_case0)) | ||||
# Blocks containing segwit spending txns are now accepted in both | # Blocks containing segwit spending txns are accepted in both nodes. | ||||
# nodes. | |||||
block(5) | block(5) | ||||
postforkblock = update_block(5, [txspend, txspend_case0]) | postforkblock = update_block(5, [txspend, txspend_case0]) | ||||
yield accepted() | yield accepted() | ||||
sync_blocks(self.nodes) | sync_blocks(self.nodes) | ||||
# Ok, now we check if a reorg work properly accross the activation. | |||||
node_nonstd.invalidateblock(postforkblock.hash) | |||||
check_mempool_equal(node_nonstd, [txspend, txspend_case0]) | |||||
# Also check that nodes checking for standardness don't return a segwit | |||||
# spending txn into the mempool when disconnecting a block. | |||||
node_std.invalidateblock(postforkblock.hash) | |||||
assert(len(node_std.getrawmempool()) == 0) | |||||
# Deactivate the fork. The spending tx has been evicted from the | |||||
# mempool | |||||
node_nonstd.invalidateblock(forkblock.hash) | |||||
assert(len(node_nonstd.getrawmempool()) == 0) | |||||
if __name__ == '__main__': | if __name__ == '__main__': | ||||
SegwitRecoveryActivationTest().main() | SegwitRecoveryTest().main() |