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 | |||||
# 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 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, RejectResult | |||||
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 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) | |||||
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 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 | |||||
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, 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]]) | |||||
# 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 using default policy config | |||||
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 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_spend) | |||||
yield rejected(b, RejectResult(16, b'blk-bad-inputs')) | |||||
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 segwit spending tx is still not acceptable into the | |||||
# mempool using default policy config | |||||
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) | |||||
deadalnix: You must check that a node for which standardness check are disabled will accept it. | |||||
florianUnsubmitted Not Done Inline ActionsWhen I disable standardness checks, nodes *already do* accept these transactions, even pre-fork (btw only possible in tesnet/regtest). I only get "Warning: -promiscuousmempool flags set to not include currently enforced soft forks...". -acceptnonstdtxn simply doesn't affect the flags used for script evaluation, so it doesn't enable the acceptance of these transactions. I tested it here in a separate branch. I need that out-of-scope fix in the mandatory flags to finally *force an error pre-fork*, get a positive result post-fork and an automagical mempool eviction in case of a reorg deactivating the fork. However this won't affect activation in mainnet in any way, since standardness checks in ATMP simply can't be disabled. Your call, would you like me to patch the mandatory flags? florian: When I disable standardness checks, nodes *already do* accept these transactions, even pre-fork… | |||||
deadalnixUnsubmitted Not Done Inline ActionsEither what you say is true and you discovered a bug - and it needs to be fixed, or you are mistaken and you test do not test what you think it is testing - and it needs to be fixed. Either way, something needs to be fixed. deadalnix: Either what you say is true and you discovered a bug - and it needs to be fixed, or you are… | |||||
# 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_spend) | |||||
yield accepted(b) | |||||
# Cause a reorg to check that the activation wasn't latched | |||||
self.log.info( | |||||
"Cause a reorg that deactivates cleanstack_exception and test again") | |||||
node.invalidateblock(fork_block.hash) | |||||
deadalnixUnsubmitted Done Inline ActionsYou must check that the transaction is not in the mempool as they are not valid anymore. deadalnix: You must check that the transaction is not in the mempool as they are not valid anymore. | |||||
# 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_spend) | |||||
yield rejected(b, RejectResult(16, b'blk-bad-inputs')) | |||||
if __name__ == '__main__': | |||||
CleanstackExceptionActivationTest().main() |
You must check that a node for which standardness check are disabled will accept it.