diff --git a/src/test/validation_tests.cpp b/src/test/validation_tests.cpp
--- a/src/test/validation_tests.cpp
+++ b/src/test/validation_tests.cpp
@@ -1,5 +1,5 @@
 // Copyright (c) 2011-2016 The Bitcoin Core developers
-// Copyright (c) 2017 The Bitcoin developers
+// Copyright (c) 2017-2018 The Bitcoin developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
@@ -7,6 +7,7 @@
 #include "config.h"
 #include "consensus/consensus.h"
 #include "primitives/transaction.h"
+#include "script/interpreter.h"
 #include "test/test_bitcoin.h"
 #include "util.h"
 #include "validation.h"
@@ -17,6 +18,10 @@
 
 #include <boost/test/unit_test.hpp>
 
+// defined in validation.cpp but not declared in validation.h - dont want to
+// expose in header just for tests
+extern uint32_t GetBlockScriptFlags(const CBlockIndex *pindex, const Config &config);
+
 static CBlock makeLargeDummyBlock(const size_t num_tx) {
     CBlock block;
     block.vtx.reserve(num_tx);
@@ -76,4 +81,30 @@
     BOOST_CHECK_NO_THROW({ LoadExternalBlockFile(config, fp, 0); });
 }
 
+BOOST_AUTO_TEST_CASE(getblockscriptflags_monolith) {
+    GlobalConfig config;
+
+    // Create genesis block and add new block to tip
+    CBlockIndex newBlock;
+    CBlockIndex *genesisBlock = chainActive.Tip();
+    newBlock.pprev = genesisBlock;
+    chainActive.SetTip(&newBlock);
+
+    // Check that monolith hard fork is not enabled
+    BOOST_CHECK(!IsMonolithEnabled(config, genesisBlock));
+
+    uint32_t flags = GetBlockScriptFlags(chainActive.Tip(), config);
+    BOOST_CHECK((flags & SCRIPT_ENABLE_MONOLITH_OPCODES) == 0);
+
+    // Activate monolith hard fork (May 15, 2018)
+    const int64_t monolithTime =
+        config.GetChainParams().GetConsensus().monolithActivationTime;
+    genesisBlock->nTime = monolithTime;
+
+    BOOST_CHECK(IsMonolithEnabled(config, genesisBlock));
+
+    flags = GetBlockScriptFlags(chainActive.Tip(), config);
+    BOOST_CHECK((flags & SCRIPT_ENABLE_MONOLITH_OPCODES) != 0);
+}
+
 BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/validation.cpp b/src/validation.cpp
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1,6 +1,6 @@
 // Copyright (c) 2009-2010 Satoshi Nakamoto
 // Copyright (c) 2009-2016 The Bitcoin Core developers
-// Copyright (c) 2017 The Bitcoin developers
+// Copyright (c) 2017-2018 The Bitcoin developers
 // Distributed under the MIT software license, see the accompanying
 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
 
@@ -194,7 +194,7 @@
 static void FindFilesToPrune(std::set<int> &setFilesToPrune,
                              uint64_t nPruneAfterHeight);
 static FILE *OpenUndoFile(const CDiskBlockPos &pos, bool fReadOnly = false);
