Changeset View
Changeset View
Standalone View
Standalone View
test/functional/abc-segwit-recovery-activation.py
- This file was added.
Property | Old Value | New Value |
---|---|---|
File Mode | null | 100755 |
#!/usr/bin/env python3 | |||||
# Copyright (c) 2019 The Bitcoin developers | |||||
# Distributed under the MIT software license, see the accompanying | |||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | |||||
""" | |||||
This test checks activation of the SCRIPT_ALLOW_SEGWIT_RECOVERY flag | |||||
""" | |||||
from test_framework.test_framework import ComparisonTestFramework | |||||
from test_framework.util import satoshi_round, assert_equal | |||||
from test_framework.comptool import TestManager, TestInstance, RejectResult | |||||
from test_framework.blocktools import * | |||||
from test_framework.script import * | |||||
# far into the future | |||||
GREAT_WALL_START_TIME = 2000000000 | |||||
class SegwitRecoveryActivationTest(ComparisonTestFramework): | |||||
def set_test_params(self): | |||||
self.num_nodes = 1 | |||||
self.setup_clean_chain = True | |||||
self.extra_args = [["-whitelist=127.0.0.1", | |||||
"-greatwallactivationtime=%d" % GREAT_WALL_START_TIME, | |||||
"-promiscuousmempoolflags=0", | |||||
"-acceptnonstdtxn", | |||||
"-replayprotectionactivationtime=%d" % (2 * GREAT_WALL_START_TIME)]] | |||||
def run_test(self): | |||||
self.test = TestManager(self, self.options.tmpdir) | |||||
self.test.add_all_connections(self.nodes) | |||||
network_thread_start() | |||||
self.test.run() | |||||
def create_tx_spending_to_segwit(self, redeem_scripts): | |||||
node = self.nodes[0] | |||||
utxos = node.listunspent() | |||||
assert(len(utxos) > 0) | |||||
utxo = utxos[0] | |||||
tx = CTransaction() | |||||
n = len(redeem_scripts) | |||||
value = int(satoshi_round(utxo["amount"]) * COIN) // n | |||||
tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]))] | |||||
tx.vout = [] | |||||
for redeem_script in redeem_scripts: | |||||
tx.vout.append( | |||||
CTxOut(value, CScript([OP_HASH160, hash160(redeem_script), OP_EQUAL]))) | |||||
tx.vout[0].nValue -= node.calculate_fee(tx) | |||||
tx_signed = node.signrawtransaction(ToHex(tx))["hex"] | |||||
tx = FromHex(CTransaction(), tx_signed) | |||||
tx.rehash() | |||||
return tx, tx_signed | |||||
def create_dummy_tx(self, utxo_n): | |||||
node = self.nodes[0] | |||||
utxos = node.listunspent() | |||||
assert(len(utxos) > utxo_n) | |||||
utxo = utxos[utxo_n] | |||||
tx = CTransaction() | |||||
value = int(satoshi_round(utxo["amount"]) * COIN) | |||||
tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]))] | |||||
tx.vout = [CTxOut(value, CScript([OP_TRUE]))] | |||||
tx.vout[0].nValue -= node.calculate_fee(tx) | |||||
tx_signed = node.signrawtransaction(ToHex(tx))["hex"] | |||||
tx = FromHex(CTransaction(), tx_signed) | |||||
tx.rehash() | |||||
return tx, tx_signed | |||||
def get_tests(self): | |||||
node = self.nodes[0] | |||||
# First, we generate some coins to spend. | |||||
node.generate(125) | |||||
# 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, | |||||
# txhash:a45698363249312f8d3d93676aa714be59b0bd758e62fa054fb1ea6218480691 | |||||
redeem_script0 = bytearray.fromhex( | |||||
'0014fcf9969ce1c98a135ed293719721fb69f0b686cb') | |||||
# Spending from a P2SH-P2WSH coin, | |||||
# txhash:6b536caf727ccd02c395a1d00b752098ec96e8ec46c96bee8582be6b5060fa2f | |||||
redeem_script1 = bytearray.fromhex( | |||||
'0020fc8b08ed636cb23afcb425ff260b3abd03380a2333b54cfa5d51ac52d803baf4') | |||||
redeem_scripts = [redeem_script0, redeem_script1] | |||||
# Create outputs in segwit addresses in 2 separate transactions | |||||
tx0, tx0_hex = self.create_tx_spending_to_segwit(redeem_scripts) | |||||
tx0_id = node.sendrawtransaction(tx0_hex) | |||||
tx1, tx1_hex = self.create_tx_spending_to_segwit(redeem_scripts) | |||||
tx1_id = node.sendrawtransaction(tx1_hex) | |||||
assert(tx0_id in node.getrawmempool()) | |||||
assert(tx1_id in node.getrawmempool()) | |||||
node.generate(1) | |||||
assert(tx0_id not in node.getrawmempool()) | |||||
assert(tx1_id not in node.getrawmempool()) | |||||
# Create 2 dummy transactions (non segwit related) with pre-fork | |||||
# coinbases | |||||
_, tx_dummy0 = self.create_dummy_tx(0) | |||||
_, tx_dummy1 = self.create_dummy_tx(1) | |||||
def spend_from_segwit(txin, redeem_scripts): | |||||
tx = CTransaction() | |||||
tx.vin = [] | |||||
amount = 0 | |||||
for i in range(len(redeem_scripts)): | |||||
tx.vin.append(CTxIn(COutPoint(txin.sha256, i))) | |||||
tx.vin[i].scriptSig = CScript([redeem_scripts[i]]) | |||||
amount += txin.vout[i].nValue | |||||
tx.vout = [ | |||||
CTxOut(amount, CScript([OP_RETURN, random.getrandbits(800)]))] | |||||
tx.vout[0].nValue -= node.calculate_fee(tx) | |||||
tx.rehash() | |||||
return tx | |||||
def next_block(block_time, spend_tx=None): | |||||
# get block height | |||||
blockchaininfo = node.getblockchaininfo() | |||||
height = int(blockchaininfo['blocks']) | |||||
# create the block | |||||
coinbase = create_coinbase(height) | |||||
coinbase.rehash() | |||||
block = create_block( | |||||
int(node.getbestblockhash(), 16), coinbase, block_time) | |||||
if spend_tx: | |||||
# Add this tx in this block and recompute the merkle root | |||||
# We ignore the transaction fees | |||||
block.vtx.append(spend_tx) | |||||
block.hashMerkleRoot = block.calc_merkle_root() | |||||
# Do PoW, which is cheap on regnet | |||||
block.solve() | |||||
return block | |||||
# returns a test case that asserts that the current tip was accepted | |||||
def accepted(tip): | |||||
return TestInstance([[tip, True]]) | |||||
# returns a test case that asserts that the current tip was rejected | |||||
def rejected(tip, reject=None): | |||||
if reject is None: | |||||
return TestInstance([[tip, False]]) | |||||
else: | |||||
return TestInstance([[tip, reject]]) | |||||
# returns a test case that asserts that the transaction was accepted | |||||
def tx_accepted(tx): | |||||
return TestInstance([[tx, True]]) | |||||
# returns a test case that asserts that the transaction was rejected | |||||
def tx_rejected(tx, reject): | |||||
return TestInstance([[tx, reject]]) | |||||
# Push MTP forward just before activation. | |||||
node.setmocktime(GREAT_WALL_START_TIME) | |||||
for i in range(6): | |||||
b = next_block(GREAT_WALL_START_TIME + i - 1) | |||||
yield accepted(b) | |||||
assert_equal( | |||||
node.getblockheader(node.getbestblockhash())['mediantime'], | |||||
GREAT_WALL_START_TIME - 1) | |||||
# Check that segwit spending tx is not acceptable into the mempool | |||||
self.log.info( | |||||
"Try to spend a segwit coin before activation via mempool") | |||||
tx_spend0 = spend_from_segwit(tx0, redeem_scripts) | |||||
yield tx_rejected(tx_spend0, RejectResult(16, b'mandatory-script-verify-flag-failed (Script did not clean its stack)')) | |||||
# Check that segwit spending tx is not acceptable in a block | |||||
self.log.info( | |||||
"Try to spend a segwit coin before activation in a new block") | |||||
b = next_block(GREAT_WALL_START_TIME + 6, tx_spend0) | |||||
yield rejected(b, RejectResult(16, b'blk-bad-inputs')) | |||||
self.log.info("Activate SEGWIT_RECOVERY") | |||||
fork_block = next_block(GREAT_WALL_START_TIME + 6) | |||||
yield accepted(fork_block) | |||||
assert_equal( | |||||
node.getblockheader(node.getbestblockhash())['mediantime'], | |||||
GREAT_WALL_START_TIME) | |||||
# Check that segwit spending tx is acceptable into the mempool since | |||||
# standardness checks were disabled | |||||
self.log.info( | |||||
"Try to spend a segwit coin after activation via mempool") | |||||
yield tx_accepted(tx_spend0) | |||||
# Check that segwit spending tx is acceptable in a block | |||||
self.log.info( | |||||
"Try to spend a segwit coin after activation in a new block") | |||||
b = next_block(GREAT_WALL_START_TIME + 7, tx_spend0) | |||||
yield accepted(b) | |||||
# Add a dummy tx in a block | |||||
tx_dummy0_id = node.sendrawtransaction(tx_dummy0) | |||||
assert(tx_dummy0_id in node.getrawmempool()) | |||||
node.generate(1) | |||||
assert(tx_dummy0_id not in node.getrawmempool()) | |||||
# Push the 2nd segwit spending tx into the mempool | |||||
tx_spend1 = spend_from_segwit(tx1, redeem_scripts) | |||||
tx_spend1_id = node.sendrawtransaction(ToHex(tx_spend1)) | |||||
assert(tx_spend1_id in node.getrawmempool()) | |||||
# Push the 2nd dummy tx into the mempool | |||||
tx_dummy1_id = node.sendrawtransaction(tx_dummy1) | |||||
assert(tx_dummy1_id in node.getrawmempool()) | |||||
# Cause a reorg to check that the activation wasn't latched | |||||
self.log.info( | |||||
"Cause a reorg that deactivates SEGWIT_RECOVERY and test again") | |||||
node.invalidateblock(fork_block.hash) | |||||
# Check that segwit spending tx is again not acceptable in a block | |||||
self.log.info( | |||||
"Try to spend a segwit coin after a reorg in a new pre-activation block") | |||||
b = next_block(GREAT_WALL_START_TIME + 6, tx_spend0) | |||||
yield rejected(b, RejectResult(16, b'blk-bad-inputs')) | |||||
# Check that both segwit spending transactions haven't returned into | |||||
# the mempool but that both dummy transactions have | |||||
assert(set(node.getrawmempool()) == set([tx_dummy0_id, tx_dummy1_id])) | |||||
if __name__ == '__main__': | |||||
SegwitRecoveryActivationTest().main() |