diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1575,6 +1575,10 @@ flags |= SCRIPT_VERIFY_CLEANSTACK; } + if (IsGreatWallEnabled(config, pChainTip)) { + flags |= SCRIPT_VERIFY_NULLDUMMY; + } + // We make sure this node will have replay protection during the next hard // fork. if (IsReplayProtectionEnabled(config, pChainTip)) { @@ -1916,6 +1920,14 @@ g_mempool.clear(); } + // If non-standard NULLDUMMY transactions were accepted before enforcement, + // then we risk having invalid tranasctions in the mempool. Clear it to be + // safe. + if (!fRequireStandard && (IsGreatWallEnabled(config, pindex)) && + !IsGreatWallEnabled(config, pindex->pprev)) { + g_mempool.clear(); + } + return true; } diff --git a/test/functional/abc-nulldummy-activation.py b/test/functional/abc-nulldummy-activation.py new file mode 100755 --- /dev/null +++ b/test/functional/abc-nulldummy-activation.py @@ -0,0 +1,96 @@ +#!/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. + +from test_framework.blocktools import create_coinbase, create_block +from test_framework.cdefs import GREAT_WALL_ACTIVATION_TIME +from test_framework.test_framework import BitcoinTestFramework +from test_framework.txtools import pad_raw_tx +from test_framework.util import sync_blocks, bytes_to_hex_str, assert_equal +import time + +IN_THE_FUTURE = int(time.time()) * 2 + +""" +This tests the mempool behavior during 'great wall' activation with regards to +nulldummy enforcement. + +This test can be removed after activation. +""" + + +class NullDummyActivation(BitcoinTestFramework): + + def set_test_params(self): + self.num_nodes = 2 + self.setup_clean_chain = True + common_args = [ + '-greatwallactivationtime=%d' % GREAT_WALL_ACTIVATION_TIME, + "-replayprotectionactivationtime=%d" % IN_THE_FUTURE] + + self.extra_args = [ + common_args + ["-acceptnonstdtxn=0"], + common_args + ["-acceptnonstdtxn=1"]] + + # 'Great wall' is NOT activated at the beginning of the test. + self.mocktime = GREAT_WALL_ACTIVATION_TIME - 1000 + + def _create_tx(self, txid_in, n): + to_addr = self.nodes[0].getnewaddress() + rawtx = self.nodes[0].createrawtransaction([{ + 'txid': txid_in, + 'vout': 0}], {to_addr: 42}) + rawtx = pad_raw_tx(rawtx) + rawtx = self.nodes[0].signrawtransaction(rawtx)['hex'] + return rawtx + + def _create_coinbase_utxo(self): + coinbase_block = self.nodes[0].generate(1) + coinbase_id = self.nodes[0].getblock(coinbase_block[0])['tx'][0] + return coinbase_id + + def run_test(self): + coinbase_id = self._create_coinbase_utxo() + self.nodes[0].generate(101) + sync_blocks(self.nodes) + + # Test that mempool is cleared after activation + rawtx = self._create_tx(coinbase_id, n=0) + for n in self.nodes: + n.sendrawtransaction(rawtx, True) + + while True: + lastblock = self.nodes[0].getblockheader( + self.nodes[0].getbestblockhash()) + + mtp = lastblock['mediantime'] + if mtp < GREAT_WALL_ACTIVATION_TIME: + # Not activated. Transaction should stay in mempool on both + # nodes. + assert_equal(1, self.nodes[0].getmempoolinfo()['size']) + assert_equal(1, self.nodes[1].getmempoolinfo()['size']) + + else: + # New rules enforced. Nodes accepting non-standard transactions + # should clear its mempool, as it may have had a non-compliant + # NULLDUMMY transaction in it + + # Node 0 did not accept non-standard + assert_equal(1, self.nodes[0].getmempoolinfo()['size']) + # Node 1 did accept non-standard + assert_equal(0, self.nodes[1].getmempoolinfo()['size']) + return + + next_time = lastblock['time'] + 600 + next_height = lastblock['height'] + 1 + coinbase = create_coinbase(next_height) + block = create_block( + int("0x" + lastblock['hash'], 0), coinbase, next_time) + for n in self.nodes: + n.setmocktime(next_time) + n.submitblock(bytes_to_hex_str(block.serialize())) + + +if __name__ == '__main__': + NullDummyActivation().main() diff --git a/test/functional/abc-replay-protection.py b/test/functional/abc-replay-protection.py --- a/test/functional/abc-replay-protection.py +++ b/test/functional/abc-replay-protection.py @@ -9,6 +9,7 @@ It is derived from the much more complex p2p-fullblocktest. """ +from test_framework.cdefs import GREAT_WALL_ACTIVATION_TIME 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 @@ -54,7 +55,10 @@ def next_block(self, number): if self.tip == None: base_block_hash = self.genesis_hash - block_time = int(time.time()) + 1 + + # Activation of GREAT_WALL_ACTIVATION_TIME clears the mempool, so + # to avoid this, the tip is set to start after this upgrade. + block_time = max(int(time.time() + 1), GREAT_WALL_ACTIVATION_TIME) else: base_block_hash = self.tip.sha256 block_time = self.tip.nTime + 1 diff --git a/test/functional/feature_nulldummy.py b/test/functional/feature_nulldummy.py --- a/test/functional/feature_nulldummy.py +++ b/test/functional/feature_nulldummy.py @@ -1,16 +1,19 @@ #!/usr/bin/env python3 # Copyright (c) 2016 The Bitcoin Core developers +# 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. -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * -from test_framework.mininode import CTransaction, network_thread_start -from test_framework.blocktools import create_coinbase, create_block +from test_framework.blocktools import create_coinbase, create_block, make_conform_to_ctor +from test_framework.cdefs import GREAT_WALL_ACTIVATION_TIME +from test_framework.mininode import CTransaction from test_framework.script import CScript +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import hex_str_to_bytes, bytes_to_hex_str, assert_equal, assert_raises_rpc_error from io import BytesIO import time +IN_THE_FUTURE = int(time.time()) * 2 NULLDUMMY_ERROR = "64: non-mandatory-script-verify-flag (Dummy CHECKMULTISIG argument must be zero)" @@ -28,62 +31,99 @@ ''' -This test is meant to exercise NULLDUMMY softfork. -Connect to a single node. -Generate 2 blocks (save the coinbases for later). -Generate 427 more blocks. -[Policy/Consensus] Check that NULLDUMMY compliant transactions are accepted in the 430th block. -[Policy] Check that non-NULLDUMMY transactions are rejected before activation. -[Consensus] Check that the new NULLDUMMY rules are not enforced on the 431st block. -[Policy/Consensus] Check that the new NULLDUMMY rules are enforced on the 432nd block. +This test is meant to exercise NULLDUMMY enforcement during 'great wall' +protocol upgrade. ''' +def fetch_best_header(node): + return LastBlock(node.getblockheader(node.getbestblockhash())) + + +class LastBlock: + def __init__(self, header): + self.header = header + + def __getattr__(self, name): + return self.header[name] + + def tip(self): + return int(self.hash, 16) + + class NULLDUMMYTest(BitcoinTestFramework): def set_test_params(self): + self.lastblock = None self.num_nodes = 1 self.setup_clean_chain = True - self.extra_args = [['-whitelist=127.0.0.1']] + + self.extra_args = [[ + '-whitelist=127.0.0.1', + '-greatwallactivationtime=%d' % GREAT_WALL_ACTIVATION_TIME, + "-replayprotectionactivationtime=%d" % (IN_THE_FUTURE)]] + + # NULLDUMMY is NOT activated at the beginning of the test. + self.mocktime = GREAT_WALL_ACTIVATION_TIME - 1000 def run_test(self): - self.address = self.nodes[0].getnewaddress() - self.ms_address = self.nodes[0].addmultisigaddress(1, [self.address]) + node = self.nodes[0] + self.address = node.getnewaddress() + self.ms_address = node.addmultisigaddress(1, [self.address]) - network_thread_start() - self.coinbase_blocks = self.nodes[0].generate(2) # Block 2 + # Create and mature coinbase for spending + self.coinbase_blocks = node.generate(2) coinbase_txid = [] for i in self.coinbase_blocks: - coinbase_txid.append(self.nodes[0].getblock(i)['tx'][0]) - self.nodes[0].generate(427) # Block 429 - self.lastblockhash = self.nodes[0].getbestblockhash() - self.tip = int("0x" + self.lastblockhash, 0) - self.lastblockheight = 429 - self.lastblocktime = int(time.time()) + 429 + coinbase_txid.append(node.getblock(i)['tx'][0]) + node.generate(101) + + self.lastblock = fetch_best_header(node) self.log.info( - "Test 1: NULLDUMMY compliant base transactions should be accepted to mempool and mined before activation [430]") - test1txs = [self.create_transaction( - self.nodes[0], coinbase_txid[0], self.ms_address, 49)] - txid1 = self.nodes[0].sendrawtransaction( + "Test 1 [Policy/Consensus]: NULLDUMMY compliant base transactions should be accepted to mempool and mined before activation") + test1txs = [] + test1txs.append(self.create_transaction( + node, coinbase_txid[0], self.ms_address, 49)) + txid1 = node.sendrawtransaction( bytes_to_hex_str(test1txs[0].serialize()), True) + test1txs.append(self.create_transaction( - self.nodes[0], txid1, self.ms_address, 48)) - txid2 = self.nodes[0].sendrawtransaction( + node, txid1, self.ms_address, 48)) + txid2 = node.sendrawtransaction( bytes_to_hex_str(test1txs[1].serialize()), True) - self.block_submit(self.nodes[0], test1txs, True) + + self.block_submit(node, test1txs, accept=True) self.log.info( - "Test 2: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation") + "Test 2 [Policy]: Non-NULLDUMMY base multisig transaction should not be accepted to mempool before activation") test2tx = self.create_transaction( - self.nodes[0], txid2, self.ms_address, 48) + node, txid2, self.ms_address, 48) trueDummy(test2tx) + txid4 = test2tx.hash + assert_raises_rpc_error(-26, NULLDUMMY_ERROR, - self.nodes[0].sendrawtransaction, bytes_to_hex_str(test2tx.serialize()), True) + node.sendrawtransaction, bytes_to_hex_str(test2tx.serialize()), True) + + self.log.info( + "Test 3 [Consensus]: Non-NULLDUMMY base transactions should be accepted in a block before activation") + self.block_submit(node, [test2tx], accept=True) + + self.activate_nulldummy_enforcement(node) self.log.info( - "Test 3: Non-NULLDUMMY base transactions should be accepted in a block before activation [431]") - self.block_submit(self.nodes[0], [test2tx], True) + "Test 4 [Consensus]: Non-NULLDUMMY base multisig transaction is invalid after activation") + test4tx = self.create_transaction(node, txid4, self.address, amount=47) + trueDummy(test4tx) + assert_raises_rpc_error(-26, NULLDUMMY_ERROR, node.sendrawtransaction, + bytes_to_hex_str(test4tx.serialize()), True) + self.block_submit(node, [test4tx], accept=False) + + self.log.info( + "Test 5 [Consensus]: NULLDUMMY compliant base transactions should be accepted to mempool and in block after activation") + test5tx = self.create_transaction(node, txid4, self.address, amount=47) + node.sendrawtransaction(bytes_to_hex_str(test5tx.serialize()), True) + self.block_submit(node, [test5tx], accept=True) def create_transaction(self, node, txid, to_address, amount): inputs = [{"txid": txid, "vout": 0}] @@ -96,26 +136,34 @@ return tx def block_submit(self, node, txs, accept=False): - block = create_block(self.tip, create_coinbase( - self.lastblockheight + 1), self.lastblocktime + 1) + block = create_block(self.lastblock.tip(), create_coinbase( + self.lastblock.height + 1), self.lastblock.time + 1) block.nVersion = 4 - for tx in txs: - tx.rehash() - block.vtx.append(tx) - block.vtx = [block.vtx[0]] + \ - sorted(block.vtx[1:], key=lambda tx: tx.get_id()) + block.vtx.extend(txs) + make_conform_to_ctor(block) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() block.solve() node.submitblock(bytes_to_hex_str(block.serialize())) if (accept): assert_equal(node.getbestblockhash(), block.hash) - self.tip = block.sha256 - self.lastblockhash = block.hash - self.lastblocktime += 1 - self.lastblockheight += 1 + self.lastblock = fetch_best_header(node) else: - assert_equal(node.getbestblockhash(), self.lastblockhash) + assert_equal(node.getbestblockhash(), self.lastblock.hash) + + def activate_nulldummy_enforcement(self, node): + while True: + self.lastblock = fetch_best_header(node) + + if self.lastblock.mediantime >= GREAT_WALL_ACTIVATION_TIME: + return + + next_time = self.lastblock.time + 600 + coinbase = create_coinbase(self.lastblock.height + 1) + block = create_block(self.lastblock.tip(), coinbase, next_time) + + node.setmocktime(next_time) + node.submitblock(bytes_to_hex_str(block.serialize())) if __name__ == '__main__': diff --git a/test/functional/test_framework/cdefs.py b/test/functional/test_framework/cdefs.py --- a/test/functional/test_framework/cdefs.py +++ b/test/functional/test_framework/cdefs.py @@ -91,6 +91,9 @@ # Maximum bytes in a TxOut pubkey script MAX_TXOUT_PUBKEY_SCRIPT = 10000 +# MTP time for activation of 'great wall' network upgrade. +GREAT_WALL_ACTIVATION_TIME = 1557921600 + if __name__ == "__main__": # Output values if run standalone to verify print("DEFAULT_MAX_BLOCK_SIZE = %d (bytes)" % DEFAULT_MAX_BLOCK_SIZE)