diff --git a/src/chainparams.cpp b/src/chainparams.cpp --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -123,8 +123,8 @@ // November 15, 2018 hard fork consensus.magneticAnomalyHeight = 556766; - // Nov 15, 2019 12:00:00 UTC protocol upgrade - consensus.gravitonActivationTime = 1573819200; + // November 15, 2019 protocol upgrade + consensus.gravitonHeight = 609135; // May 15, 2020 12:00:00 UTC protocol upgrade consensus.phononActivationTime = 1589544000; @@ -294,8 +294,8 @@ // November 15, 2018 hard fork consensus.magneticAnomalyHeight = 1267996; - // Nov 15, 2019 12:00:00 UTC protocol upgrade - consensus.gravitonActivationTime = 1573819200; + // November 15, 2019 protocol upgrade + consensus.gravitonHeight = 1341711; // May 15, 2020 12:00:00 UTC protocol upgrade consensus.phononActivationTime = 1589544000; @@ -421,8 +421,8 @@ // November 15, 2018 hard fork is always on on regtest. consensus.magneticAnomalyHeight = 0; - // Nov 15, 2019 12:00:00 UTC protocol upgrade - consensus.gravitonActivationTime = 1573819200; + // November 15, 2019 protocol upgrade + consensus.gravitonHeight = 0; // May 15, 2020 12:00:00 UTC protocol upgrade consensus.phononActivationTime = 1589544000; diff --git a/src/consensus/activation.cpp b/src/consensus/activation.cpp --- a/src/consensus/activation.cpp +++ b/src/consensus/activation.cpp @@ -48,15 +48,18 @@ return IsMagneticAnomalyEnabled(params, pindexPrev->nHeight); } +static bool IsGravitonEnabled(const Consensus::Params ¶ms, + int32_t nHeight) { + return nHeight >= params.gravitonHeight; +} + bool IsGravitonEnabled(const Consensus::Params ¶ms, const CBlockIndex *pindexPrev) { if (pindexPrev == nullptr) { return false; } - return pindexPrev->GetMedianTimePast() >= - gArgs.GetArg("-gravitonactivationtime", - params.gravitonActivationTime); + return IsGravitonEnabled(params, pindexPrev->nHeight); } bool IsPhononEnabled(const Consensus::Params ¶ms, diff --git a/src/consensus/params.h b/src/consensus/params.h --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -34,8 +34,8 @@ int daaHeight; /** Block height at which the magnetic anomaly activation becomes active */ int magneticAnomalyHeight; - /** Unix time used for MTP activation of Nov 15 2019 12:00:00 UTC upgrade */ - int gravitonActivationTime; + /** Block height at which the graviton activation becomes active */ + int gravitonHeight; /** Unix time used for MTP activation of 15 May 2020 12:00:00 UTC upgrade */ int phononActivationTime; /** Proof of work parameters */ diff --git a/src/init.cpp b/src/init.cpp --- a/src/init.cpp +++ b/src/init.cpp @@ -992,9 +992,6 @@ OptionsCategory::HIDDEN); gArgs.AddArg("-activatenextupgrade", "", false, OptionsCategory::HIDDEN); - // TODO remove after the Nov 2019 upgrade - gArgs.AddArg("-gravitonactivationtime", "", false, OptionsCategory::HIDDEN); - // TODO remove after the May 2020 upgrade gArgs.AddArg("-phononactivationtime", "", false, OptionsCategory::HIDDEN); } diff --git a/src/test/activation_tests.cpp b/src/test/activation_tests.cpp --- a/src/test/activation_tests.cpp +++ b/src/test/activation_tests.cpp @@ -23,32 +23,6 @@ BOOST_CHECK_EQUAL(blocks.back().GetMedianTimePast(), mtp); } -BOOST_AUTO_TEST_CASE(isgravitonenabled) { - CBlockIndex prev; - - const Consensus::Params ¶ms = Params().GetConsensus(); - const auto activation = - gArgs.GetArg("-gravitonactivationtime", params.gravitonActivationTime); - SetMockTime(activation - 1000000); - - BOOST_CHECK(!IsGravitonEnabled(params, nullptr)); - - std::array blocks; - for (size_t i = 1; i < blocks.size(); ++i) { - blocks[i].pprev = &blocks[i - 1]; - } - BOOST_CHECK(!IsGravitonEnabled(params, &blocks.back())); - - SetMTP(blocks, activation - 1); - BOOST_CHECK(!IsGravitonEnabled(params, &blocks.back())); - - SetMTP(blocks, activation); - BOOST_CHECK(IsGravitonEnabled(params, &blocks.back())); - - SetMTP(blocks, activation + 1); - BOOST_CHECK(IsGravitonEnabled(params, &blocks.back())); -} - BOOST_AUTO_TEST_CASE(isphononenabled) { if (!gArgs.GetBoolArg("-activatenextupgrade", false)) { CBlockIndex prev; diff --git a/src/test/test_bitcoin_main.cpp b/src/test/test_bitcoin_main.cpp --- a/src/test/test_bitcoin_main.cpp +++ b/src/test/test_bitcoin_main.cpp @@ -30,7 +30,6 @@ std::set testArgs = { "-activatenextupgrade", - "-gravitonactivationtime", "-phononactivationtime", }; diff --git a/src/validation.cpp b/src/validation.cpp --- a/src/validation.cpp +++ b/src/validation.cpp @@ -465,12 +465,6 @@ return IsMagneticAnomalyEnabled(params, chainActive.Tip()); } -static bool IsGravitonEnabledForCurrentBlock(const Consensus::Params ¶ms) - EXCLUSIVE_LOCKS_REQUIRED(cs_main) { - AssertLockHeld(cs_main); - return IsGravitonEnabled(params, chainActive.Tip()); -} - // Command-line argument "-replayprotectionactivationtime=" will // cause the node to switch to replay protected SigHash ForkID value when the // median timestamp of the previous 11 blocks is greater than or equal to @@ -831,6 +825,9 @@ if (!CheckInputsFromMempoolAndCache(tx, state, view, pool, nextBlockScriptVerifyFlags, true, txdata)) { + // This can occur under some circumstances, if the node receives an + // unrequested tx which is invalid due to new consensus rules not + // being activated yet (during IBD). return error("%s: BUG! PLEASE REPORT THIS! CheckInputs failed " "against next-block but not STANDARD flags %s, %s", __func__, txid.ToString(), FormatStateMessage(state)); diff --git a/test/functional/abc-minimaldata-activation.py b/test/functional/abc-minimaldata.py rename from test/functional/abc-minimaldata-activation.py rename to test/functional/abc-minimaldata.py --- a/test/functional/abc-minimaldata-activation.py +++ b/test/functional/abc-minimaldata.py @@ -3,7 +3,7 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ -This tests the activation of MINIMALDATA rule to consensus (from standard). +This tests the MINIMALDATA consensus rule. - test rejection in mempool, with error changing before/after activation. - test acceptance in blocks before activation, and rejection after. - check non-banning for peers who send invalid txns that would have been valid @@ -37,33 +37,21 @@ ) from test_framework.test_framework import BitcoinTestFramework from test_framework.txtools import pad_tx -from test_framework.util import assert_equal, assert_raises_rpc_error +from test_framework.util import assert_raises_rpc_error -# the upgrade activation time, which we artificially set far into the future -GRAVITON_START_TIME = 2000000000 - -# If we don't do this, autoreplay protection will activate before graviton and -# all our sigs will mysteriously fail. -REPLAY_PROTECTION_START_TIME = GRAVITON_START_TIME * 2 - - -# Both before and after the upgrade, minimal push violations in mempool are -# rejected with a bannable error. +# Minimal push violations in mempool are rejected with a bannable error. MINIMALPUSH_ERROR = 'mandatory-script-verify-flag-failed (Data push larger than necessary)' # Blocks with invalid scripts give this error: BADINPUTS_ERROR = 'blk-bad-inputs' -class SchnorrTest(BitcoinTestFramework): +class MinimaldataTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.block_heights = {} - self.extra_args = [["-gravitonactivationtime={}".format( - GRAVITON_START_TIME), - "-replayprotectionactivationtime={}".format( - REPLAY_PROTECTION_START_TIME)]] + self.extra_args = [[]] def bootstrap_p2p(self, *, num_connections=1): """Add a P2P connection to the node. @@ -114,11 +102,6 @@ [tx], self.nodes[0], success=False, expect_disconnect=True, reject_reason=reject_reason) self.reconnect_p2p() - def check_for_no_ban_on_rejected_tx(self, tx, reject_reason): - """Check we are not disconnected when sending a txn that the node rejects.""" - self.nodes[0].p2p.send_txs_and_test( - [tx], self.nodes[0], success=False, reject_reason=reject_reason) - def check_for_ban_on_rejected_block(self, block, reject_reason=None): """Check we are disconnected when sending a block that the node rejects. @@ -177,72 +160,21 @@ return txspend - # make a few of these, which are nonstandard before upgrade and invalid after. + # Non minimal tx are invalid. nonminimaltx = create_fund_and_spend_tx() - nonminimaltx_2 = create_fund_and_spend_tx() - nonminimaltx_3 = create_fund_and_spend_tx() tip = self.build_block(tip, fundings) node.p2p.send_blocks_and_test([tip], node) - self.log.info("Start preupgrade tests") - - self.log.info("Sending rejected transactions via RPC") - assert_raises_rpc_error(-26, MINIMALPUSH_ERROR, - node.sendrawtransaction, ToHex(nonminimaltx)) - assert_raises_rpc_error(-26, MINIMALPUSH_ERROR, - node.sendrawtransaction, ToHex(nonminimaltx_2)) - assert_raises_rpc_error(-26, MINIMALPUSH_ERROR, - node.sendrawtransaction, ToHex(nonminimaltx_3)) - - self.log.info( - "Sending rejected transactions via net (banning)") - self.check_for_ban_on_rejected_tx( - nonminimaltx, MINIMALPUSH_ERROR) - self.check_for_ban_on_rejected_tx( - nonminimaltx_2, MINIMALPUSH_ERROR) - self.check_for_ban_on_rejected_tx( - nonminimaltx_3, MINIMALPUSH_ERROR) - - assert_equal(node.getrawmempool(), []) - - self.log.info("Successfully mine nonstandard transaction") - tip = self.build_block(tip, [nonminimaltx]) - node.p2p.send_blocks_and_test([tip], node) - - # Activation tests - - self.log.info("Approach to just before upgrade activation") - # Move our clock to the uprade time so we will accept such future-timestamped blocks. - node.setmocktime(GRAVITON_START_TIME) - # Mine six blocks with timestamp starting at GRAVITON_START_TIME-1 - blocks = [] - for i in range(-1, 5): - tip = self.build_block(tip, nTime=GRAVITON_START_TIME + i) - blocks.append(tip) - node.p2p.send_blocks_and_test(blocks, node) - assert_equal(node.getblockchaininfo()[ - 'mediantime'], GRAVITON_START_TIME - 1) - - self.log.info( - "Mine the activation block itself, including a minimaldata violation at the last possible moment") - tip = self.build_block(tip, [nonminimaltx_2]) - node.p2p.send_blocks_and_test([tip], node) - - self.log.info("We have activated!") - assert_equal(node.getblockchaininfo()[ - 'mediantime'], GRAVITON_START_TIME) - - self.log.info( - "Trying to mine a minimaldata violation, but we are just barely too late") + self.log.info("Trying to mine a minimaldata violation.") self.check_for_ban_on_rejected_block( - self.build_block(tip, [nonminimaltx_3]), BADINPUTS_ERROR) + self.build_block(tip, [nonminimaltx]), BADINPUTS_ERROR) self.log.info( - "If we try to submit it by mempool or RPC we still aren't banned") + "If we try to submit it by mempool or RPC we are banned") assert_raises_rpc_error(-26, MINIMALPUSH_ERROR, - node.sendrawtransaction, ToHex(nonminimaltx_3)) + node.sendrawtransaction, ToHex(nonminimaltx)) self.check_for_ban_on_rejected_tx( - nonminimaltx_3, MINIMALPUSH_ERROR) + nonminimaltx, MINIMALPUSH_ERROR) self.log.info("Mine a normal block") tip = self.build_block(tip) @@ -250,4 +182,4 @@ if __name__ == '__main__': - SchnorrTest().main() + MinimaldataTest().main() diff --git a/test/functional/abc-schnorrmultisig-activation.py b/test/functional/abc-schnorrmultisig-activation.py deleted file mode 100755 --- a/test/functional/abc-schnorrmultisig-activation.py +++ /dev/null @@ -1,398 +0,0 @@ -#!/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 tests the activation of the upgraded CHECKMULTISIG mode that uses -Schnorr transaction signatures and repurposes the dummy element to indicate -which signatures are being checked. -- acceptance both in mempool and blocks. -- check non-banning for peers who send invalid txns that would have been valid -on the other side of the upgrade. -- check banning of peers for some fully-invalid transactions. - -Derived from abc-schnorr.py -""" - -from test_framework.blocktools import ( - create_block, - create_coinbase, - create_transaction, - make_conform_to_ctor, -) -from test_framework.key import CECKey -from test_framework.messages import ( - CBlock, - COutPoint, - CTransaction, - CTxIn, - CTxOut, - FromHex, - ToHex, -) -from test_framework.mininode import ( - P2PDataStore, -) -from test_framework import schnorr -from test_framework.script import ( - CScript, - OP_0, - OP_1, - OP_CHECKMULTISIG, - OP_TRUE, - SIGHASH_ALL, - SIGHASH_FORKID, - SignatureHashForkId, -) -from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal, assert_raises_rpc_error - -# the upgrade activation time, which we artificially set far into the future -GRAVITON_START_TIME = 2000000000 - -# If we don't do this, autoreplay protection will activate before graviton and -# all our sigs will mysteriously fail. -REPLAY_PROTECTION_START_TIME = GRAVITON_START_TIME * 2 - - -# Before the upgrade, Schnorr checkmultisig is rejected. -PREUPGRADE_SCHNORR_MULTISIG_ERROR = 'mandatory-script-verify-flag-failed (Signature cannot be 65 bytes in CHECKMULTISIG)' - -# After the upgrade, ECDSA checkmultisig with non-null dummy are invalid since -# the new mode refuses ECDSA. -POSTUPGRADE_ECDSA_NULLDUMMY_ERROR = 'mandatory-script-verify-flag-failed (Only Schnorr signatures allowed in this operation)' - -# A mandatory (bannable) error occurs when people pass Schnorr signatures into -# legacy OP_CHECKMULTISIG; this is the case on both sides of the upgrade. -SCHNORR_LEGACY_MULTISIG_ERROR = 'mandatory-script-verify-flag-failed (Signature cannot be 65 bytes in CHECKMULTISIG)' - -# Blocks with invalid scripts give this error: -BADINPUTS_ERROR = 'blk-bad-inputs' - - -# This 64-byte signature is used to test exclusion & banning according to -# the above error messages. -# Tests of real 64 byte ECDSA signatures can be found in script_tests. -sig64 = b'\0'*64 - - -class SchnorrTest(BitcoinTestFramework): - - def set_test_params(self): - self.num_nodes = 1 - self.block_heights = {} - self.extra_args = [["-gravitonactivationtime={}".format( - GRAVITON_START_TIME), - "-replayprotectionactivationtime={}".format( - REPLAY_PROTECTION_START_TIME)]] - - def bootstrap_p2p(self, *, num_connections=1): - """Add a P2P connection to the node. - - Helper to connect and wait for version handshake.""" - for _ in range(num_connections): - self.nodes[0].add_p2p_connection(P2PDataStore()) - - def reconnect_p2p(self, **kwargs): - """Tear down and bootstrap the P2P connection to the node. - - The node gets disconnected several times in this test. This helper - method reconnects the p2p and restarts the network thread.""" - self.nodes[0].disconnect_p2ps() - self.bootstrap_p2p(**kwargs) - - def getbestblock(self, node): - """Get the best block. Register its height so we can use build_block.""" - block_height = node.getblockcount() - blockhash = node.getblockhash(block_height) - block = FromHex(CBlock(), node.getblock(blockhash, 0)) - block.calc_sha256() - self.block_heights[block.sha256] = block_height - return block - - def build_block(self, parent, transactions=(), nTime=None): - """Make a new block with an OP_1 coinbase output. - - Requires parent to have its height registered.""" - parent.calc_sha256() - block_height = self.block_heights[parent.sha256] + 1 - block_time = (parent.nTime + 1) if nTime is None else nTime - - block = create_block( - parent.sha256, create_coinbase(block_height), block_time) - block.vtx.extend(transactions) - make_conform_to_ctor(block) - block.hashMerkleRoot = block.calc_merkle_root() - block.solve() - self.block_heights[block.sha256] = block_height - return block - - def check_for_ban_on_rejected_tx(self, tx, reject_reason=None): - """Check we are disconnected when sending a txn that the node rejects. - - (Can't actually get banned, since bitcoind won't ban local peers.)""" - self.nodes[0].p2p.send_txs_and_test( - [tx], self.nodes[0], success=False, expect_disconnect=True, reject_reason=reject_reason) - self.reconnect_p2p() - - def check_for_no_ban_on_rejected_tx(self, tx, reject_reason): - """Check we are not disconnected when sending a txn that the node rejects.""" - self.nodes[0].p2p.send_txs_and_test( - [tx], self.nodes[0], success=False, reject_reason=reject_reason) - - def check_for_ban_on_rejected_block(self, block, reject_reason=None): - """Check we are disconnected when sending a block that the node rejects. - - (Can't actually get banned, since bitcoind won't ban local peers.)""" - self.nodes[0].p2p.send_blocks_and_test( - [block], self.nodes[0], success=False, reject_reason=reject_reason, expect_disconnect=True) - self.reconnect_p2p() - - def run_test(self): - node, = self.nodes - - self.bootstrap_p2p() - - tip = self.getbestblock(node) - - self.log.info("Create some blocks with OP_1 coinbase for spending.") - blocks = [] - for _ in range(10): - tip = self.build_block(tip) - blocks.append(tip) - node.p2p.send_blocks_and_test(blocks, node, success=True) - spendable_outputs = [block.vtx[0] for block in blocks] - - self.log.info("Mature the blocks and get out of IBD.") - node.generate(100) - - tip = self.getbestblock(node) - - self.log.info("Setting up spends to test and mining the fundings.") - fundings = [] - - # Generate a key pair - privkeybytes = b"Schnorr!" * 4 - private_key = CECKey() - private_key.set_secretbytes(privkeybytes) - # get uncompressed public key serialization - public_key = private_key.get_pubkey() - - def create_fund_and_spend_tx(dummy=OP_0, sigtype='ecdsa'): - spendfrom = spendable_outputs.pop() - - script = CScript([OP_1, public_key, OP_1, OP_CHECKMULTISIG]) - - value = spendfrom.vout[0].nValue - - # Fund transaction - txfund = create_transaction(spendfrom, 0, b'', value, script) - txfund.rehash() - fundings.append(txfund) - - # Spend transaction - txspend = CTransaction() - txspend.vout.append( - CTxOut(value-1000, CScript([OP_TRUE]))) - txspend.vin.append( - CTxIn(COutPoint(txfund.sha256, 0), b'')) - - # Sign the transaction - sighashtype = SIGHASH_ALL | SIGHASH_FORKID - hashbyte = bytes([sighashtype & 0xff]) - sighash = SignatureHashForkId( - script, txspend, 0, sighashtype, value) - if sigtype == 'schnorr': - txsig = schnorr.sign(privkeybytes, sighash) + hashbyte - elif sigtype == 'ecdsa': - txsig = private_key.sign(sighash) + hashbyte - txspend.vin[0].scriptSig = CScript([dummy, txsig]) - txspend.rehash() - - return txspend - - # two of these transactions, which are valid both before and after upgrade. - ecdsa0tx = create_fund_and_spend_tx(OP_0, 'ecdsa') - ecdsa0tx_2 = create_fund_and_spend_tx(OP_0, 'ecdsa') - - # two of these, which are nonstandard before upgrade and invalid after. - ecdsa1tx = create_fund_and_spend_tx(OP_1, 'ecdsa') - ecdsa1tx_2 = create_fund_and_spend_tx(OP_1, 'ecdsa') - - # this one is always invalid. - schnorr0tx = create_fund_and_spend_tx(OP_0, 'schnorr') - - # this one is only going to be valid after the upgrade. - schnorr1tx = create_fund_and_spend_tx(OP_1, 'schnorr') - - tip = self.build_block(tip, fundings) - node.p2p.send_blocks_and_test([tip], node) - - self.log.info("Start preupgrade tests") - - self.log.info("Sending rejected transactions via RPC") - assert_raises_rpc_error(-26, SCHNORR_LEGACY_MULTISIG_ERROR, - node.sendrawtransaction, ToHex(schnorr0tx)) - # Since MULTISIG_SCHNORR is in mandatory flags, we are not accepting - # non-null-dummy ECDSA transactions before the upgrade. We get a - # post-upgrade error since the mempool is using post-upgrade flags. - assert_raises_rpc_error(-26, POSTUPGRADE_ECDSA_NULLDUMMY_ERROR, - node.sendrawtransaction, ToHex(ecdsa1tx)) - - # The Schnorr multisig almost gets accepted here but it finally gets - # caught in the block flags check. Note that "BUG! PLEASE REPORT - # THIS!" will appear in the log, since AcceptToMemoryPoolWorker expects - # that scriptVerifyFlags is more strict than nextBlockScriptVerifyFlags. - # For strictly subtractive ('soft forking') flags, it is fine if they - # are always part of scriptVerifyFlags and only sometimes appear in - # nextBlockScriptVerifyFlags, but for additive flags this kind of - # strange situation can be created. - # In practice, only new nodes will ever be in a pre-upgrade state, - # and they will also be in initial block download mode and hence - # not request transactions from peers. So, this weird log message - # could only be triggered by unsolicited submission of a tx, and - # it would be benign since the node is behaving correctly by rejecting - # the transaction (as tested here). - assert_raises_rpc_error(-26, PREUPGRADE_SCHNORR_MULTISIG_ERROR, - node.sendrawtransaction, ToHex(schnorr1tx)) - - self.log.info( - "Sending rejected transactions via net (bannable)") - self.check_for_ban_on_rejected_tx( - schnorr0tx, SCHNORR_LEGACY_MULTISIG_ERROR) - self.check_for_ban_on_rejected_tx( - ecdsa1tx, POSTUPGRADE_ECDSA_NULLDUMMY_ERROR) - # If we are sent unsolicited post-upgrade transactions while before - # the upgrade block, the tx is to be rejected. - self.check_for_ban_on_rejected_tx( - schnorr1tx, PREUPGRADE_SCHNORR_MULTISIG_ERROR) - - self.log.info( - "Sending invalid transactions in blocks (and get banned!)") - self.check_for_ban_on_rejected_block( - self.build_block(tip, [schnorr0tx]), BADINPUTS_ERROR) - self.check_for_ban_on_rejected_block( - self.build_block(tip, [schnorr1tx]), BADINPUTS_ERROR) - - self.log.info("Sending valid transaction via net, then mining it") - node.p2p.send_txs_and_test([ecdsa0tx], node) - assert_equal(node.getrawmempool(), [ecdsa0tx.hash]) - tip = self.build_block(tip, [ecdsa0tx]) - node.p2p.send_blocks_and_test([tip], node) - assert_equal(node.getrawmempool(), []) - - # Activation tests - - self.log.info("Approach to just before upgrade activation") - # Move our clock to the uprade time so we will accept such future-timestamped blocks. - node.setmocktime(GRAVITON_START_TIME) - # Mine six blocks with timestamp starting at GRAVITON_START_TIME-1 - blocks = [] - for i in range(-1, 5): - tip = self.build_block(tip, nTime=GRAVITON_START_TIME + i) - blocks.append(tip) - node.p2p.send_blocks_and_test(blocks, node) - assert_equal(node.getblockchaininfo()[ - 'mediantime'], GRAVITON_START_TIME - 1) - - self.log.info( - "The next block will activate, but the activation block itself must follow old rules") - self.check_for_ban_on_rejected_block( - self.build_block(tip, [schnorr0tx]), BADINPUTS_ERROR) - - self.log.info( - "Send a lecacy ECDSA multisig into mempool, we will check after upgrade to make sure it didn't get cleaned out unnecessarily.") - node.p2p.send_txs_and_test([ecdsa0tx_2], node) - assert_equal(node.getrawmempool(), [ecdsa0tx_2.hash]) - - # save this tip for later - preupgrade_block = tip - - self.log.info( - "Mine the activation block itself, including a non-null-dummy ECDSA at the last possible moment") - tip = self.build_block(tip, [ecdsa1tx]) - node.p2p.send_blocks_and_test([tip], node) - - self.log.info("We have activated!") - assert_equal(node.getblockchaininfo()[ - 'mediantime'], GRAVITON_START_TIME) - assert_equal(node.getrawmempool(), [ecdsa0tx_2.hash]) - - # save this tip for later - upgrade_block = tip - - self.log.info( - "Trying to mine a non-null-dummy ECDSA, but we are just barely too late") - self.check_for_ban_on_rejected_block( - self.build_block(tip, [ecdsa1tx_2]), BADINPUTS_ERROR) - self.log.info( - "If we try to submit it by mempool or RPC, it is rejected and we are banned") - assert_raises_rpc_error(-26, POSTUPGRADE_ECDSA_NULLDUMMY_ERROR, - node.sendrawtransaction, ToHex(ecdsa1tx_2)) - self.check_for_ban_on_rejected_tx( - ecdsa1tx_2, POSTUPGRADE_ECDSA_NULLDUMMY_ERROR) - - self.log.info( - "Submitting a new Schnorr-multisig via net, and mining it in a block") - node.p2p.send_txs_and_test([schnorr1tx], node) - assert_equal(set(node.getrawmempool()), { - ecdsa0tx_2.hash, schnorr1tx.hash}) - tip = self.build_block(tip, [schnorr1tx]) - node.p2p.send_blocks_and_test([tip], node) - - # save this tip for later - postupgrade_block = tip - - self.log.info( - "That legacy ECDSA multisig is still in mempool, let's mine it") - assert_equal(node.getrawmempool(), [ecdsa0tx_2.hash]) - tip = self.build_block(tip, [ecdsa0tx_2]) - node.p2p.send_blocks_and_test([tip], node) - assert_equal(node.getrawmempool(), []) - - self.log.info( - "Trying Schnorr in legacy multisig remains invalid and banworthy as ever") - self.check_for_ban_on_rejected_tx( - schnorr0tx, SCHNORR_LEGACY_MULTISIG_ERROR) - self.check_for_ban_on_rejected_block( - self.build_block(tip, [schnorr0tx]), BADINPUTS_ERROR) - - # Deactivation tests - - self.log.info( - "Invalidating the post-upgrade blocks returns the transactions to mempool") - node.invalidateblock(postupgrade_block.hash) - assert_equal(set(node.getrawmempool()), { - ecdsa0tx_2.hash, schnorr1tx.hash}) - self.log.info( - "Invalidating the upgrade block evicts the transactions valid only after upgrade") - node.invalidateblock(upgrade_block.hash) - assert_equal(set(node.getrawmempool()), { - ecdsa0tx_2.hash}) - - self.log.info("Return to our tip") - node.reconsiderblock(upgrade_block.hash) - node.reconsiderblock(postupgrade_block.hash) - assert_equal(node.getbestblockhash(), tip.hash) - assert_equal(node.getrawmempool(), []) - - self.log.info( - "Create an empty-block reorg that forks from pre-upgrade") - tip = preupgrade_block - blocks = [] - for _ in range(10): - tip = self.build_block(tip) - blocks.append(tip) - node.p2p.send_blocks_and_test(blocks, node) - - self.log.info("Transactions from orphaned blocks are sent into mempool ready to be mined again, including upgrade-dependent ones even though the fork deactivated and reactivated the upgrade.") - assert_equal(set(node.getrawmempool()), { - ecdsa0tx_2.hash, schnorr1tx.hash}) - node.generate(1) - tip = self.getbestblock(node) - assert set(tx.rehash() for tx in tip.vtx).issuperset( - {ecdsa0tx_2.hash, schnorr1tx.hash}) - - -if __name__ == '__main__': - SchnorrTest().main() diff --git a/test/functional/abc-minimaldata-activation.py b/test/functional/abc-schnorrmultisig.py rename from test/functional/abc-minimaldata-activation.py rename to test/functional/abc-schnorrmultisig.py --- a/test/functional/abc-minimaldata-activation.py +++ b/test/functional/abc-schnorrmultisig.py @@ -3,11 +3,12 @@ # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """ -This tests the activation of MINIMALDATA rule to consensus (from standard). -- test rejection in mempool, with error changing before/after activation. -- test acceptance in blocks before activation, and rejection after. +This tests the CHECKMULTISIG mode that uses Schnorr transaction signatures and +repurposes the dummy element to indicate which signatures are being checked. +- acceptance both in mempool and blocks. - check non-banning for peers who send invalid txns that would have been valid on the other side of the upgrade. +- check banning of peers for some fully-invalid transactions. Derived from abc-schnorr.py """ @@ -18,6 +19,7 @@ create_transaction, make_conform_to_ctor, ) +from test_framework.key import CECKey from test_framework.messages import ( CBlock, COutPoint, @@ -30,40 +32,44 @@ from test_framework.mininode import ( P2PDataStore, ) +from test_framework import schnorr from test_framework.script import ( CScript, - OP_ADD, + OP_0, + OP_1, + OP_CHECKMULTISIG, OP_TRUE, + SIGHASH_ALL, + SIGHASH_FORKID, + SignatureHashForkId, ) from test_framework.test_framework import BitcoinTestFramework -from test_framework.txtools import pad_tx from test_framework.util import assert_equal, assert_raises_rpc_error -# the upgrade activation time, which we artificially set far into the future -GRAVITON_START_TIME = 2000000000 -# If we don't do this, autoreplay protection will activate before graviton and -# all our sigs will mysteriously fail. -REPLAY_PROTECTION_START_TIME = GRAVITON_START_TIME * 2 +# ECDSA checkmultisig with non-null dummy are invalid since the new mode +# refuses ECDSA. +ECDSA_NULLDUMMY_ERROR = 'mandatory-script-verify-flag-failed (Only Schnorr signatures allowed in this operation)' - -# Both before and after the upgrade, minimal push violations in mempool are -# rejected with a bannable error. -MINIMALPUSH_ERROR = 'mandatory-script-verify-flag-failed (Data push larger than necessary)' +# A mandatory (bannable) error occurs when people pass Schnorr signatures into +# legacy OP_CHECKMULTISIG. +SCHNORR_LEGACY_MULTISIG_ERROR = 'mandatory-script-verify-flag-failed (Signature cannot be 65 bytes in CHECKMULTISIG)' # Blocks with invalid scripts give this error: BADINPUTS_ERROR = 'blk-bad-inputs' -class SchnorrTest(BitcoinTestFramework): +# This 64-byte signature is used to test exclusion & banning according to +# the above error messages. +# Tests of real 64 byte ECDSA signatures can be found in script_tests. +sig64 = b'\0'*64 + + +class SchnorrMultisigTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 self.block_heights = {} - self.extra_args = [["-gravitonactivationtime={}".format( - GRAVITON_START_TIME), - "-replayprotectionactivationtime={}".format( - REPLAY_PROTECTION_START_TIME)]] def bootstrap_p2p(self, *, num_connections=1): """Add a P2P connection to the node. @@ -114,11 +120,6 @@ [tx], self.nodes[0], success=False, expect_disconnect=True, reject_reason=reject_reason) self.reconnect_p2p() - def check_for_no_ban_on_rejected_tx(self, tx, reject_reason): - """Check we are not disconnected when sending a txn that the node rejects.""" - self.nodes[0].p2p.send_txs_and_test( - [tx], self.nodes[0], success=False, reject_reason=reject_reason) - def check_for_ban_on_rejected_block(self, block, reject_reason=None): """Check we are disconnected when sending a block that the node rejects. @@ -150,10 +151,17 @@ self.log.info("Setting up spends to test and mining the fundings.") fundings = [] - def create_fund_and_spend_tx(): + # Generate a key pair + privkeybytes = b"Schnorr!" * 4 + private_key = CECKey() + private_key.set_secretbytes(privkeybytes) + # get uncompressed public key serialization + public_key = private_key.get_pubkey() + + def create_fund_and_spend_tx(dummy=OP_0, sigtype='ecdsa'): spendfrom = spendable_outputs.pop() - script = CScript([OP_ADD]) + script = CScript([OP_1, public_key, OP_1, OP_CHECKMULTISIG]) value = spendfrom.vout[0].nValue @@ -170,84 +178,70 @@ CTxIn(COutPoint(txfund.sha256, 0), b'')) # Sign the transaction - txspend.vin[0].scriptSig = CScript( - b'\x01\x01\x51') # PUSH1(0x01) OP_1 - pad_tx(txspend) + sighashtype = SIGHASH_ALL | SIGHASH_FORKID + hashbyte = bytes([sighashtype & 0xff]) + sighash = SignatureHashForkId( + script, txspend, 0, sighashtype, value) + if sigtype == 'schnorr': + txsig = schnorr.sign(privkeybytes, sighash) + hashbyte + elif sigtype == 'ecdsa': + txsig = private_key.sign(sighash) + hashbyte + txspend.vin[0].scriptSig = CScript([dummy, txsig]) txspend.rehash() return txspend - # make a few of these, which are nonstandard before upgrade and invalid after. - nonminimaltx = create_fund_and_spend_tx() - nonminimaltx_2 = create_fund_and_spend_tx() - nonminimaltx_3 = create_fund_and_spend_tx() + # This is valid. + ecdsa0tx = create_fund_and_spend_tx(OP_0, 'ecdsa') + + # This is invalid. + ecdsa1tx = create_fund_and_spend_tx(OP_1, 'ecdsa') + + # This is invalid. + schnorr0tx = create_fund_and_spend_tx(OP_0, 'schnorr') + + # This is valid. + schnorr1tx = create_fund_and_spend_tx(OP_1, 'schnorr') tip = self.build_block(tip, fundings) node.p2p.send_blocks_and_test([tip], node) - self.log.info("Start preupgrade tests") - - self.log.info("Sending rejected transactions via RPC") - assert_raises_rpc_error(-26, MINIMALPUSH_ERROR, - node.sendrawtransaction, ToHex(nonminimaltx)) - assert_raises_rpc_error(-26, MINIMALPUSH_ERROR, - node.sendrawtransaction, ToHex(nonminimaltx_2)) - assert_raises_rpc_error(-26, MINIMALPUSH_ERROR, - node.sendrawtransaction, ToHex(nonminimaltx_3)) + self.log.info("Send a legacy ECDSA multisig into mempool.") + node.p2p.send_txs_and_test([ecdsa0tx], node) + assert_equal(node.getrawmempool(), [ecdsa0tx.hash]) + self.log.info("Trying to mine a non-null-dummy ECDSA.") + self.check_for_ban_on_rejected_block( + self.build_block(tip, [ecdsa1tx]), BADINPUTS_ERROR) self.log.info( - "Sending rejected transactions via net (banning)") + "If we try to submit it by mempool or RPC, it is rejected and we are banned") + assert_raises_rpc_error(-26, ECDSA_NULLDUMMY_ERROR, + node.sendrawtransaction, ToHex(ecdsa1tx)) self.check_for_ban_on_rejected_tx( - nonminimaltx, MINIMALPUSH_ERROR) - self.check_for_ban_on_rejected_tx( - nonminimaltx_2, MINIMALPUSH_ERROR) - self.check_for_ban_on_rejected_tx( - nonminimaltx_3, MINIMALPUSH_ERROR) + ecdsa1tx, ECDSA_NULLDUMMY_ERROR) - assert_equal(node.getrawmempool(), []) - - self.log.info("Successfully mine nonstandard transaction") - tip = self.build_block(tip, [nonminimaltx]) + self.log.info( + "Submitting a Schnorr-multisig via net, and mining it in a block") + node.p2p.send_txs_and_test([schnorr1tx], node) + assert_equal(set(node.getrawmempool()), { + ecdsa0tx.hash, schnorr1tx.hash}) + tip = self.build_block(tip, [schnorr1tx]) node.p2p.send_blocks_and_test([tip], node) - # Activation tests - - self.log.info("Approach to just before upgrade activation") - # Move our clock to the uprade time so we will accept such future-timestamped blocks. - node.setmocktime(GRAVITON_START_TIME) - # Mine six blocks with timestamp starting at GRAVITON_START_TIME-1 - blocks = [] - for i in range(-1, 5): - tip = self.build_block(tip, nTime=GRAVITON_START_TIME + i) - blocks.append(tip) - node.p2p.send_blocks_and_test(blocks, node) - assert_equal(node.getblockchaininfo()[ - 'mediantime'], GRAVITON_START_TIME - 1) - self.log.info( - "Mine the activation block itself, including a minimaldata violation at the last possible moment") - tip = self.build_block(tip, [nonminimaltx_2]) + "That legacy ECDSA multisig is still in mempool, let's mine it") + assert_equal(node.getrawmempool(), [ecdsa0tx.hash]) + tip = self.build_block(tip, [ecdsa0tx]) node.p2p.send_blocks_and_test([tip], node) + assert_equal(node.getrawmempool(), []) - self.log.info("We have activated!") - assert_equal(node.getblockchaininfo()[ - 'mediantime'], GRAVITON_START_TIME) - - self.log.info( - "Trying to mine a minimaldata violation, but we are just barely too late") - self.check_for_ban_on_rejected_block( - self.build_block(tip, [nonminimaltx_3]), BADINPUTS_ERROR) self.log.info( - "If we try to submit it by mempool or RPC we still aren't banned") - assert_raises_rpc_error(-26, MINIMALPUSH_ERROR, - node.sendrawtransaction, ToHex(nonminimaltx_3)) + "Trying Schnorr in legacy multisig is invalid and banworthy.") self.check_for_ban_on_rejected_tx( - nonminimaltx_3, MINIMALPUSH_ERROR) - - self.log.info("Mine a normal block") - tip = self.build_block(tip) - node.p2p.send_blocks_and_test([tip], node) + schnorr0tx, SCHNORR_LEGACY_MULTISIG_ERROR) + self.check_for_ban_on_rejected_block( + self.build_block(tip, [schnorr0tx]), BADINPUTS_ERROR) if __name__ == '__main__': - SchnorrTest().main() + SchnorrMultisigTest().main() diff --git a/test/functional/timing.json b/test/functional/timing.json --- a/test/functional/timing.json +++ b/test/functional/timing.json @@ -36,7 +36,7 @@ "time": 2 }, { - "name": "abc-minimaldata-activation.py", + "name": "abc-minimaldata.py", "time": 2 }, { @@ -64,7 +64,7 @@ "time": 2 }, { - "name": "abc-schnorrmultisig-activation.py", + "name": "abc-schnorrmultisig.py", "time": 3 }, {