diff --git a/src/Makefile.test.include b/src/Makefile.test.include index ab449f3e7..d37696199 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -51,6 +51,7 @@ BITCOIN_TESTS =\ test/key_tests.cpp \ test/main_tests.cpp \ test/miner_tests.cpp \ + test/blockv2_tests.cpp \ test/mruset_tests.cpp \ test/multisig_tests.cpp \ test/netbase_tests.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 179db5a81..d924a6a2b 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -214,8 +214,50 @@ public: }; static CRegTestParams regTestParams; +// +// Regression test +// +class CUnitTestParams : public CMainParams, public CModifiableParams { +public: + CUnitTestParams() { + networkID = CBaseChainParams::UNITTEST; + strNetworkID = "unittest"; + nDefaultPort = 18445; + vFixedSeeds.clear(); + vSeeds.clear(); // Regtest mode doesn't have any DNS seeds. + + fRequireRPCPassword = false; + fMiningRequiresPeers = false; + fDefaultCheckMemPool = true; + fAllowMinDifficultyBlocks = false; + fMineBlocksOnDemand = true; + fSkipProofOfWorkCheck = false; + } + virtual bool SkipProofOfWorkCheck() const { return fSkipProofOfWorkCheck; } +protected: + bool fSkipProofOfWorkCheck; +public: + // Published setters to allow changing values in unit test cases + virtual void setSubsidyHalvingInterval(int anSubsidyHalvingInterval) { nSubsidyHalvingInterval=anSubsidyHalvingInterval; } + virtual void setEnforceBlockUpgradeMajority(int anEnforceBlockUpgradeMajority) { nEnforceBlockUpgradeMajority=anEnforceBlockUpgradeMajority; } + virtual void setRejectBlockOutdatedMajority(int anRejectBlockOutdatedMajority) { nRejectBlockOutdatedMajority=anRejectBlockOutdatedMajority; } + virtual void setToCheckBlockUpgradeMajority(int anToCheckBlockUpgradeMajority) { nToCheckBlockUpgradeMajority=anToCheckBlockUpgradeMajority; } + virtual void setDefaultCheckMemPool(bool aDefaultCheckMemPool) { fDefaultCheckMemPool=aDefaultCheckMemPool; } + virtual void setAllowMinDifficultyBlocks(bool aAllowMinDifficultyBlocks) { fAllowMinDifficultyBlocks=aAllowMinDifficultyBlocks; } + virtual void setSkipProofOfWorkCheck(bool aSkipProofOfWorkCheck) { fSkipProofOfWorkCheck = aSkipProofOfWorkCheck; } +}; +static CUnitTestParams unitTestParams; + + static CChainParams *pCurrentParams = 0; +CModifiableParams *ModifiableParams() +{ + assert(pCurrentParams); + assert(pCurrentParams==&unitTestParams); + return (CModifiableParams*)&unitTestParams; +} + const CChainParams &Params() { assert(pCurrentParams); return *pCurrentParams; @@ -229,6 +271,8 @@ CChainParams &Params(CBaseChainParams::Network network) { return testNetParams; case CBaseChainParams::REGTEST: return regTestParams; + case CBaseChainParams::UNITTEST: + return unitTestParams; default: assert(false && "Unimplemented network"); return mainParams; diff --git a/src/chainparams.h b/src/chainparams.h index e5dfc87c6..171a590a5 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -61,6 +61,8 @@ public: bool DefaultCheckMemPool() const { return fDefaultCheckMemPool; } /* Allow mining of a min-difficulty block */ bool AllowMinDifficultyBlocks() const { return fAllowMinDifficultyBlocks; } + /* Skip proof-of-work check: allow mining of any difficulty block */ + virtual bool SkipProofOfWorkCheck() const { return false; } /* Make standard checks */ bool RequireStandard() const { return fRequireStandard; } int64_t TargetTimespan() const { return nTargetTimespan; } @@ -105,6 +107,24 @@ protected: bool fMineBlocksOnDemand; }; +/** Modifiable parameters interface is used by test cases to adapt the parameters in order +*** to test specific features more easily. Test cases should always restore the previous +*** values after finalization. +**/ + +class CModifiableParams { +public: + // Published setters to allow changing values in unit test cases + virtual void setSubsidyHalvingInterval(int anSubsidyHalvingInterval) =0; + virtual void setEnforceBlockUpgradeMajority(int anEnforceBlockUpgradeMajority)=0; + virtual void setRejectBlockOutdatedMajority(int anRejectBlockOutdatedMajority)=0; + virtual void setToCheckBlockUpgradeMajority(int anToCheckBlockUpgradeMajority)=0; + virtual void setDefaultCheckMemPool(bool aDefaultCheckMemPool)=0; + virtual void setAllowMinDifficultyBlocks(bool aAllowMinDifficultyBlocks)=0; + virtual void setSkipProofOfWorkCheck(bool aSkipProofOfWorkCheck)=0; +}; + + /** * Return the currently selected parameters. This won't change after app startup * outside of the unit tests. @@ -114,6 +134,9 @@ const CChainParams &Params(); /** Return parameters for the given network. */ CChainParams &Params(CBaseChainParams::Network network); +/** Get modifyable network parameters (UNITTEST only) */ +CModifiableParams *ModifiableParams(); + /** Sets the params returned by Params() to those for the given network. */ void SelectParams(CBaseChainParams::Network network); diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 98bb5b855..e9d63197b 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -57,6 +57,20 @@ public: }; static CBaseRegTestParams regTestParams; +// +// Unit test +// +class CBaseUnitTestParams : public CBaseMainParams +{ +public: + CBaseUnitTestParams() + { + networkID = CBaseChainParams::UNITTEST; + strDataDir = "unittest"; + } +}; +static CBaseUnitTestParams unitTestParams; + static CBaseChainParams* pCurrentBaseParams = 0; const CBaseChainParams& BaseParams() @@ -77,6 +91,9 @@ void SelectBaseParams(CBaseChainParams::Network network) case CBaseChainParams::REGTEST: pCurrentBaseParams = ®TestParams; break; + case CBaseChainParams::UNITTEST: + pCurrentBaseParams = &unitTestParams; + break; default: assert(false && "Unimplemented network"); return; diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h index c054f03f1..cc154cf50 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -19,6 +19,7 @@ public: MAIN, TESTNET, REGTEST, + UNITTEST, MAX_NETWORK_TYPES }; diff --git a/src/checkpoints.cpp b/src/checkpoints.cpp index c41deea7c..9a6bc05e6 100644 --- a/src/checkpoints.cpp +++ b/src/checkpoints.cpp @@ -88,6 +88,8 @@ namespace Checkpoints { return dataTestnet; else if (Params().NetworkID() == CBaseChainParams::MAIN) return data; + else if (Params().NetworkID() == CBaseChainParams::UNITTEST) // UnitTest share the same checkpoints as MAIN + return data; else return dataRegtest; } diff --git a/src/main.cpp b/src/main.cpp index 9a4271eda..4bd8fd6e3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2304,6 +2304,7 @@ bool AcceptBlockHeader(CBlockHeader& block, CValidationState& state, CBlockIndex nHeight = pindexPrev->nHeight+1; // Check proof of work + if (!Params().SkipProofOfWorkCheck()) if (block.nBits != GetNextWorkRequired(pindexPrev, &block)) return state.DoS(100, error("AcceptBlock() : incorrect proof of work"), REJECT_INVALID, "bad-diffbits"); diff --git a/src/pow.cpp b/src/pow.cpp index 893f6c18b..d50222849 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -81,6 +81,10 @@ bool CheckProofOfWork(uint256 hash, unsigned int nBits) bool fNegative; bool fOverflow; uint256 bnTarget; + + if (Params().SkipProofOfWorkCheck()) + return true; + bnTarget.SetCompact(nBits, &fNegative, &fOverflow); // Check range diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index 58fffb6df..c298c805d 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -175,7 +175,7 @@ BOOST_AUTO_TEST_CASE(base58_keys_valid_parse) BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid pubkey as privkey:" + strTest); } } - SelectParams(CBaseChainParams::MAIN); + SelectParams(CBaseChainParams::UNITTEST); } // Goal: check that generated keys match test vectors @@ -243,7 +243,7 @@ BOOST_AUTO_TEST_CASE(base58_keys_valid_gen) CTxDestination nodest = CNoDestination(); BOOST_CHECK(!dummyAddr.Set(nodest)); - SelectParams(CBaseChainParams::MAIN); + SelectParams(CBaseChainParams::UNITTEST); } // Goal: check that base58 parsing code is robust against a variety of corrupted data diff --git a/src/test/blockv2_tests.cpp b/src/test/blockv2_tests.cpp new file mode 100644 index 000000000..0d59c8761 --- /dev/null +++ b/src/test/blockv2_tests.cpp @@ -0,0 +1,232 @@ +// Copyright (c) 2011-2014 The Bitcoin Core developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "main.h" +#include "miner.h" +#include "uint256.h" +#include "util.h" + +#include + +// Tests the majority rule which states that after 1000 v2 blocks no v1 block can go +BOOST_AUTO_TEST_SUITE(blockv2_tests) + +static CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG; + +static void SetEmptyBlock(CBlock * pblock) +{ + pblock->nVersion = 2; + pblock->nTime = chainActive.Tip()->GetMedianTimePast()+1; + pblock->nNonce = 0; +} + +static void SetBlockDefaultAttributesAndHeight(CBlock * pblock,bool addHeight,int difValue) +{ + SetEmptyBlock(pblock); + + // Add the coinbase + CMutableTransaction txCoinbase(pblock->vtx[0]); + + if (addHeight) + txCoinbase.vin[0].scriptSig = (CScript() << (chainActive.Height()+1+difValue) << 0); + else + txCoinbase.vin[0].scriptSig = (CScript() << difValue << 0); // At least size 2, this is a protocol spec + + txCoinbase.vout[0].scriptPubKey = CScript(); + pblock->vtx[0] = CTransaction(txCoinbase); + pblock->hashMerkleRoot = pblock->BuildMerkleTree(); +} + +void CheckSubsidyHalving(CBlockTemplate * &pblocktemplate, CBlock * &pblock) +{ + if ((chainActive.Height()+1) % Params().SubsidyHalvingInterval() == 0) + { + // The RegTest network has a low subsidy halving interval (150) so + // we must recompute the coinbase subsidy if we reach the boundary. + + // preserve parent hash + uint256 prevParent = pblock->hashPrevBlock; + delete pblocktemplate; + pblocktemplate = CreateNewBlock(scriptPubKey); + pblock = &pblocktemplate->block; // pointer for convenience + pblock->hashPrevBlock = prevParent; + } +} + +void Blockv2test() +{ + assert(Params().NetworkID() == CBaseChainParams::UNITTEST); + ModifiableParams()->setSkipProofOfWorkCheck(true); + + // We don't know the state of the block-chain here: it depends on which other tests are run before this test. + // See https://github.com/bitcoin/bitcoin/pull/4688 for a patch that allows the re-creation of the block-chain + // for each testcase that requires it. + + // If miner_tests.cpp is run before, the chain will be 100 blocks long, and all of them will be v1 + + + LogPrintf("Blockv2test testcase starts\n"); + + CBlockTemplate *pblocktemplate; + CScript script; + uint256 hash; + int PreviousHeight; + + + LOCK(cs_main); + + + // Simple block creation, nothing special yet. + pblocktemplate = CreateNewBlock(scriptPubKey); + CBlock *pblock = &pblocktemplate->block; // pointer for convenience + + LogPrintf("Blockv2test block v1 add begin\n"); + // First create a block v1, check that it is accepted. The block has an invalid height + SetBlockDefaultAttributesAndHeight(pblock,false,5000); + pblock->nVersion = 1; + CValidationState state1; + PreviousHeight = chainActive.Height(); + BOOST_CHECK(ProcessBlock(state1, NULL, pblock)); + BOOST_CHECK(state1.IsValid()); + BOOST_CHECK((PreviousHeight+1) == chainActive.Height()); // to differentiate from orphan blocks, which also get accepted in ProcessBlock() + pblock->hashPrevBlock = pblock->GetHash(); // update parent + + + // Now create exactly 1000 blocks v2 + + // First check that the supermajority threshold is exactly 1000 blocks + BOOST_CHECK(Params().ToCheckBlockUpgradeMajority()==1000); // + BOOST_CHECK(Params().EnforceBlockUpgradeMajority()==750); + BOOST_CHECK(Params().RejectBlockOutdatedMajority()==950); + + // Over the last 1000 blocks, 750 blocks must be v2 to switch to v2-only mode. + // Here we're testing only the last 750, not any subset. + + LogPrintf("Blockv2test BIP30 repetition begin\n"); + + // First, if we try to add a block v2 with the same coinbase tx, we should get + // "bad-txns-BIP30" because the coinbase tx has the same hash as the previous. + // Unluckily, even if ConnectBlock returns a "bad-txns-BIP30", ActivateBestChainStep clears + // the state, so we get true here and the "bad-txns-BIP30" reason is lost. + // We verify instead that the chain height has not been incremented. + + CValidationState state7; + PreviousHeight = chainActive.Height(); + CheckSubsidyHalving(pblocktemplate,pblock); + SetBlockDefaultAttributesAndHeight(pblock,false,5000); // + pblock->nVersion = 2; + BOOST_CHECK(ProcessBlock(state7, NULL, pblock)); // should we care about the return value? + BOOST_CHECK(state7.IsValid()); + BOOST_CHECK(PreviousHeight == chainActive.Height()); // we check the block has not been added. + + LogPrintf("Blockv2test 750 v2 blocks begin\n"); + for (int i=0;i<750;i++) + { + + LogPrintf("Blockv2test block %d begin\n",i); + + CheckSubsidyHalving(pblocktemplate,pblock); + + // We add a value to the height to make is NOT equal to the actual height. + SetBlockDefaultAttributesAndHeight(pblock,true,1000); // blocks version 2 without height are allowed! for only 750 blocks + pblock->nVersion = 2; + CValidationState state; + + PreviousHeight = chainActive.Height(); + BOOST_CHECK(ProcessBlock(state, NULL, pblock)); + BOOST_CHECK(state.IsValid()); + BOOST_CHECK((PreviousHeight+1) == chainActive.Height()); // to differentiate from orphan blocks, which also get accepted in ProcessBlock() + pblock->hashPrevBlock = pblock->GetHash(); // update parent + } + + LogPrintf("Blockv2test v2 without height rejected begin\n"); + + // Now we try to add a block v2, with an invalid height and it should be rejected. We use 2000 because is not in the range [1000..1750]. + CheckSubsidyHalving(pblocktemplate,pblock); + SetBlockDefaultAttributesAndHeight(pblock,true,2000); // + pblock->nVersion = 2; + CValidationState state0; + BOOST_CHECK(ProcessBlock(state0, NULL, pblock)==false); + BOOST_CHECK(!state0.IsValid()); + BOOST_CHECK(state0.GetRejectReason()=="bad-cb-height"); + // Do not update parent since block has failed + + LogPrintf("Blockv2test v2 with height accepted begin\n"); + + + // Now we add a block with height, must be ok. + for (int i=0;i<200;i++) + { + + LogPrintf("Blockv2test v2block %d begin\n",i); + CheckSubsidyHalving(pblocktemplate,pblock); + SetBlockDefaultAttributesAndHeight(pblock,true,0); + pblock->nVersion = 2; + CValidationState state; + PreviousHeight = chainActive.Height(); + BOOST_CHECK(ProcessBlock(state, NULL, pblock)); + BOOST_CHECK(state.IsValid()); + BOOST_CHECK((PreviousHeight+1) == chainActive.Height()); // to differentiate from orphan blocks, which also get accepted in ProcessBlock() + + pblock->hashPrevBlock = pblock->GetHash(); // update parent + } + + + LogPrintf("Blockv2test block v1 rejected\n"); + // Now we add 200 additional blocks, until we get 950 (the threshold were v1 blocks are not accepted anymore) + // Now we try to add a block v1, it should be rejected, even if it hash the height field + CheckSubsidyHalving(pblocktemplate,pblock); + SetBlockDefaultAttributesAndHeight(pblock,true,0); + pblock->nVersion = 1; + CValidationState state2; + BOOST_CHECK(ProcessBlock(state2, NULL, pblock)==false); + BOOST_CHECK(!state2.IsValid()); + BOOST_CHECK(state2.GetRejectReason()=="bad-version"); + // Do not update parent since block has failed + + + + // Some other missing tests, added here as bonus... + + // Block time too old check + CheckSubsidyHalving(pblocktemplate,pblock); + SetBlockDefaultAttributesAndHeight(pblock,true,0); + pblock->nVersion = 2; + pblock->nTime = chainActive.Tip()->GetMedianTimePast()-1; + CValidationState state4; + BOOST_CHECK(ProcessBlock(state4, NULL, pblock)==false); + BOOST_CHECK(!state4.IsValid()); + BOOST_CHECK(state4.GetRejectReason()=="time-too-old"); + // Do not update parent since block has failed + + // Adding a non-final coinbase, must modify coinbase + CheckSubsidyHalving(pblocktemplate,pblock); + SetEmptyBlock(pblock); + // Use a mutable coinbase to change nLockTime and nSequence + CMutableTransaction txCoinbase(pblock->vtx[0]); + txCoinbase.vin[0].scriptSig = (CScript() << chainActive.Height() << 0); + txCoinbase.nLockTime = LOCKTIME_THRESHOLD-1; // refers to height + txCoinbase.vin[0].nSequence = 1; // non-zero sequence + pblock->vtx[0] = CTransaction(txCoinbase); + pblock->nVersion = 2; + pblock->hashMerkleRoot = pblock->BuildMerkleTree(); + CValidationState state5; + BOOST_CHECK(ProcessBlock(state5, NULL, pblock)==false); + BOOST_CHECK(!state5.IsValid()); + BOOST_CHECK(state5.GetRejectReason()=="bad-txns-nonfinal"); + // Do not update parent since block has failed + + + delete pblocktemplate; + + ModifiableParams()->setSkipProofOfWorkCheck(false); + LogPrintf("Blockv2test testcase ends\n"); +} + +BOOST_AUTO_TEST_CASE(Blockv2testcase) +{ + Blockv2test(); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index 9e4669eba..bad5c13ac 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -253,6 +253,7 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) chainActive.Tip()->nHeight--; SetMockTime(0); + mempool.clear(); BOOST_FOREACH(CTransaction *tx, txFirst) delete tx; diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index 68fad8d03..6e5f0e3fa 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -31,7 +31,7 @@ struct TestingSetup { TestingSetup() { fPrintToDebugLog = false; // don't want to write to debug.log file - SelectParams(CBaseChainParams::MAIN); + SelectParams(CBaseChainParams::UNITTEST); noui_connect(); #ifdef ENABLE_WALLET bitdb.MakeMock();