diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -987,6 +987,10 @@ // Set extraFlags as a set of flags that needs to be activated. uint32_t extraFlags = 0; + if (hasMonolith) { + extraFlags |= SCRIPT_ENABLE_MONOLITH_OPCODES; + } + if (IsReplayProtectionEnabledForCurrentBlock(config)) { extraFlags |= SCRIPT_ENABLE_REPLAY_PROTECTION; } @@ -1033,7 +1037,6 @@ // to set the replay protection flag manually here until this is fixed. // FIXME: https://reviews.bitcoinabc.org/T288 currentBlockScriptVerifyFlags |= extraFlags; - if (!CheckInputsFromMempoolAndCache(tx, state, view, pool, currentBlockScriptVerifyFlags, true, txdata)) { @@ -1563,16 +1566,26 @@ if (pvChecks) { pvChecks->push_back(std::move(check)); } else if (!check()) { - if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) { + const bool hasNonMandatoryFlags = + (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) != 0; + const bool doesNotHaveMonolith = + (flags & SCRIPT_ENABLE_MONOLITH_OPCODES) == 0; + if (hasNonMandatoryFlags || doesNotHaveMonolith) { // Check whether the failure was caused by a non-mandatory // script verification check, such as non-standard DER encodings // or non-null dummy arguments; if so, don't trigger DoS // protection to avoid splitting the network between upgraded // and non-upgraded nodes. - CScriptCheck check2(scriptPubKey, amount, tx, i, - flags & - ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS, - sigCacheStore, txdata); + // + // We also check activating the monolith opcodes as it is a + // strictly additive change and we would not like to ban some of + // our peer that are ahead of us and are considering the fork + // as activated. + CScriptCheck check2( + scriptPubKey, amount, tx, i, + (flags & ~STANDARD_NOT_MANDATORY_VERIFY_FLAGS) | + SCRIPT_ENABLE_MONOLITH_OPCODES, + sigCacheStore, txdata); if (check2()) { return state.Invalid( false, REJECT_NONSTANDARD, @@ -1934,6 +1947,11 @@ flags |= SCRIPT_VERIFY_NULLFAIL; } + // The monolith HF enable a set of opcodes. + if (IsMonolithEnabled(config, pindex->pprev)) { + flags |= SCRIPT_ENABLE_MONOLITH_OPCODES; + } + // We make sure this node will have replay protection during the next hard // fork. if (IsReplayProtectionEnabled(config, pindex->pprev)) { @@ -2584,8 +2602,13 @@ // remove transactions that are replay protected from the mempool. There is // no easy way to do this so we'll just discard the whole mempool and then // add the transaction of the block we just disconnected back. - if (IsReplayProtectionEnabled(config, pindexDelete) && - !IsReplayProtectionEnabled(config, pindexDelete->pprev)) { + // + // Samewise, if this block enabled the monolith opcodes, then we need to + // clear the mempool of any transaction using them. + if ((IsReplayProtectionEnabled(config, pindexDelete) && + !IsReplayProtectionEnabled(config, pindexDelete->pprev)) || + (IsMonolithEnabled(config, pindexDelete) && + !IsMonolithEnabled(config, pindexDelete->pprev))) { mempool.clear(); // While not strictly necessary, clearing the disconnect pool is also // beneficial so we don't try to reuse its content at the end of the diff --git a/test/functional/abc-monolith-activation.py b/test/functional/abc-monolith-activation.py new file mode 100755 --- /dev/null +++ b/test/functional/abc-monolith-activation.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# 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 monolith opcodes +""" + +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 +MONOLITH_START_TIME = 2000000000 + +# Error due to invalid opcodes +DISABLED_OPCODE_ERROR = b'non-mandatory-script-verify-flag (Attempted to use a disabled opcode)' +RPC_DISABLED_OPCODE_ERROR = "64: " + \ + DISABLED_OPCODE_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 MonolithActivationTest(ComparisonTestFramework): + + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + self.extra_args = [['-whitelist=127.0.0.1', + "-monolithactivationtime=%d" % MONOLITH_START_TIME, + "-replayprotectionactivationtime=%d" % (2 * MONOLITH_START_TIME)]] + + def create_and_tx(self, count): + node = self.nodes[0] + utxos = node.listunspent() + assert(len(utxos) > 0) + utxo = utxos[0] + tx = CTransaction() + value = int(satoshi_round( + utxo["amount"] - self.relayfee) * COIN) // count + tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]))] + tx.vout = [] + for _ in range(count): + tx.vout.append(CTxOut(value, CScript([OP_1, OP_1, OP_AND]))) + tx_signed = node.signrawtransaction(ToHex(tx))["hex"] + return tx_signed + + 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 get_tests(self): + node = self.nodes[0] + self.relayfee = self.nodes[0].getnetworkinfo()["relayfee"] + + # First, we generate some coins to spend. + node.generate(125) + + # Create various outputs using the OP_AND to check for activation. + tx_hex = self.create_and_tx(25) + txid = node.sendrawtransaction(tx_hex) + assert(txid in set(node.getrawmempool())) + + node.generate(1) + assert(txid not in set(node.getrawmempool())) + + # register the spendable outputs. + tx = FromHex(CTransaction(), tx_hex) + tx.rehash() + spendable_ands = [PreviousSpendableOutput( + tx, i) for i in range(len(tx.vout))] + + def spend_and(): + outpoint = spendable_ands.pop() + out = outpoint.tx.vout[outpoint.n] + value = int(out.nValue - (self.relayfee * COIN)) + tx = CTransaction() + tx.vin = [CTxIn(COutPoint(outpoint.tx.sha256, outpoint.n))] + tx.vout = [CTxOut(value, CScript([]))] + tx.rehash() + return tx + + # Check that large opreturn are not accepted yet. + self.log.info("Try to use the monolith opcodes before activation") + + tx0 = spend_and() + tx0_hex = ToHex(tx0) + assert_raises_rpc_error(-26, RPC_DISABLED_OPCODE_ERROR, + node.sendrawtransaction, tx0_hex) + + # Push MTP forward just before activation. + self.log.info("Pushing MTP just before the activation and check again") + node.setmocktime(MONOLITH_START_TIME) + + # 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]]) + + 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 + + for i in range(6): + b = next_block(MONOLITH_START_TIME + i - 1) + yield accepted(b) + + # Check again just before the activation time + assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], + MONOLITH_START_TIME - 1) + assert_raises_rpc_error(-26, RPC_DISABLED_OPCODE_ERROR, + node.sendrawtransaction, tx0_hex) + + def add_tx(block, tx): + block.vtx.append(tx) + block.hashMerkleRoot = block.calc_merkle_root() + block.solve() + + b = next_block(MONOLITH_START_TIME + 6) + add_tx(b, tx0) + yield rejected(b, RejectResult(16, b'blk-bad-inputs')) + + self.log.info("Activates the new opcodes") + fork_block = next_block(MONOLITH_START_TIME + 6) + yield accepted(fork_block) + + assert_equal(node.getblockheader(node.getbestblockhash())['mediantime'], + MONOLITH_START_TIME) + + tx0id = node.sendrawtransaction(tx0_hex) + assert(tx0id in set(node.getrawmempool())) + + # Transactions can also be included in blocks. + monolithblock = next_block(MONOLITH_START_TIME + 7) + add_tx(monolithblock, tx0) + yield accepted(monolithblock) + + self.log.info("Cause a reorg that deactivate the monolith opcodes") + + # Invalidate the monolith block, ensure tx0 gets back to the mempool. + assert(tx0id not in set(node.getrawmempool())) + + node.invalidateblock(format(monolithblock.sha256, 'x')) + assert(tx0id in set(node.getrawmempool())) + + node.invalidateblock(format(fork_block.sha256, 'x')) + assert(tx0id not in set(node.getrawmempool())) + + +if __name__ == '__main__': + MonolithActivationTest().main()