Changeset View
Standalone View
test/functional/abc-cleanstack_exception-activation.py
- This file was added.
Property | Old Value | New Value |
---|---|---|
File Mode | null | 100755 |
#!/usr/bin/env python3 | |||||
Lint: Code style violation: '/home/ubuntu/dev/bitcoin-abc/test/functional/abc-cleanstack_exception-activation.py' has code… | |||||
deadalnixUnsubmitted Done Inline ActionsYou should have autopep8 and flake8 installed. deadalnix: You should have autopep8 and flake8 installed. | |||||
# Copyright (c) 2018 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 CLEANSTACK_EXCEPTION flag | |||||
""" | |||||
from test_framework.test_framework import ComparisonTestFramework | |||||
from test_framework.util import satoshi_round, assert_equal, assert_raises_rpc_error | |||||
from test_framework.comptool import TestManager, TestInstance | |||||
from test_framework.blocktools import * | |||||
from test_framework.script import * | |||||
# far into the future | |||||
GREAT_WALL_START_TIME = 2000000000 | |||||
# Error due to invalid opcodes | |||||
CLEANSTACK_ERROR = b'non-mandatory-script-verify-flag (Script did not clean its stack)' | |||||
RPC_CLEANSTACK_ERROR = "64: " + \ | |||||
CLEANSTACK_ERROR.decode("utf-8") | |||||
class PreviousSpendableOutput(): | |||||
def __init__(self, tx=CTransaction(), n=-1): | |||||
self.tx = tx | |||||
self.n = n # the output we're spending | |||||
class CleanstackExceptionActivationTest(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, | |||||
"-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) | |||||
# Start up network handling in another thread | |||||
NetworkThread().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 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, tx hash: a45698363249312f8d3d93676aa714be59b0bd758e62fa054fb1ea6218480691 | |||||
redeem_script0 = bytearray.fromhex('0014fcf9969ce1c98a135ed293719721fb69f0b686cb') | |||||
# Spending from a P2SH-P2WSH coin, tx hash: 6b536caf727ccd02c395a1d00b752098ec96e8ec46c96bee8582be6b5060fa2f | |||||
redeem_script1 = bytearray.fromhex('0020fc8b08ed636cb23afcb425ff260b3abd03380a2333b54cfa5d51ac52d803baf4') | |||||
redeem_scripts = [redeem_script0, redeem_script1] | |||||
# Create outputs in segwit addresses | |||||
tx, tx_hex = self.create_tx_spending_to_segwit(redeem_scripts) | |||||
tx_id = node.sendrawtransaction(tx_hex) | |||||
assert(tx_id in set(node.getrawmempool())) | |||||
node.generate(1) | |||||
assert(tx_id not in set(node.getrawmempool())) | |||||
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([])), | |||||
CTxOut(0, CScript([random.getrandbits(800), OP_RETURN]))] | |||||
tx.vout[0].nValue -= node.calculate_fee(tx) | |||||
tx.rehash() | |||||
return tx | |||||
def next_block(block_time): | |||||
# 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) | |||||
# Do PoW, which is cheap on regnet | |||||
block.solve() | |||||
return block | |||||
def add_tx(block, tx): | |||||
block.vtx.append(tx) | |||||
deadalnixUnsubmitted Done Inline ActionsThis will not work in case there are several txns in there. In that case you'd benefit from jst adding an optional tx to add in the block in the next_block function. deadalnix: This will not work in case there are several txns in there. In that case you'd benefit from jst… | |||||
block.hashMerkleRoot = block.calc_merkle_root() | |||||
block.solve() | |||||
# 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]]) | |||||
# 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 transactions spending from segwit address are not acceptable into the mempool | |||||
self.log.info("Try to spend a segwit coin before activation via mempool") | |||||
tx_spend = spend_from_segwit(tx, redeem_scripts) | |||||
tx_spend_hex = ToHex(tx_spend) | |||||
assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | |||||
node.sendrawtransaction, tx_spend_hex) | |||||
# Check that transactions spending from segwit address are not acceptable in blocks | |||||
self.log.info("Try to spend a segwit coin before activation in a new block") | |||||
b = next_block(GREAT_WALL_START_TIME + 6) | |||||
add_tx(b, tx_spend) | |||||
yield rejected(b) | |||||
deadalnixUnsubmitted Not Done Inline ActionsAlways check why things are rejected. For all you know, you might be screwing up something and things do not work for a completely unrelated reason. deadalnix: Always check why things are rejected. For all you know, you might be screwing up something and… | |||||
florianUnsubmitted Done Inline ActionsYeah, very wise! florian: Yeah, very wise! | |||||
self.log.info("Activate cleanstack_exception") | |||||
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 transactions spending from segwit address are still not acceptable into the mempool | |||||
self.log.info("Try to spend a segwit coin after activation via mempool") | |||||
assert_raises_rpc_error(-26, RPC_CLEANSTACK_ERROR, | |||||
node.sendrawtransaction, tx_spend_hex) | |||||
# Check that transactions spending from segwit address are acceptable in blocks | |||||
self.log.info("Try to spend a segwit coin after activation in a new block") | |||||
b = next_block(GREAT_WALL_START_TIME + 7) | |||||
add_tx(b, tx_spend) | |||||
yield accepted(b) | |||||
self.log.info("Cause a reorg that deactivate cleanstack_exception") | |||||
# Check that the transaction hasn't returned to the mempool | |||||
postforkblockid = node.getbestblockhash() | |||||
node.invalidateblock(postforkblockid) | |||||
assert(tx_spend_hex not in set(node.getrawmempool())) | |||||
deadalnixUnsubmitted Not Done Inline ActionsThere is no code ensuring that this is the case, so I would be very skeptical about the test passing here. I don't think this is testing what you think this is testing. You may want to try with a node that is configured to not care about standardness,for instance. deadalnix: There is no code ensuring that this is the case, so I would be very skeptical about the test… | |||||
florianUnsubmitted Done Inline ActionsIndeed. I managed to get the tx back into the mempool clearing the CLEANSTACK flag with -promiscuousmempoolflags. This isn't testing anything useful, I'm taking it out. florian: Indeed. I managed to get the tx back into the mempool clearing the CLEANSTACK flag with… | |||||
if __name__ == '__main__': | |||||
CleanstackExceptionActivationTest().main() |
'/home/ubuntu/dev/bitcoin-abc/test/functional/abc-cleanstack_exception-activation.py' has code style errors.