-static uint32_t GetBlockScriptFlags(const CBlockIndex *pindex,
+uint32_t GetBlockScriptFlags(const CBlockIndex *pindex,
                                     const Config &config);
 
 static bool IsFinalTx(const CTransaction &tx, int nBlockHeight,
@@ -993,6 +993,10 @@
                 gArgs.GetArg("-promiscuousmempoolflags", scriptVerifyFlags);
         }
 
+        if (hasMonolith) {
+            scriptVerifyFlags |= SCRIPT_ENABLE_MONOLITH_OPCODES;
+        }
+
         const bool hasReplayProtection =
             IsReplayProtectionEnabledForCurrentBlock(config);
         if (hasReplayProtection) {
@@ -1564,6 +1568,21 @@
         if (pvChecks) {
             pvChecks->push_back(std::move(check));
         } else if (!check()) {
+            if (flags & ~SCRIPT_ENABLE_MONOLITH_OPCODES) {
+                // Check whether the failure was caused by an opcode used in
+                // the monolith fork deployment. We do not want to trigger DoS
+                // protection and cause a network split if so.
+                CScriptCheck check2(scriptPubKey, amount, tx, i,
+                                    flags |=
+                                        SCRIPT_ENABLE_MONOLITH_OPCODES,
+                                    sigCacheStore, txdata);
+                if (check2()) {
+                    return state.Invalid(
+                        false, REJECT_INVALID,
+                        strprintf("monolith-fork-script-failure (%s)",
+                                  ScriptErrorString(check.GetScriptError())));
+                }
+            }
             if (flags & STANDARD_NOT_MANDATORY_VERIFY_FLAGS) {
                 // Check whether the failure was caused by a non-mandatory
                 // script verification check, such as non-standard DER encodings
@@ -1890,7 +1909,7 @@
 static ThresholdConditionCache warningcache[VERSIONBITS_NUM_BITS];
 
 // Returns the script flags which should be checked for a given block
-static uint32_t GetBlockScriptFlags(const CBlockIndex *pindex,
+uint32_t GetBlockScriptFlags(const CBlockIndex *pindex,
                                     const Config &config) {
     AssertLockHeld(cs_main);
     const Consensus::Params &consensusparams =
@@ -1935,6 +1954,10 @@
         flags |= SCRIPT_VERIFY_NULLFAIL;
     }
 
+    if (IsMonolithEnabled(config, pindex)) {
+        flags |= SCRIPT_ENABLE_MONOLITH_OPCODES;
+    }
+
     // We make sure this node will have replay protection during the next hard
     // fork.
     if (IsReplayProtectionEnabled(config, pindex->pprev)) {
diff --git a/test/functional/monolith-opcodes.py b/test/functional/monolith-opcodes.py
new file mode 100755
--- /dev/null
+++ b/test/functional/monolith-opcodes.py
@@ -0,0 +1,295 @@
+#!/usr/bin/env python3
+# Copyright (c) 2015-2016 The Bitcoin Core developers
+# Copyright (c) 2017-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 opcodes and the different consensus
+related to this activation.
+"""
+
+from test_framework.test_framework import ComparisonTestFramework
+from test_framework.util import assert_equal, assert_raises_rpc_error
+from test_framework.comptool import TestManager, TestInstance, RejectResult
+from test_framework.blocktools import *
+import time
+from test_framework.key import CECKey
+from test_framework.script import *
+
+# Far into the future.
+MONOLITH_START_TIME = 2000000000
+
+# Error due to disabled opcode.
+DISABLED_OPCODE_ERROR = b'monolith-fork-script-failure (Attempted to use a disabled opcode)'
+RPC_DISABLED_OPCODE_ERROR = "16: " + DISABLED_OPCODE_ERROR.decode("utf-8")
+
+
+class PreviousSpendableOutput(object):
+
+    def __init__(self, tx=CTransaction(), n=-1):
+        self.tx = tx
+        self.n = n  # the output we're spending.
+
+
+class MonolithOpcodeTest(ComparisonTestFramework):
+
+    def set_test_params(self):
+        self.num_nodes = 1
+        self.setup_clean_chain = True
+        self.block_heights = {}
+        self.tip = None
+        self.blocks = {}
+        self.extra_args = [
+            ['-whitelist=127.0.0.1', "-monolithactivationtime=%d" % MONOLITH_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.nodes[0].setmocktime(MONOLITH_START_TIME)
+        self.test.run()
+
+    def next_block(self, number):
+        if self.tip == None:
+            base_block_hash = self.genesis_hash
+            block_time = int(time.time()) + 1
+        else:
+            base_block_hash = self.tip.sha256
+            block_time = self.tip.nTime + 1
+        # First create the coinbase.
+        height = self.block_heights[base_block_hash] + 1
+        coinbase = create_coinbase(height)
+        coinbase.rehash()
+        block = create_block(base_block_hash, coinbase, block_time)
+
+        # Do PoW.
+        block.solve()
+        self.tip = block
+        self.block_heights[block.sha256] = height
+        assert number not in self.blocks
+        self.blocks[number] = block
+        return block
+
+    def get_tests(self):
+        self.genesis_hash = int(self.nodes[0].getbestblockhash(), 16)
+        self.block_heights[self.genesis_hash] = 0
+        spendable_outputs = []
+
+        # Save the current tip so it can be spent by a later block.
+        def save_spendable_output():
+            spendable_outputs.append(self.tip)
+
+        # Get an output that we previously marked as spendable.
+        def get_spendable_output():
+            return PreviousSpendableOutput(spendable_outputs.pop(0).vtx[0], 0)
+
+        # Returns a test case that asserts that the current tip was accepted.
+        def accepted():
+            return TestInstance([[self.tip, True]])
+
+        # Returns a test case that asserts that the current tip was rejected.
+        def rejected(reject=None):
+            if reject is None:
+                return TestInstance([[self.tip, False]])
+            else:
+                return TestInstance([[self.tip, reject]])
+
+        # Move the tip back to a previous block.
+        def tip(number):
+            self.tip = self.blocks[number]
+
+        # Adds transactions to the block and updates state.
+        def update_block(block_number, new_transactions):
+            [tx.rehash() for tx in new_transactions]
+            block = self.blocks[block_number]
+            block.vtx.extend(new_transactions)
+            old_sha256 = block.sha256
+            block.hashMerkleRoot = block.calc_merkle_root()
+            block.solve()
+            # Update the internal state just like in next_block.
+            self.tip = block
+            if block.sha256 != old_sha256:
+                self.block_heights[
+                    block.sha256] = self.block_heights[old_sha256]
+                del self.block_heights[old_sha256]
+            self.blocks[block_number] = block
+            return block
+
+        # Shorthand for functions.
+        block = self.next_block
+        node = self.nodes[0]
+
+        # Create a new block.
+        b0 = block(0)
+        save_spendable_output()
+        yield accepted()
+
+        # Now we need that block to mature so we can spend the coinbase.
+        test = TestInstance(sync_every_block=False)
+        for i in range(193):
+            block(5000 + i)
+            test.blocks_and_transactions.append([self.tip, True])
+            save_spendable_output()
+        yield test
+
+        # Collect spendable outputs now to avoid cluttering the code later on.
+        out = []
+        for i in range(100):
+            out.append(get_spendable_output())
+
+        # Generate a key pair to test P2SH sigops count.
+        private_key = CECKey()
+        private_key.set_secretbytes(b"monolithopcodes")
+        public_key = private_key.get_pubkey()
+
+        # This is a little handier to use than the version in blocktools.py.
+        def create_fund_and_spend_tx(spend, scriptPubKey=[OP_TRUE], scriptSig=[]):
+            # Fund transaction.
+            txfund = create_transaction(
+                spend.tx, spend.n, b'', 50 * COIN, CScript(scriptPubKey))
+            txfund.rehash()
+
+            # Spend transaction.
+            txspend = CTransaction()
+            txspend.vout.append(
+                CTxOut(50 * COIN - 1000, CScript(scriptPubKey)))
+            txspend.vin.append(CTxIn(COutPoint(txfund.sha256, 0), b''))
+
+            txspend.vin[0].scriptSig = CScript(scriptSig)
+            txspend.rehash()
+            return [txfund, txspend]
+
+        def send_transaction_to_mempool(tx):
+            tx_id = node.sendrawtransaction(ToHex(tx))
+            assert(tx_id in set(node.getrawmempool()))
+            return tx_id
+
+        def get_banscore():
+            s = node.getpeerinfo()
+            return s[0]['banscore']
+
+        # Normal txns can get in the mempool before monolith activates.
+        txns = create_fund_and_spend_tx(out[0])
+        send_transaction_to_mempool(txns[0])
+        send_transaction_to_mempool(txns[1])
+
+        # And they get mined in a block properly.
+        b1 = block(1)
+        update_block(1, txns)
+        yield accepted()
+
+        # Transactions with the monolith opcodes are rejected.
+        opand_txns = create_fund_and_spend_tx(out[1], [OP_AND], [0x01, 0x01])
+        send_transaction_to_mempool(opand_txns[0])
+        oldbs = get_banscore()
+        assert_raises_rpc_error(-26, RPC_DISABLED_OPCODE_ERROR,
+                                node.sendrawtransaction, ToHex(opand_txns[1]))
+
+        # Check that ban score does not increase for p2p transaction relaying.
+        assert_equal(oldbs, get_banscore())
+
+        # Check that blocks containing monolith opcodes are rejected as well.
+        b9000 = block(9000)
+        update_block(9000, opand_txns)
+        yield rejected(RejectResult(16, b'blk-bad-inputs'))
+        # Relaying of an invalid block will increase the banscore.
+        assert(oldbs < get_banscore())
+
+        # Rewind bad block.
+        tip(1)
+
+        # Create block immediately preceding monolith activation by two milliseconds.
+        b2 = block(2)
+        b2.nTime = MONOLITH_START_TIME - 2
+        update_block(2, [])
+        yield accepted()
+        for i in range(5):
+            block(3 + i)
+            test.blocks_and_transactions.append([self.tip, True])
+        yield test
+
+        # Check we are just before the monolith activation time by two milliseconds.
+        # Again this is based on time only and not MTP activation.
+        assert_equal(
+            node.getblockheader(node.getbestblockhash())['mediantime'],
+                     MONOLITH_START_TIME - 2)
+
+        # Txns with monolith opcodes are still rejected.
+        send_transaction_to_mempool(opand_txns[0])
+        assert_raises_rpc_error(-26, RPC_DISABLED_OPCODE_ERROR,
+                                node.sendrawtransaction, ToHex(opand_txns[1]))
+
+        # Create new pre-fork block preceding monolith activation time by one millisecond.
+        b9001 = block(9001)
+        b9001.nTime = MONOLITH_START_TIME - 1
+        update_block(9001, opand_txns)
+        yield rejected(RejectResult(16, b'blk-bad-inputs'))
+        # This should increase the banscore since it's an invalid block.
+        assert(oldbs < get_banscore())
+
+        # Rewind bad block
+        tip(7)
+
+        # Submit block exceeding monolith activation time (but not MTP activation).
+        # Include premature monolith opcode txns and test for failure.
+        b9002 = block(9002)
+        b9002.nTime = MONOLITH_START_TIME + 2000
+        update_block(9002, opand_txns)
+        yield rejected(RejectResult(16, b'blk-bad-inputs'))
+
+        # Test that monolith transactions are not relayed until valid MTP activation.
+        assert_raises_rpc_error(-26, RPC_DISABLED_OPCODE_ERROR,
+                                node.sendrawtransaction, ToHex(opand_txns[1]))
+
+        # Rewind bad block
+        tip(7)
+
+        # Create 2 more blocks until MTP activation time starts.
+        block(8)
+        preforkblockid0 = node.getbestblockhash()
+        yield accepted()
+        block(9)
+        preforkblockid1 = node.getbestblockhash()
+        yield accepted()
+
+        # Check we have our first block meeting MTP activation initiation.
+        assert_equal(
+            node.getblockheader(node.getbestblockhash())['mediantime'],
+                     MONOLITH_START_TIME)
+
+        # Txns with monolith opcodes are now valid.
+        forkblockid = node.getbestblockhash()
+        send_transaction_to_mempool(opand_txns[0])
+        opand_txid = send_transaction_to_mempool(opand_txns[1])
+        assert(opand_txid in set(node.getrawmempool()))
+
+        # Force a re-org post-activation.
+        # This should not clear the mempool.
+        block(10)
+        postforkblockid0 = node.getbestblockhash()
+        yield accepted()
+        block(11)
+        postforkblockid1 = node.getbestblockhash()
+        yield accepted()
+        assert(opand_txid in set(node.getrawmempool()))
+
+        # Deactivating monolith and force a re-org.
+        # This should clear the mempool.
+        node.invalidateblock(preforkblockid0)
+        assert(opand_txid not in set(node.getrawmempool()))
+
+
+        # Reconnect invalidated blocks and mine new block with monolith opcodes.
+        node.reconsiderblock(preforkblockid0)
+        node.reconsiderblock(preforkblockid1)
+        node.reconsiderblock(forkblockid)
+        node.reconsiderblock(postforkblockid0)
+        node.reconsiderblock(postforkblockid1)
+        block(12)
+        update_block(12, opand_txns)
+        yield accepted()
+
+
+if __name__ == '__main__':
+    MonolithOpcodeTest().main()