diff --git a/src/blockassembler.cpp b/src/blockassembler.cpp index e500ffccc1ce..125c67906739 100644 --- a/src/blockassembler.cpp +++ b/src/blockassembler.cpp @@ -483,7 +483,18 @@ uint256 CalculateSaplingTreeRoot(CBlock* pblock, int nHeight, const CChainParams return UINT256_ZERO; } -void IncrementExtraNonce(std::shared_ptr& pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce) +bool SolveBlock(std::shared_ptr& pblock, int nHeight) +{ + unsigned int extraNonce = 0; + IncrementExtraNonce(pblock, nHeight, extraNonce); + while (pblock->nNonce < std::numeric_limits::max() && + !CheckProofOfWork(pblock->GetHash(), pblock->nBits)) { + ++pblock->nNonce; + } + return pblock->nNonce != std::numeric_limits::max(); +} + +void IncrementExtraNonce(std::shared_ptr& pblock, int nHeight, unsigned int& nExtraNonce) { // Update nExtraNonce static uint256 hashPrevBlock; @@ -492,7 +503,6 @@ void IncrementExtraNonce(std::shared_ptr& pblock, const CBlockIndex* pin hashPrevBlock = pblock->hashPrevBlock; } ++nExtraNonce; - unsigned int nHeight = pindexPrev->nHeight + 1; // Height first in coinbase required for block.version=2 CMutableTransaction txCoinbase(*pblock->vtx[0]); txCoinbase.vin[0].scriptSig = (CScript() << nHeight << CScriptNum(nExtraNonce)) + COINBASE_FLAGS; assert(txCoinbase.vin[0].scriptSig.size() <= 100); diff --git a/src/blockassembler.h b/src/blockassembler.h index 753b10048201..9014167326e1 100644 --- a/src/blockassembler.h +++ b/src/blockassembler.h @@ -92,8 +92,9 @@ class BlockAssembler bool isStillDependent(CTxMemPool::txiter iter); }; -/** Modify the extranonce in a block */ -void IncrementExtraNonce(std::shared_ptr& pblock, const CBlockIndex* pindexPrev, unsigned int& nExtraNonce); +/** Modify the nonce/extranonce in a block */ +bool SolveBlock(std::shared_ptr& pblock, int nHeight); +void IncrementExtraNonce(std::shared_ptr& pblock, int nHeight, unsigned int& nExtraNonce); int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev); int32_t ComputeBlockVersion(const Consensus::Params& consensusParams, int nHeight); diff --git a/src/chainparams.cpp b/src/chainparams.cpp index cb1ba61ad338..1c75860ff517 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -134,6 +134,7 @@ class CMainParams : public CChainParams assert(genesis.hashMerkleRoot == uint256S("0x1b2ef6e2f28be914103a277377ae7729dcd125dfeb8bf97bd5964ba72b6dc39b")); consensus.fPowAllowMinDifficultyBlocks = false; + consensus.fPowNoRetargeting = false; consensus.powLimit = uint256S("0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV1 = uint256S("0x000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV2 = uint256S("0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); @@ -275,6 +276,7 @@ class CTestNetParams : public CChainParams assert(genesis.hashMerkleRoot == uint256S("0x1b2ef6e2f28be914103a277377ae7729dcd125dfeb8bf97bd5964ba72b6dc39b")); consensus.fPowAllowMinDifficultyBlocks = true; + consensus.fPowNoRetargeting = false; consensus.powLimit = uint256S("0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV1 = uint256S("0x000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV2 = uint256S("0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); @@ -393,13 +395,14 @@ class CRegTestParams : public CChainParams { strNetworkID = "regtest"; - genesis = CreateGenesisBlock(1454124731, 2402015, 0x1e0ffff0, 1, 250 * COIN); + genesis = CreateGenesisBlock(1454124731, 1, 0x207fffff, 1, 250 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); - assert(consensus.hashGenesisBlock == uint256S("0x0000041e482b9b9691d98eefb48473405c0b8ec31b76df3797c74a78680ef818")); + assert(consensus.hashGenesisBlock == uint256S("0x7445589c4c8e52b105247b13373e5ee325856aa05d53f429e59ea46b7149ae3f")); assert(genesis.hashMerkleRoot == uint256S("0x1b2ef6e2f28be914103a277377ae7729dcd125dfeb8bf97bd5964ba72b6dc39b")); consensus.fPowAllowMinDifficultyBlocks = true; - consensus.powLimit = uint256S("0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + consensus.fPowNoRetargeting = true; + consensus.powLimit = uint256S("0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV1 = uint256S("0x000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.posLimitV2 = uint256S("0x00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); consensus.nBudgetCycleBlocks = 144; // approx 10 cycles per day diff --git a/src/consensus/params.h b/src/consensus/params.h index 3e2750089ecf..e43ad5484a95 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -88,6 +88,7 @@ struct NetworkUpgrade { struct Params { uint256 hashGenesisBlock; bool fPowAllowMinDifficultyBlocks; + bool fPowNoRetargeting; uint256 powLimit; uint256 posLimitV1; uint256 posLimitV2; diff --git a/src/consensus/tx_verify.cpp b/src/consensus/tx_verify.cpp index f8d9d0f4115c..58ecb2b1e8b6 100644 --- a/src/consensus/tx_verify.cpp +++ b/src/consensus/tx_verify.cpp @@ -74,7 +74,7 @@ bool CheckTransaction(const CTransaction& tx, CValidationState& state, bool fCol static_assert(MAX_TX_SIZE_AFTER_SAPLING > MAX_ZEROCOIN_TX_SIZE, "New max TX size must be bigger than old max TX size"); // sanity const unsigned int nMaxSize = tx.IsShieldedTx() ? MAX_TX_SIZE_AFTER_SAPLING : MAX_ZEROCOIN_TX_SIZE; if (tx.GetTotalSize() > nMaxSize) { - return state.DoS(10, false, REJECT_INVALID, "bad-txns-oversize"); + return state.DoS(10, error("tx oversize: %d > %d", tx.GetTotalSize(), nMaxSize), REJECT_INVALID, "bad-txns-oversize"); } // Dispatch to Sapling validator diff --git a/src/miner.cpp b/src/miner.cpp index 68291fc60ef2..a9f674bfb6b8 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -188,7 +188,7 @@ void BitcoinMiner(CWallet* pwallet, bool fProofOfStake) } // POW - miner main - IncrementExtraNonce(pblock, pindexPrev, nExtraNonce); + IncrementExtraNonce(pblock, pindexPrev->nHeight + 1, nExtraNonce); LogPrintf("Running PIVXMiner with %u transactions in block (%u bytes)\n", pblock->vtx.size(), ::GetSerializeSize(*pblock, SER_NETWORK, PROTOCOL_VERSION)); diff --git a/src/pow.cpp b/src/pow.cpp index 384cb742957c..7bd1422e6708 100644 --- a/src/pow.cpp +++ b/src/pow.cpp @@ -18,7 +18,9 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHeader* pblock) { - if (Params().IsRegTestNet()) + const Consensus::Params& consensus = Params().GetConsensus(); + + if (consensus.fPowNoRetargeting) return pindexLast->nBits; /* current difficulty formula, pivx - DarkGravity v3, written by Evan Duffield - evan@dashpay.io */ @@ -31,7 +33,6 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast, const CBlockHead int64_t CountBlocks = 0; arith_uint256 PastDifficultyAverage; arith_uint256 PastDifficultyAveragePrev; - const Consensus::Params& consensus = Params().GetConsensus(); const arith_uint256& powLimit = UintToArith256(consensus.powLimit); if (BlockLastSolved == NULL || BlockLastSolved->nHeight == 0 || BlockLastSolved->nHeight < PastBlocksMin) { @@ -124,8 +125,6 @@ bool CheckProofOfWork(uint256 hash, unsigned int nBits) bool fOverflow; arith_uint256 bnTarget; - if (Params().IsRegTestNet()) return true; - bnTarget.SetCompact(nBits, &fNegative, &fOverflow); // Check range diff --git a/src/qt/pivx/send.cpp b/src/qt/pivx/send.cpp index ea1e49d39dde..af8407289cd0 100644 --- a/src/qt/pivx/send.cpp +++ b/src/qt/pivx/send.cpp @@ -415,11 +415,6 @@ void SendWidget::ProcessSend(QList& recipients, bool hasShie // First check SPORK_20 (before unlock) bool isShieldedTx = hasShieldedOutput || !isTransparent; if (isShieldedTx) { - if (!walletModel->isSaplingEnforced()) { - inform(tr("Cannot perform shielded operations, v5 upgrade isn't being enforced yet!")); - return; - } - if (walletModel->isSaplingInMaintenance()) { inform(tr("Sapling Protocol temporarily in maintenance. Shielded transactions disabled (SPORK 20)")); return; @@ -702,8 +697,8 @@ void SendWidget::onCoinControlClicked() void SendWidget::onShieldCoinsClicked() { - if (!walletModel->isSaplingEnforced()) { - inform(tr("Cannot perform shielded operations, v5 upgrade isn't being enforced yet!")); + if (walletModel->isSaplingInMaintenance()) { + inform(tr("Sapling Protocol temporarily in maintenance. Shielded transactions disabled (SPORK 20)")); return; } @@ -792,13 +787,6 @@ void SendWidget::onCheckBoxChanged() void SendWidget::onPIVSelected(bool _isTransparent) { isTransparent = _isTransparent; - - if (!isTransparent && !walletModel->isSaplingEnforced()) { - ui->pushLeft->setChecked(true); - inform(tr("Cannot perform shielded operations, v5 upgrade isn't being enforced yet!")); - return; - } - resetChangeAddress(); resetCoinControl(); tryRefreshAmounts(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 50ca2957f36c..796265bf4a99 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -85,11 +85,6 @@ bool WalletModel::isSaplingInMaintenance() const return sporkManager.IsSporkActive(SPORK_20_SAPLING_MAINTENANCE); } -bool WalletModel::isSaplingEnforced() const -{ - return Params().GetConsensus().NetworkUpgradeActive(cachedNumBlocks, Consensus::UPGRADE_V5_0); -} - bool WalletModel::isV6Enforced() const { return Params().GetConsensus().NetworkUpgradeActive(cachedNumBlocks, Consensus::UPGRADE_V6_0); @@ -603,14 +598,6 @@ OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction* bool fromTransparent, const CCoinControl* coinControl) { - // Basic checks first - - // Check network status - int nextBlockHeight = cachedNumBlocks + 1; - if (!Params().GetConsensus().NetworkUpgradeActive(nextBlockHeight, Consensus::UPGRADE_V5_0)) { - return errorOut("Error, cannot send transaction. Sapling is not activated"); - } - // Load shieldedAddrRecipients. std::vector recipients; for (const auto& recipient : modelTransaction->getRecipients()) { @@ -630,6 +617,7 @@ OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction* if (!opResult) return opResult; // Create the operation + int nextBlockHeight = cachedNumBlocks + 1; SaplingOperation operation(Params().GetConsensus(), nextBlockHeight, wallet); auto operationResult = operation.setRecipients(recipients) ->setTransparentKeyChange(modelTransaction->getPossibleKeyChange()) diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index f7808e111edf..b5977b7ae2c0 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -155,7 +155,6 @@ class WalletModel : public QObject /** Whether cold staking is enabled or disabled in the network **/ bool isColdStakingNetworkelyEnabled() const; bool isSaplingInMaintenance() const; - bool isSaplingEnforced() const; bool isV6Enforced() const; CAmount getMinColdStakingAmount() const; /* current staking status from the miner thread **/ diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 1d1a7f715f7a..2fe468f85006 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -35,7 +35,6 @@ UniValue generateBlocks(const Consensus::Params& consensus, CScript* coinbaseScript) { UniValue blockHashes(UniValue::VARR); - unsigned int nExtraNonce = 0; while (nHeight < nHeightEnd && !ShutdownRequested()) { @@ -52,16 +51,8 @@ UniValue generateBlocks(const Consensus::Params& consensus, std::shared_ptr pblock = std::make_shared(pblocktemplate->block); if(!fPoS) { - { - LOCK(cs_main); - IncrementExtraNonce(pblock, chainActive.Tip(), nExtraNonce); - } - while (pblock->nNonce < std::numeric_limits::max() && - !CheckProofOfWork(pblock->GetHash(), pblock->nBits)) { - ++pblock->nNonce; - } if (ShutdownRequested()) break; - if (pblock->nNonce == std::numeric_limits::max()) continue; + if (!SolveBlock(pblock, nHeight + 1)) continue; } CValidationState state; diff --git a/src/sapling/transaction_builder.cpp b/src/sapling/transaction_builder.cpp index 90dba6c01e3c..80acb9547553 100644 --- a/src/sapling/transaction_builder.cpp +++ b/src/sapling/transaction_builder.cpp @@ -129,15 +129,6 @@ std::string TransactionBuilderResult::GetError() { } } -// Set default values of new CMutableTransaction based on consensus rules at given height. -CMutableTransaction CreateNewContextualCMutableTransaction(const Consensus::Params& consensusParams, int nHeight) -{ - CMutableTransaction mtx; - bool isSapling = consensusParams.NetworkUpgradeActive(nHeight, Consensus::UPGRADE_V5_0); - mtx.nVersion = isSapling ? CTransaction::TxVersion::SAPLING : CTransaction::TxVersion::LEGACY; - return mtx; -} - TransactionBuilder::TransactionBuilder( const Consensus::Params& _consensusParams, int _nHeight, @@ -151,7 +142,8 @@ TransactionBuilder::TransactionBuilder( void TransactionBuilder::Clear() { - mtx = CreateNewContextualCMutableTransaction(consensusParams, nHeight); + mtx = CMutableTransaction(); + mtx.nVersion = CTransaction::TxVersion::SAPLING; spends.clear(); outputs.clear(); tIns.clear(); diff --git a/src/test/librust/sapling_rpc_wallet_tests.cpp b/src/test/librust/sapling_rpc_wallet_tests.cpp index 3e1e1da3e4a3..3e17d198d231 100644 --- a/src/test/librust/sapling_rpc_wallet_tests.cpp +++ b/src/test/librust/sapling_rpc_wallet_tests.cpp @@ -85,8 +85,6 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_sapling_validateaddress) BOOST_AUTO_TEST_CASE(rpc_wallet_getbalance) { - SelectParams(CBaseChainParams::TESTNET); - { LOCK(pwalletMain->cs_wallet); pwalletMain->SetMinVersion(FEATURE_SAPLING); @@ -96,9 +94,9 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_getbalance) BOOST_CHECK_THROW(CallRPC("getshieldbalance too many args"), std::runtime_error); BOOST_CHECK_THROW(CallRPC("getshieldbalance invalidaddress"), std::runtime_error); BOOST_CHECK_THROW(CallRPC("getshieldbalance tmC6YZnCUhm19dEXxh3Jb7srdBJxDawaCab"), std::runtime_error); - BOOST_CHECK_NO_THROW(CallRPC("getshieldbalance ptestsapling1h0w73csah2aq0a32h42kr7tq4htlt5wfn4ejxfnm56f6ehjvek7k4e244g6v8v3pgylmz5ea8jh")); - BOOST_CHECK_THROW(CallRPC("getshieldbalance ptestsapling1h0w73csah2aq0a32h42kr7tq4htlt5wfn4ejxfnm56f6ehjvek7k4e244g6v8v3pgylmz5ea8jh -1"), std::runtime_error); - BOOST_CHECK_NO_THROW(CallRPC("getshieldbalance ptestsapling1nrn6exksuqtpld9gu6fwdz4hwg54h2x37gutdds89pfyg6mtjf63km45a8eare5qla45cj75vs8 0")); + BOOST_CHECK_NO_THROW(CallRPC("getshieldbalance ps1u87kylcmn28yclnx2uy0psnvuhs2xn608ukm6n2nshrpg2nzyu3n62ls8j77m9cgp40dx40evej")); + BOOST_CHECK_THROW(CallRPC("getshieldbalance ps1u87kylcmn28yclnx2uy0psnvuhs2xn608ukm6n2nshrpg2nzyu3n62ls8j77m9cgp40dx40evej -1"), std::runtime_error); + BOOST_CHECK_NO_THROW(CallRPC("getshieldbalance ps1u87kylcmn28yclnx2uy0psnvuhs2xn608ukm6n2nshrpg2nzyu3n62ls8j77m9cgp40dx40evej 0")); BOOST_CHECK_THROW(CallRPC("getshieldbalance tnRZ8bPq2pff3xBWhTJhNkVUkm2uhzksDeW5PvEa7aFKGT9Qi3YgTALZfjaY4jU3HLVKBtHdSXxoPoLA3naMPcHBcY88FcF 1"), std::runtime_error); BOOST_CHECK_NO_THROW(CallRPC("getshieldbalance *")); BOOST_CHECK_NO_THROW(CallRPC("getshieldbalance * 6")); @@ -106,15 +104,15 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_getbalance) BOOST_CHECK_THROW(CallRPC("listreceivedbyshieldaddress too many args"), std::runtime_error); // negative minconf not allowed - BOOST_CHECK_THROW(CallRPC("listreceivedbyshieldaddress yBYhwgzufrZ6F5VVuK9nEChENArq934mqC -1"), std::runtime_error); + BOOST_CHECK_THROW(CallRPC("listreceivedbyshieldaddress DMKU6mc52un1MThGCsnNwAtEvncaTdAuaZ -1"), std::runtime_error); // invalid zaddr, taddr not allowed - BOOST_CHECK_THROW(CallRPC("listreceivedbyshieldaddress yBYhwgzufrZ6F5VVuK9nEChENArq934mqC 0"), std::runtime_error); + BOOST_CHECK_THROW(CallRPC("listreceivedbyshieldaddress DMKU6mc52un1MThGCsnNwAtEvncaTdAuaZ 0"), std::runtime_error); // don't have the spending key - BOOST_CHECK_THROW(CallRPC("listreceivedbyshieldaddress ptestsapling1nrn6exksuqtpld9gu6fwdz4hwg54h2x37gutdds89pfyg6mtjf63km45a8eare5qla45cj75vs8 1"), std::runtime_error); + BOOST_CHECK_THROW(CallRPC("listreceivedbyshieldaddress ps1u87kylcmn28yclnx2uy0psnvuhs2xn608ukm6n2nshrpg2nzyu3n62ls8j77m9cgp40dx40evej 1"), std::runtime_error); } -BOOST_AUTO_TEST_CASE(rpc_wallet_sapling_importkey_paymentaddress) { - SelectParams(CBaseChainParams::MAIN); +BOOST_AUTO_TEST_CASE(rpc_wallet_sapling_importkey_paymentaddress) +{ { LOCK(pwalletMain->cs_wallet); pwalletMain->SetMinVersion(FEATURE_SAPLING); @@ -234,8 +232,8 @@ void CheckHaveAddr(const libzcash::PaymentAddress& addr) { BOOST_CHECK(pwalletMain->HaveSpendingKeyForPaymentAddress(*addr_of_type)); } -BOOST_AUTO_TEST_CASE(rpc_wallet_getnewshieldaddress) { - UniValue addr; +BOOST_AUTO_TEST_CASE(rpc_wallet_getnewshieldaddress) +{ { LOCK(pwalletMain->cs_wallet); pwalletMain->SetMinVersion(FEATURE_SAPLING); @@ -243,7 +241,7 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_getnewshieldaddress) { } // No parameter defaults to sapling address - addr = CallRPC("getnewshieldaddress"); + UniValue addr = CallRPC("getnewshieldaddress"); CheckHaveAddr(KeyIO::DecodePaymentAddress(addr.get_str())); // Too many arguments will throw with the help BOOST_CHECK_THROW(CallRPC("getnewshieldaddress many args"), std::runtime_error); @@ -251,8 +249,6 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_getnewshieldaddress) { BOOST_AUTO_TEST_CASE(rpc_shieldsendmany_parameters) { - SelectParams(CBaseChainParams::TESTNET); - { LOCK(pwalletMain->cs_wallet); pwalletMain->SetMinVersion(FEATURE_SAPLING); @@ -265,41 +261,40 @@ BOOST_AUTO_TEST_CASE(rpc_shieldsendmany_parameters) // bad from address BOOST_CHECK_THROW(CallRPC("shieldsendmany " - "INVALIDyBYhwgzufrZ6F5VVuK9nEChENArq934mqC []"), std::runtime_error); + "INVALIDDMKU6mc52un1MThGCsnNwAtEvncaTdAuaZ []"), std::runtime_error); // empty amounts BOOST_CHECK_THROW(CallRPC("shieldsendmany " - "yBYhwgzufrZ6F5VVuK9nEChENArq934mqC []"), std::runtime_error); + "DMKU6mc52un1MThGCsnNwAtEvncaTdAuaZ []"), std::runtime_error); // don't have the spending key for this address BOOST_CHECK_THROW(CallRPC("shieldsendmany " - "ptestsapling1wpurflqllgkcs48m46yu9ktlfe3ahndely20dpaanqq3lw9l5xw7yfehst68yclvlpz7x8cltxe" - "UkJ1oSfbhTJhm72WiZizvkZz5aH1 []"), std::runtime_error); + "ps1u87kylcmn28yclnx2uy0psnvuhs2xn608ukm6n2nshrpg2nzyu3n62ls8j77m9cgp40dx40evej []"), std::runtime_error); // duplicate address BOOST_CHECK_THROW(CallRPC("shieldsendmany " - "yBYhwgzufrZ6F5VVuK9nEChENArq934mqC " - "[{\"address\":\"yAJ4bGeDFcEtx24kbr413fBLpWQcdR5F2z\", \"amount\":50.0}," - " {\"address\":\"yAJ4bGeDFcEtx24kbr413fBLpWQcdR5F2z\", \"amount\":12.0} ]" + "DDTBEPEaub5sk31mUifiv5nHGXtHGnuAJc " + "[{\"address\":\"DMKU6mc52un1MThGCsnNwAtEvncaTdAuaZ\", \"amount\":50.0}," + " {\"address\":\"DMKU6mc52un1MThGCsnNwAtEvncaTdAuaZ\", \"amount\":12.0} ]" ), std::runtime_error); // invalid fee amount, cannot be negative BOOST_CHECK_THROW(CallRPC("shieldsendmany " - "yBYhwgzufrZ6F5VVuK9nEChENArq934mqC " - "[{\"address\":\"yAJ4bGeDFcEtx24kbr413fBLpWQcdR5F2z\", \"amount\":50.0}] " + "DDTBEPEaub5sk31mUifiv5nHGXtHGnuAJc " + "[{\"address\":\"DMKU6mc52un1MThGCsnNwAtEvncaTdAuaZ\", \"amount\":50.0}] " "1 -0.0001" ), std::runtime_error); // invalid fee amount, bigger than MAX_MONEY BOOST_CHECK_THROW(CallRPC("shieldsendmany " - "yBYhwgzufrZ6F5VVuK9nEChENArq934mqC " - "[{\"address\":\"yAJ4bGeDFcEtx24kbr413fBLpWQcdR5F2z\", \"amount\":50.0}] " + "DDTBEPEaub5sk31mUifiv5nHGXtHGnuAJc " + "[{\"address\":\"DMKU6mc52un1MThGCsnNwAtEvncaTdAuaZ\", \"amount\":50.0}] " "1 21000001" ), std::runtime_error); // fee amount is bigger than sum of outputs BOOST_CHECK_THROW(CallRPC("shieldsendmany " - "yBYhwgzufrZ6F5VVuK9nEChENArq934mqC " - "[{\"address\":\"yAJ4bGeDFcEtx24kbr413fBLpWQcdR5F2z\", \"amount\":50.0}] " + "DDTBEPEaub5sk31mUifiv5nHGXtHGnuAJc " + "[{\"address\":\"DMKU6mc52un1MThGCsnNwAtEvncaTdAuaZ\", \"amount\":50.0}] " "1 50.00000001" ), std::runtime_error); @@ -309,18 +304,18 @@ BOOST_AUTO_TEST_CASE(rpc_shieldsendmany_parameters) std::string badmemo(v.begin(), v.end()); auto pa = pwalletMain->GenerateNewSaplingZKey(); std::string zaddr1 = KeyIO::EncodePaymentAddress(pa); - BOOST_CHECK_THROW(CallRPC(std::string("shieldsendmany yBYhwgzufrZ6F5VVuK9nEChENArq934mqC ") + BOOST_CHECK_THROW(CallRPC(std::string("shieldsendmany DMKU6mc52un1MThGCsnNwAtEvncaTdAuaZ ") + "[{\"address\":\"" + zaddr1 + "\", \"amount\":123.456}]"), std::runtime_error); } -// TODO: test private methods -BOOST_AUTO_TEST_CASE(saplingOperationTests) { - RegtestActivateSapling(); - auto consensusParams = Params().GetConsensus(); - - LOCK2(cs_main, pwalletMain->cs_wallet); - pwalletMain->SetupSPKM(false); +BOOST_AUTO_TEST_CASE(saplingOperationTests) +{ + { + LOCK2(cs_main, pwalletMain->cs_wallet); + pwalletMain->SetupSPKM(false); + } + auto consensusParams = Params().GetConsensus(); UniValue retValue; // add keys manually @@ -388,17 +383,15 @@ BOOST_AUTO_TEST_CASE(saplingOperationTests) { const std::string& errStr = res.getError(); BOOST_CHECK(errStr.find("too big") != std::string::npos); } - RegtestDeactivateSapling(); } BOOST_AUTO_TEST_CASE(rpc_shieldsendmany_taddr_to_sapling) { - SelectParams(CBaseChainParams::REGTEST); - RegtestActivateSapling(); - - LOCK2(cs_main, pwalletMain->cs_wallet); - pwalletMain->SetupSPKM(false); + { + LOCK2(cs_main, pwalletMain->cs_wallet); + pwalletMain->SetupSPKM(false); + } UniValue retValue; @@ -477,9 +470,6 @@ BOOST_AUTO_TEST_CASE(rpc_shieldsendmany_taddr_to_sapling) // Tear down chainActive.SetTip(nullptr); mapBlockIndex.erase(blockHash); - - // Revert to default - RegtestDeactivateSapling(); } BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_sapzkeys) @@ -546,8 +536,6 @@ BOOST_AUTO_TEST_CASE(rpc_wallet_encrypted_wallet_sapzkeys) BOOST_AUTO_TEST_CASE(rpc_listshieldunspent_parameters) { - SelectParams(CBaseChainParams::TESTNET); - { LOCK(pwalletMain->cs_wallet); pwalletMain->SetupSPKM(false); @@ -568,19 +556,19 @@ BOOST_AUTO_TEST_CASE(rpc_listshieldunspent_parameters) BOOST_CHECK_THROW(CallRPC("listshieldunspent 1 9999999999"), std::runtime_error); // must be an array of addresses - BOOST_CHECK_THROW(CallRPC("listshieldunspent 1 999 false ptestsapling1wpurflqllgkcs48m46yu9ktlfe3ahndely20dpaanqq3lw9l5xw7yfehst68yclvlpz7x8cltxe"), std::runtime_error); + BOOST_CHECK_THROW(CallRPC("listshieldunspent 1 999 false ps1u87kylcmn28yclnx2uy0psnvuhs2xn608ukm6n2nshrpg2nzyu3n62ls8j77m9cgp40dx40evej"), std::runtime_error); // address must be string BOOST_CHECK_THROW(CallRPC("listshieldunspent 1 999 false [123456]"), std::runtime_error); // no spending key - BOOST_CHECK_THROW(CallRPC("listshieldunspent 1 999 false [\"ptestsapling1wpurflqllgkcs48m46yu9ktlfe3ahndely20dpaanqq3lw9l5xw7yfehst68yclvlpz7x8cltxe\"]"), std::runtime_error); + BOOST_CHECK_THROW(CallRPC("listshieldunspent 1 999 false [\"ps1u87kylcmn28yclnx2uy0psnvuhs2xn608ukm6n2nshrpg2nzyu3n62ls8j77m9cgp40dx40evej\"]"), std::runtime_error); // allow watch only - BOOST_CHECK_NO_THROW(CallRPC("listshieldunspent 1 999 true [\"ptestsapling1wpurflqllgkcs48m46yu9ktlfe3ahndely20dpaanqq3lw9l5xw7yfehst68yclvlpz7x8cltxe\"]")); + BOOST_CHECK_NO_THROW(CallRPC("listshieldunspent 1 999 true [\"ps1u87kylcmn28yclnx2uy0psnvuhs2xn608ukm6n2nshrpg2nzyu3n62ls8j77m9cgp40dx40evej\"]")); - // wrong network, mainnet instead of testnet - BOOST_CHECK_THROW(CallRPC("listshieldunspent 1 999 true [\"ps1qenk9kapr0crx7lmdl4yclx78spc36wh7d5hm9hglp85f43k9dupyf0c5836h42wq2ejv0ef2v3\"]"), std::runtime_error); + // wrong network, testnet/regtest instead of mainnet + BOOST_CHECK_THROW(CallRPC("listshieldunspent 1 999 true [\"ptestsapling1wpurflqllgkcs48m46yu9ktlfe3ahndely20dpaanqq3lw9l5xw7yfehst68yclvlpz7x8cltxe\"]"), std::runtime_error); // create shielded address so we have the spending key BOOST_CHECK_NO_THROW(retValue = CallRPC("getnewshieldaddress")); diff --git a/src/test/librust/sapling_test_fixture.cpp b/src/test/librust/sapling_test_fixture.cpp index dc388352d9d7..740c70341fee 100644 --- a/src/test/librust/sapling_test_fixture.cpp +++ b/src/test/librust/sapling_test_fixture.cpp @@ -5,7 +5,7 @@ #include "test/librust/sapling_test_fixture.h" #include "sapling/sapling_util.h" -SaplingTestingSetup::SaplingTestingSetup() : TestingSetup() +SaplingTestingSetup::SaplingTestingSetup(const std::string& chainName) : TestingSetup(chainName) { initZKSNARKS(); // init zk-snarks lib } diff --git a/src/test/librust/sapling_test_fixture.h b/src/test/librust/sapling_test_fixture.h index 7207a23705c4..59ae6927b46d 100644 --- a/src/test/librust/sapling_test_fixture.h +++ b/src/test/librust/sapling_test_fixture.h @@ -10,8 +10,9 @@ /** * Testing setup that configures a complete environment for Sapling testing. */ -struct SaplingTestingSetup : public TestingSetup { - SaplingTestingSetup(); +struct SaplingTestingSetup : public TestingSetup +{ + SaplingTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); ~SaplingTestingSetup(); }; diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index a0924ad7d143..adb10537882b 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -17,7 +17,8 @@ #include -BOOST_FIXTURE_TEST_SUITE(miner_tests, WalletTestingSetup) +// future: this should be MAINNET. +BOOST_FIXTURE_TEST_SUITE(miner_tests, WalletRegTestingSetup) // BOOST_CHECK_EXCEPTION predicates to check the specific validation error class HasReason { @@ -73,7 +74,6 @@ struct { BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) { // Note that by default, these tests run with size accounting enabled. - SelectParams(CBaseChainParams::REGTEST); // future: this should be MAINNET. const CChainParams& chainparams = Params(); CScript scriptPubKey = CScript() << OP_DUP << OP_HASH160 << ParseHex("8d5b4f83212214d6ef693e02e6d71969fddad976") << OP_EQUALVERIFY << OP_CHECKSIG; diff --git a/src/test/script_P2CS_tests.cpp b/src/test/script_P2CS_tests.cpp index 45889f4166ea..7385aac42feb 100644 --- a/src/test/script_P2CS_tests.cpp +++ b/src/test/script_P2CS_tests.cpp @@ -59,8 +59,8 @@ BOOST_AUTO_TEST_CASE(extract_cold_staking_destination_keys) static CScript GetNewP2CS(CKey& stakerKey, CKey& ownerKey, bool fLastOutFree) { - stakerKey = DecodeSecret("91yo52JPHDVUG3jXWLKGyzEdjn1a9nbnurLdmQEf2UzbgzkTc2c"); - ownerKey = DecodeSecret("92KgNFNfmVVJRQuzssETc7NhwufGuHsLvPQxW9Nwmxs7PB4ByWB"); + stakerKey = DecodeSecret("YNdsth3BsW53DYmCiR12SofWSAt2utXQUSGoin3PekVQCMbzfS7E"); + ownerKey = DecodeSecret("YUo8oW3y8cUQdQxQxCdnUJ4Ww5H7nHBEMwD2bNDpBbuLM59t4rvd"); return fLastOutFree ? GetScriptForStakeDelegationLOF(stakerKey.GetPubKey().GetID(), ownerKey.GetPubKey().GetID()) : GetScriptForStakeDelegation(stakerKey.GetPubKey().GetID(), ownerKey.GetPubKey().GetID()); } @@ -124,7 +124,6 @@ static bool CheckP2CSScript(const CScript& scriptSig, const CScript& scriptPubKe BOOST_AUTO_TEST_CASE(coldstake_lof_script) { - SelectParams(CBaseChainParams::REGTEST); CScript scriptP2CS; CKey stakerKey, ownerKey; @@ -150,7 +149,7 @@ BOOST_AUTO_TEST_CASE(coldstake_lof_script) SignColdStake(tx, 0, scriptP2CS, stakerKey, true); BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err)); - const CKey& dummyKey = DecodeSecret("91t7cwPGevo885Uccg87nVjzUxKhXta9JprHM3R21PQkBFMFg2i"); + const CKey& dummyKey = DecodeSecret("YNdsth3BsW53DYmCiR12SofWSAt2utXQUSGoin3PekVQCMbzfS7E"); const CKeyID& dummyKeyID = dummyKey.GetPubKey().GetID(); const CScript& dummyP2PKH = GetDummyP2PKH(dummyKeyID); @@ -201,7 +200,6 @@ BOOST_AUTO_TEST_CASE(coldstake_lof_script) BOOST_AUTO_TEST_CASE(coldstake_script) { - SelectParams(CBaseChainParams::REGTEST); CScript scriptP2CS; CKey stakerKey, ownerKey; @@ -227,7 +225,7 @@ BOOST_AUTO_TEST_CASE(coldstake_script) SignColdStake(tx, 0, scriptP2CS, stakerKey, true); BOOST_CHECK(CheckP2CSScript(tx.vin[0].scriptSig, scriptP2CS, tx, err)); - const CKey& dummyKey = DecodeSecret("91t7cwPGevo885Uccg87nVjzUxKhXta9JprHM3R21PQkBFMFg2i"); + const CKey& dummyKey = DecodeSecret("YNdsth3BsW53DYmCiR12SofWSAt2utXQUSGoin3PekVQCMbzfS7E"); const CKeyID& dummyKeyID = dummyKey.GetPubKey().GetID(); const CScript& dummyP2PKH = GetDummyP2PKH(dummyKeyID); diff --git a/src/test/test_pivx.cpp b/src/test/test_pivx.cpp index 732fce7474d6..00376f32753b 100644 --- a/src/test/test_pivx.cpp +++ b/src/test/test_pivx.cpp @@ -133,13 +133,11 @@ TestChainSetup::TestChainSetup(int blockCount) : TestingSetup(CBaseChainParams:: } } -// -// Create a new block with just given transactions, coinbase paying to -// scriptPubKey, and try to add it to the current chain. -// -CBlock TestChainSetup::CreateAndProcessBlock(const std::vector& txns, const CScript& scriptPubKey) +// Create a new block with coinbase paying to scriptPubKey, and try to add it to the current chain. +// Include given transactions, and, if fNoMempoolTx=true, remove transactions coming from the mempool. +CBlock TestChainSetup::CreateAndProcessBlock(const std::vector& txns, const CScript& scriptPubKey, bool fNoMempoolTx) { - CBlock block = CreateBlock(txns, scriptPubKey); + CBlock block = CreateBlock(txns, scriptPubKey, fNoMempoolTx); CValidationState state; ProcessNewBlock(state, nullptr, std::make_shared(block), nullptr); return block; @@ -151,25 +149,32 @@ CBlock TestChainSetup::CreateAndProcessBlock(const std::vector& txns, const CScript& scriptPubKey) +CBlock TestChainSetup::CreateBlock(const std::vector& txns, const CScript& scriptPubKey, bool fNoMempoolTx) { std::unique_ptr pblocktemplate = BlockAssembler( Params(), DEFAULT_PRINTPRIORITY).CreateNewBlock(scriptPubKey, nullptr, false); std::shared_ptr pblock = std::make_shared(pblocktemplate->block); + const int nHeight = WITH_LOCK(cs_main, return chainActive.Height()) + 1; + // Replace mempool-selected txns with just coinbase plus passed-in txns: - pblock->vtx.resize(1); - for (const CMutableTransaction& tx : txns) - pblock->vtx.push_back(MakeTransactionRef(tx)); + if (fNoMempoolTx) { + pblock->vtx.resize(1); - // IncrementExtraNonce creates a valid coinbase and merkleRoot - unsigned int extraNonce = 0; - { - LOCK(cs_main); - IncrementExtraNonce(pblock, chainActive.Tip(), extraNonce); + // Replace coinbase output amount (could have included fee in CreateNewBlock) + CMutableTransaction txCoinbase(*pblock->vtx[0]); + txCoinbase.vout[0].nValue = GetBlockValue(nHeight); + pblock->vtx[0] = MakeTransactionRef(txCoinbase); } + for (const CMutableTransaction& tx : txns) { + pblock->vtx.push_back(MakeTransactionRef(tx)); + } + + // Re-compute sapling root + pblock->hashFinalSaplingRoot = CalculateSaplingTreeRoot(pblock.get(), nHeight, Params()); - while (!CheckProofOfWork(pblock->GetHash(), pblock->nBits)) ++pblock->nNonce; + // Find valid PoW + assert(SolveBlock(pblock, nHeight)); return *pblock; } diff --git a/src/test/test_pivx.h b/src/test/test_pivx.h index 67ed4d039377..02badf2e95a2 100644 --- a/src/test/test_pivx.h +++ b/src/test/test_pivx.h @@ -45,7 +45,8 @@ struct BasicTestingSetup { * and wallet (if enabled) setup. */ class CConnman; -struct TestingSetup: public BasicTestingSetup { +struct TestingSetup: public BasicTestingSetup +{ CCoinsViewDB *pcoinsdbview; fs::path pathTemp; boost::thread_group threadGroup; @@ -56,6 +57,11 @@ struct TestingSetup: public BasicTestingSetup { ~TestingSetup(); }; +struct RegTestingSetup : public TestingSetup +{ + RegTestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {} +}; + class CBlock; struct CMutableTransaction; class CScript; @@ -66,11 +72,11 @@ struct TestChainSetup : public TestingSetup TestChainSetup(int blockCount); ~TestChainSetup(); - // Create a new block with just given transactions, coinbase paying to - // scriptPubKey, and try to add it to the current chain. - CBlock CreateAndProcessBlock(const std::vector& txns, const CScript& scriptPubKey); + // Create a new block with coinbase paying to scriptPubKey, and try to add it to the current chain. + // Include given transactions, and, if fNoMempoolTx=true, remove transactions coming from the mempool. + CBlock CreateAndProcessBlock(const std::vector& txns, const CScript& scriptPubKey, bool fNoMempoolTx = true); CBlock CreateAndProcessBlock(const std::vector& txns, const CKey& scriptKey); - CBlock CreateBlock(const std::vector& txns, const CScript& scriptPubKey); + CBlock CreateBlock(const std::vector& txns, const CScript& scriptPubKey, bool fNoMempoolTx = true); CBlock CreateBlock(const std::vector& txns, const CKey& scriptKey); std::vector coinbaseTxns; // For convenience, coinbase transactions @@ -78,13 +84,15 @@ struct TestChainSetup : public TestingSetup }; // Testing fixture that pre-creates a 100-block REGTEST-mode blockchain -struct TestChain100Setup : public TestChainSetup { +struct TestChain100Setup : public TestChainSetup +{ TestChain100Setup() : TestChainSetup(100) {} }; // Testing fixture that pre-creates a 400-block REGTEST-mode blockchain // all 400 blocks are PoW. PoS starts at height 500 -struct TestChain400Setup : public TestChainSetup { +struct TestChain400Setup : public TestChainSetup +{ TestChain400Setup() : TestChainSetup(400) {} }; diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index f27f42e50523..31c3f81c89fb 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -14,13 +14,11 @@ #include "validation.h" #include "validationinterface.h" + #define ASSERT_WITH_MSG(cond, msg) if (!cond) { BOOST_ERROR(msg); } -struct RegtestingSetup : public TestingSetup { - RegtestingSetup() : TestingSetup(CBaseChainParams::REGTEST) {} -}; -BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegtestingSetup) +BOOST_FIXTURE_TEST_SUITE(validation_block_tests, RegTestingSetup) struct TestSubscriber : public CValidationInterface { uint256 m_expected_tip; diff --git a/src/test/validation_tests.cpp b/src/test/validation_tests.cpp index 5ea84f423215..41b6dca855b8 100644 --- a/src/test/validation_tests.cpp +++ b/src/test/validation_tests.cpp @@ -4,17 +4,18 @@ #include "test/test_pivx.h" #include "blockassembler.h" -#include "consensus/merkle.h" #include "primitives/transaction.h" #include "sapling/sapling_validation.h" #include "test/librust/utiltest.h" #include -BOOST_FIXTURE_TEST_SUITE(validation_tests, TestingSetup) +BOOST_AUTO_TEST_SUITE(validation_tests) -void test_simple_sapling_invalidity(CMutableTransaction& tx) +BOOST_FIXTURE_TEST_CASE(test_simple_shielded_invalid, TestingSetup) { + CMutableTransaction tx; + tx.nVersion = CTransaction::TxVersion::SAPLING; CAmount nDummyValueOut; { CMutableTransaction newTx(tx); @@ -91,26 +92,10 @@ void test_simple_sapling_invalidity(CMutableTransaction& tx) } } -BOOST_AUTO_TEST_CASE(test_simple_shielded_invalid) -{ - // Switch to regtest parameters so we can activate Sapling - SelectParams(CBaseChainParams::REGTEST); - - CMutableTransaction mtx; - mtx.nVersion = CTransaction::TxVersion::SAPLING; - - UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V5_0, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); - test_simple_sapling_invalidity(mtx); - UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V5_0, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); - - // Switch back to mainnet parameters as originally selected in test fixture - SelectParams(CBaseChainParams::MAIN); -} - -void CheckBlockZcRejection(const std::shared_ptr& pblock, CMutableTransaction& mtx) +void CheckBlockZcRejection(std::shared_ptr& pblock, int nHeight, CMutableTransaction& mtx) { pblock->vtx.emplace_back(MakeTransactionRef(mtx)); - pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); + BOOST_CHECK(SolveBlock(pblock, nHeight)); CValidationState state; BOOST_CHECK(!ProcessNewBlock(state, nullptr, pblock, nullptr)); BOOST_CHECK(!state.IsValid()); @@ -127,9 +112,11 @@ void CheckMempoolZcRejection(CMutableTransaction& mtx) BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-tx-with-zc"); } -BOOST_AUTO_TEST_CASE(zerocoin_rejection_tests) +/* + * Running on regtest to have v5 upgrade enforced at block 1 and test in-block zc rejection + */ +BOOST_FIXTURE_TEST_CASE(zerocoin_rejection_tests, RegTestingSetup) { - SelectParams(CBaseChainParams::REGTEST); UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V5_0, Consensus::NetworkUpgrade::ALWAYS_ACTIVE); const CChainParams& chainparams = Params(); @@ -149,18 +136,21 @@ BOOST_AUTO_TEST_CASE(zerocoin_rejection_tests) mtx.vout[0].scriptPubKey = CScript() << OP_ZEROCOINMINT << CBigNum::randBignum(chainparams.GetConsensus().Zerocoin_Params(false)->coinCommitmentGroup.groupOrder).getvch(); mtx.vout[0].nValue = 1 * COIN; - CheckBlockZcRejection(std::make_shared(pblocktemplate->block), mtx); + std::shared_ptr pblock = std::make_shared(pblocktemplate->block); + CheckBlockZcRejection(pblock, 1, mtx); CheckMempoolZcRejection(mtx); // Zerocoin spends rejection test mtx.vout[0].scriptPubKey = scriptPubKey; mtx.vin[0].scriptSig = CScript() << OP_ZEROCOINSPEND; - CheckBlockZcRejection(std::make_shared(pblocktemplate->block), mtx); + pblock = std::make_shared(pblocktemplate->block); + CheckBlockZcRejection(pblock, 1, mtx); CheckMempoolZcRejection(mtx); // Zerocoin public spends rejection test mtx.vin[0].scriptSig = CScript() << OP_ZEROCOINPUBLICSPEND; - CheckBlockZcRejection(std::make_shared(pblocktemplate->block), mtx); + pblock = std::make_shared(pblocktemplate->block); + CheckBlockZcRejection(pblock, 1, mtx); CheckMempoolZcRejection(mtx); } diff --git a/src/validation.cpp b/src/validation.cpp index db625a30b883..553eca535262 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1644,10 +1644,9 @@ static bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockInd } } else if (!tx.IsCoinBase()) { - if (!view.HaveInputs(tx)) - return state.DoS(100, error("ConnectBlock() : inputs missing/spent"), - REJECT_INVALID, "bad-txns-inputs-missingorspent"); - + if (!view.HaveInputs(tx)) { + return state.DoS(100, false, REJECT_INVALID, "bad-txns-inputs-missingorspent"); + } // Sapling: are the sapling spends' requirements met in tx(valid anchors/nullifiers)? if (!view.HaveShieldedRequirements(tx)) return state.DoS(100, error("%s: spends requirements not met", __func__), diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e75e86d20939..d2af659d3def 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1168,8 +1168,8 @@ UniValue CreateColdStakeDelegation(const UniValue& params, CTransactionRef& txNe const Consensus::Params& consensus = Params().GetConsensus(); // Check network status int nextBlockHeight = chainActive.Height() + 1; - if (!consensus.NetworkUpgradeActive(nextBlockHeight, Consensus::UPGRADE_V5_0)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling not active yet"); + if (sporkManager.IsSporkActive(SPORK_20_SAPLING_MAINTENANCE)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "SHIELD in maintenance (SPORK 20)"); } std::vector recipients = {SendManyRecipient(ownerKey, *stakeKey, nValue, fV6Enforced)}; SaplingOperation operation(consensus, nextBlockHeight, pwalletMain); @@ -1626,11 +1626,10 @@ static SaplingOperation CreateShieldedTransaction(const JSONRPCRequest& request) } // Check network status - if (!Params().GetConsensus().NetworkUpgradeActive(nextBlockHeight, Consensus::UPGRADE_V5_0) || - sporkManager.IsSporkActive(SPORK_20_SAPLING_MAINTENANCE)) { - // If Sapling is not active, do not allow sending from or sending to Sapling addresses. + if (sporkManager.IsSporkActive(SPORK_20_SAPLING_MAINTENANCE)) { + // If Sapling is disabled, do not allow sending from or sending to Sapling addresses. if (fromSapling || containsSaplingOutput) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, Sapling not active yet"); + throw JSONRPCError(RPC_INVALID_PARAMETER, "SHIELD in maintenance (SPORK 20)"); } } diff --git a/src/wallet/test/wallet_sapling_transactions_validations_tests.cpp b/src/wallet/test/wallet_sapling_transactions_validations_tests.cpp index 779a4b8d6733..672adc74bae1 100644 --- a/src/wallet/test/wallet_sapling_transactions_validations_tests.cpp +++ b/src/wallet/test/wallet_sapling_transactions_validations_tests.cpp @@ -13,30 +13,52 @@ #include -BOOST_FIXTURE_TEST_SUITE(wallet_sapling_transactions_validations_tests, WalletTestingSetup) - -void setupWallet() +/* + * A text fixture with a preloaded 100-blocks regtest chain, with sapling activating at block 101, + * and a wallet containing the key used for the coinbase outputs. + */ +struct TestSaplingChainSetup: public TestChain100Setup { - LOCK(pwalletMain->cs_wallet); - pwalletMain->SetMinVersion(FEATURE_SAPLING); - gArgs.ForceSetArg("-keypool", "5"); - pwalletMain->SetupSPKM(true); -} + TestSaplingChainSetup() : TestChain100Setup() + { + initZKSNARKS(); // init zk-snarks lib + + bitdb.MakeMock(); + bool fFirstRun; + std::unique_ptr dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat")); + pwalletMain = new CWallet(std::move(dbw)); + pwalletMain->LoadWallet(fFirstRun); + RegisterValidationInterface(pwalletMain); + + int SAPLING_ACTIVATION_HEIGHT = 101; + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V5_0, SAPLING_ACTIVATION_HEIGHT); + + // setup wallet + { + LOCK(pwalletMain->cs_wallet); + pwalletMain->SetMinVersion(FEATURE_SAPLING); + gArgs.ForceSetArg("-keypool", "5"); + pwalletMain->SetupSPKM(true); + + // import coinbase key used to generate the 100-blocks chain + BOOST_CHECK(pwalletMain->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey())); + } + WalletRescanReserver reserver(pwalletMain); + BOOST_CHECK(reserver.reserve()); + pwalletMain->RescanFromTime(0, reserver, true /* update */); + } -void generateBlock(const CScript& scriptPubKey, int expectedBlockHeight) -{ - std::unique_ptr pblocktemplate; - BOOST_CHECK(pblocktemplate = BlockAssembler(Params(), false).CreateNewBlock(scriptPubKey, pwalletMain, false)); - std::shared_ptr pblock = std::make_shared(pblocktemplate->block); - pblock->nTime = WITH_LOCK(cs_main, return chainActive.Tip()->GetMedianTimePast() + 60); - pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); - CValidationState state; - BOOST_CHECK_MESSAGE(ProcessNewBlock(state, nullptr, pblock, nullptr), strprintf("Failed creating block at height %d", expectedBlockHeight)); - BOOST_CHECK(state.IsValid()); + ~TestSaplingChainSetup() + { + UnregisterValidationInterface(pwalletMain); + delete pwalletMain; + pwalletMain = nullptr; + bitdb.Flush(true); + bitdb.Reset(); + } +}; - // Let the wallet sync the blocks - SyncWithValidationInterfaceQueue(); -} +BOOST_FIXTURE_TEST_SUITE(wallet_sapling_transactions_validations_tests, TestSaplingChainSetup) SaplingOperation createOperationAndBuildTx(std::vector recipients, int nextBlockHeight, @@ -60,11 +82,6 @@ SaplingOperation createOperationAndBuildTx(std::vector recipi // Test double spend notes in the mempool and in blocks. BOOST_AUTO_TEST_CASE(test_in_block_and_mempool_notes_double_spend) { - SelectParams(CBaseChainParams::REGTEST); - int SAPLING_ACTIVATION_HEIGHT = 99; - UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V5_0, SAPLING_ACTIVATION_HEIGHT); - setupWallet(); - CTxDestination coinbaseDest; auto ret = pwalletMain->getNewAddress(coinbaseDest, "coinbase"); BOOST_ASSERT_MSG(ret.result, "cannot create address"); @@ -72,14 +89,17 @@ BOOST_AUTO_TEST_CASE(test_in_block_and_mempool_notes_double_spend) BOOST_ASSERT_MSG(IsMine(*pwalletMain, coinbaseDest), "destination not from wallet"); // create the chain + int tipHeight = WITH_LOCK(cs_main, return chainActive.Tip()->nHeight); + BOOST_CHECK_EQUAL(tipHeight, 100); const CScript& scriptPubKey = GetScriptForDestination(coinbaseDest); int nGenerateBlocks = 110; - for (int i = 0; i < nGenerateBlocks; ++i) { - generateBlock(scriptPubKey, (int) i); + for (int i = tipHeight; i < nGenerateBlocks; ++i) { + CreateAndProcessBlock({}, scriptPubKey); + SyncWithValidationInterfaceQueue(); } // Verify that we are at block 110 - int tipHeight = WITH_LOCK(cs_main, return chainActive.Tip()->nHeight); + tipHeight = WITH_LOCK(cs_main, return chainActive.Tip()->nHeight); BOOST_CHECK_EQUAL(tipHeight, nGenerateBlocks); // Verify that the wallet has all of the coins BOOST_CHECK_EQUAL(pwalletMain->GetAvailableBalance(), CAmount(250 * COIN * 10)); // 10 blocks available @@ -98,7 +118,10 @@ BOOST_AUTO_TEST_CASE(test_in_block_and_mempool_notes_double_spend) BOOST_ASSERT_MSG(operation.send(retHash), "error committing and broadcasting the transaction"); // Generate a five blocks to fully confirm the tx and test balance - for (int i = 0; i < 5; ++i) { generateBlock(scriptPubKey, WITH_LOCK(cs_main, return chainActive.Tip()->nHeight + 1)); } + for (int i = 1; i <= 5; ++i) { + CreateAndProcessBlock({}, scriptPubKey, false /*fNoMempoolTx*/); + } + SyncWithValidationInterfaceQueue(); BOOST_CHECK_EQUAL(pwalletMain->GetAvailableShieldedBalance(), CAmount(100 * COIN)); // 100 shield PIVs BOOST_CHECK_EQUAL(pwalletMain->GetUnconfirmedShieldedBalance(), CAmount(0)); // 0 shield PIVs @@ -133,21 +156,13 @@ BOOST_AUTO_TEST_CASE(test_in_block_and_mempool_notes_double_spend) // let's now test it inside a block // create the block with the two transactions and test validity - std::unique_ptr pblocktemplate; - BOOST_CHECK(pblocktemplate = BlockAssembler(Params(), false).CreateNewBlock(scriptPubKey, pwalletMain, false)); - std::shared_ptr pblock = std::make_shared(pblocktemplate->block); - pblock->nTime = WITH_LOCK(cs_main, return chainActive.Tip()->GetMedianTimePast() + 60); - pblock->vtx.emplace_back(MakeTransactionRef(operation3.getFinalTx())); // Force the invalid tx append - pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); - pblock->hashFinalSaplingRoot = CalculateSaplingTreeRoot(pblock.get(), 116, Params()); - CValidationState state; - ProcessNewBlock(state, nullptr, pblock, nullptr); - BOOST_CHECK(state.IsValid()); + const CBlock& block = CreateAndProcessBlock({operation3.getFinalTx()}, scriptPubKey, false /*fNoMempoolTx*/); + SyncWithValidationInterfaceQueue(); { LOCK(cs_main); // Same tip as before, no block connection - BOOST_CHECK(chainActive.Tip()->GetBlockHash() != pblock->GetHash()); + BOOST_CHECK(chainActive.Tip()->GetBlockHash() != block.GetHash()); BOOST_ASSERT_MSG(chainActive.Tip()->nHeight, 115); } } diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index 6e8a95734a46..cbfeaca0e3fe 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -19,7 +19,8 @@ void clean() bitdb.Reset(); } -WalletTestingSetup::WalletTestingSetup(): SaplingTestingSetup() +WalletTestingSetup::WalletTestingSetup(const std::string& chainName): + SaplingTestingSetup(chainName) { clean(); // todo: research why we have an initialized bitdb here. bitdb.MakeMock(); diff --git a/src/wallet/test/wallet_test_fixture.h b/src/wallet/test/wallet_test_fixture.h index 704a33df075a..616ce9926214 100644 --- a/src/wallet/test/wallet_test_fixture.h +++ b/src/wallet/test/wallet_test_fixture.h @@ -9,10 +9,16 @@ /** Testing setup and teardown for wallet. */ -struct WalletTestingSetup : public SaplingTestingSetup { - WalletTestingSetup(); +struct WalletTestingSetup : public SaplingTestingSetup +{ + WalletTestingSetup(const std::string& chainName = CBaseChainParams::MAIN); ~WalletTestingSetup(); }; +struct WalletRegTestingSetup : public WalletTestingSetup +{ + WalletRegTestingSetup() : WalletTestingSetup(CBaseChainParams::REGTEST) {} +}; + #endif diff --git a/test/functional/README.md b/test/functional/README.md index 1397e664dfb2..b32749f381eb 100644 --- a/test/functional/README.md +++ b/test/functional/README.md @@ -112,15 +112,9 @@ Generally useful functions. #### [test_framework/mininode.py](test_framework/mininode.py) Basic code to support P2P connectivity to a pivxd. -#### [test_framework/comptool.py](test_framework/comptool.py) -Framework for comparison-tool style, p2p tests. - #### [test_framework/script.py](test_framework/script.py) Utilities for manipulating transaction scripts (originally from python-bitcoinlib) -#### [test_framework/blockstore.py](test_framework/blockstore.py) -Implements disk-backed block and tx storage. - #### [test_framework/key.py](test_framework/key.py) Wrapper around OpenSSL EC_Key (originally from python-bitcoinlib) @@ -129,49 +123,3 @@ Helpers for script.py #### [test_framework/blocktools.py](test_framework/blocktools.py) Helper functions for creating blocks and transactions. - -### Comptool - -* Testing framework for writing tests that compare the block/tx acceptance -behavior of a pivxd against 1 or more other pivxd instances, or against -known outcomes, or both. - -* Set the ```num_nodes``` variable (defined in ```ComparisonTestFramework```) to start up -1 or more nodes. If using 1 node, then ```--testbinary``` can be used as a command line -option to change the pivxd binary used by the test. If using 2 or more nodes, -then ```--refbinary``` can be optionally used to change the pivxd that will be used -on nodes 2 and up. - -* Implement a (generator) function called ```get_tests()``` which yields ```TestInstance```s. -Each ```TestInstance``` consists of: - - a list of ```[object, outcome, hash]``` entries - * ```object``` is a ```CBlock```, ```CTransaction```, or - ```CBlockHeader```. ```CBlock```'s and ```CTransaction```'s are tested for - acceptance. ```CBlockHeader```s can be used so that the test runner can deliver - complete headers-chains when requested from the pivxd, to allow writing - tests where blocks can be delivered out of order but still processed by - headers-first pivxd's. - * ```outcome``` is ```True```, ```False```, or ```None```. If ```True``` - or ```False```, the tip is compared with the expected tip -- either the - block passed in, or the hash specified as the optional 3rd entry. If - ```None``` is specified, then the test will compare all the pivxd's - being tested to see if they all agree on what the best tip is. - * ```hash``` is the block hash of the tip to compare against. Optional to - specify; if left out then the hash of the block passed in will be used as - the expected tip. This allows for specifying an expected tip while testing - the handling of either invalid blocks or blocks delivered out of order, - which complete a longer chain. - - ```sync_every_block```: ```True/False```. If ```False```, then all blocks - are inv'ed together, and the test runner waits until the node receives the - last one, and tests only the last block for tip acceptance using the - outcome and specified tip. If ```True```, then each block is tested in - sequence and synced (this is slower when processing many blocks). - - ```sync_every_transaction```: ```True/False```. Analogous to - ```sync_every_block```, except if the outcome on the last tx is "None", - then the contents of the entire mempool are compared across all pivxd - connections. If ```True``` or ```False```, then only the last tx's - acceptance is tested against the given outcome. - -* For examples of tests written in this framework, see - ```invalidblockrequest.py``` and ```p2p-fullblocktest.py```. - diff --git a/test/functional/feature_block.py b/test/functional/feature_block.py index 91adcfc268fe..9a1a5fc477e6 100755 --- a/test/functional/feature_block.py +++ b/test/functional/feature_block.py @@ -1,37 +1,61 @@ #!/usr/bin/env python3 -# Copyright (c) 2015-2017 The Bitcoin Core developers +# Copyright (c) 2015-2021 The Bitcoin Core developers +# Copyright (c) 2020-2021 The PIVX developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Test block processing. - -This reimplements tests from the bitcoinj/FullBlockTestGenerator used -by the pull-tester. - -We use the testing framework in which we expect a particular answer from -each test. -""" - -from test_framework.test_framework import ComparisonTestFramework -from test_framework.util import * -from test_framework.comptool import TestManager, TestInstance, RejectResult -from test_framework.blocktools import * +"""Test block processing.""" +import copy +import struct import time + +from test_framework.blocktools import create_block, create_coinbase, create_transaction, get_legacy_sigopcount_block from test_framework.key import CECKey -from test_framework.script import * -from test_framework.mininode import network_thread_start -import struct +from test_framework.messages import ( + CBlock, + COIN, + COutPoint, + CTransaction, + CTxIn, + CTxOut, + MAX_BLOCK_BASE_SIZE, + uint256_from_compact, + uint256_from_str, +) +from test_framework.mininode import P2PDataStore, network_thread_start, network_thread_join +from test_framework.script import ( + CScript, + MAX_SCRIPT_ELEMENT_SIZE, + OP_2DUP, + OP_CHECKMULTISIG, + OP_CHECKMULTISIGVERIFY, + OP_CHECKSIG, + OP_CHECKSIGVERIFY, + OP_ELSE, + OP_ENDIF, + OP_EQUAL, + OP_FALSE, + OP_HASH160, + OP_IF, + OP_INVALIDOPCODE, + OP_RETURN, + OP_TRUE, + SIGHASH_ALL, + SignatureHash, + hash160, +) +from test_framework.test_framework import PivxTestFramework +from test_framework.util import assert_equal, bytes_to_hex_str + +MAX_BLOCK_SIGOPS = MAX_BLOCK_BASE_SIZE // 50 class PreviousSpendableOutput(): - def __init__(self, tx = CTransaction(), n = -1): + def __init__(self, tx=CTransaction(), n=-1): self.tx = tx self.n = n # the output we're spending # Use this class for tests that require behavior other than normal "mininode" behavior. # For now, it is used to serialize a bloated varint (b64). class CBrokenBlock(CBlock): - def __init__(self, header=None): - super(CBrokenBlock, self).__init__(header) - def initialize(self, base_block): self.vtx = copy.deepcopy(base_block.vtx) self.hashMerkleRoot = self.calc_merkle_root() @@ -48,381 +72,271 @@ def serialize(self, with_witness=False): return r def normal_serialize(self): - r = b"" - r += super(CBrokenBlock, self).serialize() - return r + return super().serialize() -class FullBlockTest(ComparisonTestFramework): - # Can either run this test as 1 node with expected answers, or two and compare them. - # Change the "outcome" variable from each TestInstance object to only do the comparison. +class FullBlockTest(PivxTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True + self.extra_args = [["-acceptnonstdtxn=1"]] # This is a consensus block test, we don't care about tx policy + + def run_test(self): + node = self.nodes[0] # convenience reference to the node + # reconnect_p2p() expects the network thread to be running + self.log.info("Starting network thread...") + network_thread_start() + self.reconnect_p2p() + self.block_heights = {} self.coinbase_key = CECKey() self.coinbase_key.set_secretbytes(b"horsebattery") self.coinbase_pubkey = self.coinbase_key.get_pubkey() self.tip = None self.blocks = {} - - def add_options(self, parser): - super().add_options(parser) - parser.add_option("--runbarelyexpensive", dest="runbarelyexpensive", default=True) - - def run_test(self): - self.test = TestManager(self, self.options.tmpdir) - self.test.add_all_connections(self.nodes) - network_thread_start() - self.test.run() - - def add_transactions_to_block(self, block, tx_list): - [ tx.rehash() for tx in tx_list ] - block.vtx.extend(tx_list) - - # this is a little handier to use than the version in blocktools.py - def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE, OP_DROP] * 15 + [OP_TRUE])): - tx = create_transaction(spend_tx, n, b"", value, script) - return tx - - # sign a transaction, using the key we know about - # this signs input 0 in tx, which is assumed to be spending output n in spend_tx - def sign_tx(self, tx, spend_tx, n): - scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey) - if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend - tx.vin[0].scriptSig = CScript() - return - (sighash, err) = SignatureHash(spend_tx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL) - tx.vin[0].scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))]) - - def create_and_sign_transaction(self, spend_tx, n, value, script=CScript([OP_TRUE])): - tx = self.create_tx(spend_tx, n, value, script) - self.sign_tx(tx, spend_tx, n) - tx.rehash() - return tx - - def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), solve=True): - 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, self.coinbase_pubkey) - coinbase.vout[0].nValue += additional_coinbase_value - coinbase.rehash() - if spend == None: - block = create_block(base_block_hash, coinbase, block_time) - else: - coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 # all but one satoshi to fees - coinbase.rehash() - block = create_block(base_block_hash, coinbase, block_time) - tx = create_transaction(spend.tx, spend.n, b"", 1, script) # spend 1 satoshi - self.sign_tx(tx, spend.tx, spend.n) - self.add_transactions_to_block(block, [tx]) - block.hashMerkleRoot = block.calc_merkle_root() - if solve: - 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): - block = self.blocks[block_number] - self.add_transactions_to_block(block, 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 - create_tx = self.create_tx - create_and_sign_tx = self.create_and_sign_transaction - - # these must be updated if consensus changes - MAX_BLOCK_SIGOPS = 20000 - + self.spendable_outputs = [] # Create a new block - 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) + self.log.info("Creating and sending a block...") + b0 = self.next_block(0) + self.save_spendable_output() + self.send_blocks([b0]) + + # Allow the block to mature + self.log.info("Maturing the block...") + blocks = [] for i in range(99): - block(5000 + i) - test.blocks_and_transactions.append([self.tip, True]) - save_spendable_output() - yield test + blocks.append(self.next_block(4000 + i)) + self.save_spendable_output() + self.log.info("Sending all new blocks") + self.send_blocks(blocks) # collect spendable outputs now to avoid cluttering the code later on + self.log.info("Collect spendable outputs...") out = [] for i in range(33): - out.append(get_spendable_output()) + out.append(self.get_spendable_output()) # Start by building a couple of blocks on top (which output is spent is # in parentheses): # genesis -> b1 (0) -> b2 (1) - block(1, spend=out[0]) - save_spendable_output() - yield accepted() + self.log.info("Build b1 and b2") + b1 = self.next_block(1, spend=out[0]) + self.save_spendable_output() + + b2 = self.next_block(2, spend=out[1]) + self.save_spendable_output() - block(2, spend=out[1]) - yield accepted() - save_spendable_output() + self.send_blocks([b1, b2]) - # so fork like this: + # Fork like this: # # genesis -> b1 (0) -> b2 (1) # \-> b3 (1) # # Nothing should happen at this point. We saw b2 first so it takes priority. - tip(1) - b3 = block(3, spend=out[1]) + self.log.info("Don't reorg to a chain of the same length") + self.move_tip(1) + b3 = self.next_block(3, spend=out[1]) txout_b3 = PreviousSpendableOutput(b3.vtx[1], 0) - yield rejected() - + self.send_blocks([b3], False) # Now we add another block to make the alternative chain longer. # # genesis -> b1 (0) -> b2 (1) # \-> b3 (1) -> b4 (2) - block(4, spend=out[2]) - yield accepted() - + self.log.info("Reorg to a longer chain") + b4 = self.next_block(4, spend=out[2]) + self.send_blocks([b4]) # ... and back to the first chain. # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b3 (1) -> b4 (2) - tip(2) - block(5, spend=out[2]) - save_spendable_output() - yield rejected() + self.move_tip(2) + b5 = self.next_block(5, spend=out[2]) + self.save_spendable_output() + self.send_blocks([b5], False) - block(6, spend=out[3]) - yield accepted() + self.log.info("Reorg back to the original chain") + b6 = self.next_block(6, spend=out[3]) + self.send_blocks([b6], True) # Try to create a fork that double-spends # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b7 (2) -> b8 (4) # \-> b3 (1) -> b4 (2) - tip(5) - block(7, spend=out[2]) - yield rejected() + self.log.info("Reject a chain with a double spend, even if it is longer") + self.move_tip(5) + b7 = self.next_block(7, spend=out[2]) + self.send_blocks([b7], False) - block(8, spend=out[4]) - yield rejected() + b8 = self.next_block(8, spend=out[4]) + self.send_blocks([b8], False, reconnect=True) # Try to create a block that has too much fee # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b9 (4) # \-> b3 (1) -> b4 (2) - tip(6) - block(9, spend=out[4], additional_coinbase_value=1) - yield rejected(RejectResult(16, b'bad-cb-amount')) + self.log.info("Reject a block where the miner creates too much coinbase reward") + self.move_tip(6) + b9 = self.next_block(9, spend=out[4], additional_coinbase_value=1) + # !TODO: fix expect_disconnect + self.send_blocks([b9], False, "bad-blk-amount", reconnect=False) # Create a fork that ends in a block with too much fee (the one that causes the reorg) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b10 (3) -> b11 (4) # \-> b3 (1) -> b4 (2) - tip(5) - block(10, spend=out[3]) - yield rejected() - - block(11, spend=out[4], additional_coinbase_value=1) - yield rejected(RejectResult(16, b'bad-cb-amount')) + self.log.info("Reject a chain where the miner creates too much coinbase reward, even if the chain is longer") + self.move_tip(5) + b10 = self.next_block(10, spend=out[3]) + self.send_blocks([b10], False) + b11 = self.next_block(11, spend=out[4], additional_coinbase_value=1) + # !TODO: fix expect_disconnect + self.send_blocks([b11], False, "bad-blk-amount", reconnect=False) # Try again, but with a valid fork first # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b14 (5) - # (b12 added last) # \-> b3 (1) -> b4 (2) - tip(5) - b12 = block(12, spend=out[3]) - save_spendable_output() - b13 = block(13, spend=out[4]) - # Deliver the block header for b12, and the block b13. - # b13 should be accepted but the tip won't advance until b12 is delivered. - yield TestInstance([[CBlockHeader(b12), None], [b13, False]]) - - save_spendable_output() - # b14 is invalid, but the node won't know that until it tries to connect - # Tip still can't advance because b12 is missing - block(14, spend=out[5], additional_coinbase_value=1) - yield rejected() - - yield TestInstance([[b12, True, b13.sha256]]) # New tip should be b13. + self.log.info("Reject a chain where the miner creates too much coinbase reward, even if the chain is longer (on a forked chain)") + self.move_tip(5) + b12 = self.next_block(12, spend=out[3]) + self.save_spendable_output() + b13 = self.next_block(13, spend=out[4]) + self.save_spendable_output() + b14 = self.next_block(14, spend=out[5], additional_coinbase_value=1) + # !TODO: fix expect_disconnect + self.send_blocks([b12, b13, b14], False, "bad-blk-amount", reconnect=False) + + # New tip should be b13. + assert_equal(node.getbestblockhash(), b13.hash) # Add a block with MAX_BLOCK_SIGOPS and one with one more sigop # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b16 (6) # \-> b3 (1) -> b4 (2) - - # Test that a block with a lot of checksigs is okay + self.log.info("Accept a block with lots of checksigs") lots_of_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS - 1)) - tip(13) - block(15, spend=out[5], script=lots_of_checksigs) - yield accepted() - save_spendable_output() - + self.move_tip(13) + b15 = self.next_block(15, spend=out[5], script=lots_of_checksigs) + self.save_spendable_output() + self.send_blocks([b15], True) - # Test that a block with too many checksigs is rejected + self.log.info("Reject a block with too many checksigs") too_many_checksigs = CScript([OP_CHECKSIG] * (MAX_BLOCK_SIGOPS)) - block(16, spend=out[6], script=too_many_checksigs) - yield rejected(RejectResult(16, b'bad-blk-sigops')) - + b16 = self.next_block(16, spend=out[6], script=too_many_checksigs) + self.send_blocks([b16], False, "bad-blk-sigops", reconnect=True) # Attempt to spend a transaction created on a different fork # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b17 (b3.vtx[1]) # \-> b3 (1) -> b4 (2) - tip(15) - block(17, spend=txout_b3) - yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent')) + self.log.info("Reject a block with a spend from a re-org'ed out tx") + self.move_tip(15) + b17 = self.next_block(17, spend=txout_b3) + # !TODO: fix expect_disconnect + self.send_blocks([b17], False, "bad-txns-inputs-missingorspent", reconnect=False) # Attempt to spend a transaction created on a different fork (on a fork this time) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) # \-> b18 (b3.vtx[1]) -> b19 (6) # \-> b3 (1) -> b4 (2) - tip(13) - block(18, spend=txout_b3) - yield rejected() + self.log.info("Reject a block with a spend from a re-org'ed out tx (on a forked chain)") + self.move_tip(13) + b18 = self.next_block(18, spend=txout_b3) + self.send_blocks([b18], False) - block(19, spend=out[6]) - yield rejected() + b19 = self.next_block(19, spend=out[6]) + self.send_blocks([b19], False, "bad-txns-inputs-missingorspent", reconnect=True) # Attempt to spend a coinbase at depth too low # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b20 (7) # \-> b3 (1) -> b4 (2) - tip(15) - block(20, spend=out[7]) - yield rejected(RejectResult(16, b'bad-txns-premature-spend-of-coinbase')) + self.log.info("Reject a block spending an immature coinbase.") + self.move_tip(15) + b20 = self.next_block(20, spend=out[7]) + self.send_blocks([b20], False, "bad-txns-premature-spend-of-coinbase") # Attempt to spend a coinbase at depth too low (on a fork this time) # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) # \-> b21 (6) -> b22 (5) # \-> b3 (1) -> b4 (2) - tip(13) - block(21, spend=out[6]) - yield rejected() + self.log.info("Reject a block spending an immature coinbase (on a forked chain)") + self.move_tip(13) + b21 = self.next_block(21, spend=out[6]) + self.send_blocks([b21], False) - block(22, spend=out[5]) - yield rejected() + b22 = self.next_block(22, spend=out[5]) + self.send_blocks([b22], False, "bad-txns-premature-spend-of-coinbase") # Create a block on either side of MAX_BLOCK_BASE_SIZE and make sure its accepted/rejected # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) # \-> b24 (6) -> b25 (7) # \-> b3 (1) -> b4 (2) - tip(15) - b23 = block(23, spend=out[6]) - tx = CTransaction() - script_length = MAX_BLOCK_BASE_SIZE - len(b23.serialize()) - 69 - script_output = CScript([b'\x00' * script_length]) - tx.vout.append(CTxOut(0, script_output)) - tx.vin.append(CTxIn(COutPoint(b23.vtx[1].sha256, 0))) - b23 = update_block(23, [tx]) - # Make sure the math above worked out to produce a max-sized block - assert_equal(len(b23.serialize()), MAX_BLOCK_BASE_SIZE) - yield accepted() - save_spendable_output() - - # Make the next block one byte bigger and check that it fails - tip(15) - b24 = block(24, spend=out[6]) - script_length = MAX_BLOCK_BASE_SIZE - len(b24.serialize()) - 69 - script_output = CScript([b'\x00' * (script_length+1)]) - tx.vout = [CTxOut(0, script_output)] - b24 = update_block(24, [tx]) - assert_equal(len(b24.serialize()), MAX_BLOCK_BASE_SIZE+1) - yield rejected(RejectResult(16, b'bad-blk-length')) - - block(25, spend=out[7]) - yield rejected() + self.log.info("Accept a block of size MAX_BLOCK_BASE_SIZE") + self.move_tip(15) + height = self.block_heights[self.tip.sha256] + 1 + b23 = self.next_block(23) + b23 = self.create_sized_block(b23, out[6], MAX_BLOCK_BASE_SIZE) + self.block_heights[b23.sha256] = height + self.blocks[23] = b23 + self.send_blocks([b23], True) + self.save_spendable_output() + + self.log.info("Reject a block of size MAX_BLOCK_BASE_SIZE + 2") + self.move_tip(15) + b24 = self.next_block(24) + b24 = self.create_sized_block(b24, out[6], MAX_BLOCK_BASE_SIZE + 2) + self.block_heights[b24.sha256] = height + self.blocks[24] = b24 + self.send_blocks([b24], False, "bad-blk-length", reconnect=True) + + b25 = self.next_block(25, spend=out[7]) + self.send_blocks([b25], False) # Create blocks with a coinbase input script size out of range # genesis -> b1 (0) -> b2 (1) -> b5 (2) -> b6 (3) # \-> b12 (3) -> b13 (4) -> b15 (5) -> b23 (6) -> b30 (7) # \-> ... (6) -> ... (7) # \-> b3 (1) -> b4 (2) - tip(15) - b26 = block(26, spend=out[6]) + self.log.info("Reject a block with coinbase input script size out of range") + self.move_tip(15) + b26 = self.next_block(26, spend=out[6]) b26.vtx[0].vin[0].scriptSig = b'\x00' b26.vtx[0].rehash() # update_block causes the merkle root to get updated, even with no new # transactions, and updates the required state. - b26 = update_block(26, []) - yield rejected(RejectResult(16, b'bad-cb-length')) + b26 = self.update_block(26, []) + self.send_blocks([b26], False, "bad-cb-length", reconnect=True) # Extend the b26 chain to make sure pivxd isn't accepting b26 - block(27, spend=out[7]) - yield rejected(False) + b27 = self.next_block(27, spend=out[7]) + self.send_blocks([b27], False) # Now try a too-large-coinbase script - tip(15) - b28 = block(28, spend=out[6]) - b28.vtx[0].vin[0].scriptSig = b'\x00' * 101 + self.move_tip(15) + b28 = self.next_block(28, spend=out[6]) + b28.vtx[0].vin[0].scriptSig += b'\x00' * 151 b28.vtx[0].rehash() - b28 = update_block(28, []) - yield rejected(RejectResult(16, b'bad-cb-length')) + b28 = self.update_block(28, []) + self.send_blocks([b28], False, "bad-cb-length", reconnect=True) # Extend the b28 chain to make sure pivxd isn't accepting b28 - block(29, spend=out[7]) - yield rejected(False) - - # b30 has a max-sized coinbase scriptSig. - tip(23) - b30 = block(30) - b30.vtx[0].vin[0].scriptSig = b'\x00' * 100 - b30.vtx[0].rehash() - b30 = update_block(30, []) - yield accepted() - save_spendable_output() + b29 = self.next_block(29, spend=out[7]) + self.send_blocks([b29], False) + # b30 + self.move_tip(23) + b30 = self.next_block(30) + b30 = self.update_block(30, []) + self.send_blocks([b30], True) + self.save_spendable_output() # b31 - b35 - check sigops of OP_CHECKMULTISIG / OP_CHECKMULTISIGVERIFY / OP_CHECKSIGVERIFY # @@ -433,42 +347,45 @@ def update_block(block_number, new_transactions): # # MULTISIG: each op code counts as 20 sigops. To create the edge case, pack another 19 sigops at the end. - lots_of_multisigs = CScript([OP_CHECKMULTISIG] * ((MAX_BLOCK_SIGOPS-1) // 20) + [OP_CHECKSIG] * 19) - b31 = block(31, spend=out[8], script=lots_of_multisigs) + self.log.info("Accept a block with the max number of OP_CHECKMULTISIG sigops") + lots_of_multisigs = CScript([OP_CHECKMULTISIG] * ((MAX_BLOCK_SIGOPS - 1) // 20) + [OP_CHECKSIG] * 19) + b31 = self.next_block(31, spend=out[8], script=lots_of_multisigs) assert_equal(get_legacy_sigopcount_block(b31), MAX_BLOCK_SIGOPS) - yield accepted() - save_spendable_output() + self.send_blocks([b31], True) + self.save_spendable_output() # this goes over the limit because the coinbase has one sigop + self.log.info("Reject a block with too many OP_CHECKMULTISIG sigops") too_many_multisigs = CScript([OP_CHECKMULTISIG] * (MAX_BLOCK_SIGOPS // 20)) - b32 = block(32, spend=out[9], script=too_many_multisigs) + b32 = self.next_block(32, spend=out[9], script=too_many_multisigs) assert_equal(get_legacy_sigopcount_block(b32), MAX_BLOCK_SIGOPS + 1) - yield rejected(RejectResult(16, b'bad-blk-sigops')) - + self.send_blocks([b32], False, "bad-blk-sigops", reconnect=True) # CHECKMULTISIGVERIFY - tip(31) - lots_of_multisigs = CScript([OP_CHECKMULTISIGVERIFY] * ((MAX_BLOCK_SIGOPS-1) // 20) + [OP_CHECKSIG] * 19) - block(33, spend=out[9], script=lots_of_multisigs) - yield accepted() - save_spendable_output() - + self.log.info("Accept a block with the max number of OP_CHECKMULTISIGVERIFY sigops") + self.move_tip(31) + lots_of_multisigs = CScript([OP_CHECKMULTISIGVERIFY] * ((MAX_BLOCK_SIGOPS - 1) // 20) + [OP_CHECKSIG] * 19) + b33 = self.next_block(33, spend=out[9], script=lots_of_multisigs) + self.send_blocks([b33], True) + self.save_spendable_output() + + self.log.info("Reject a block with too many OP_CHECKMULTISIGVERIFY sigops") too_many_multisigs = CScript([OP_CHECKMULTISIGVERIFY] * (MAX_BLOCK_SIGOPS // 20)) - block(34, spend=out[10], script=too_many_multisigs) - yield rejected(RejectResult(16, b'bad-blk-sigops')) - + b34 = self.next_block(34, spend=out[10], script=too_many_multisigs) + self.send_blocks([b34], False, "bad-blk-sigops", reconnect=True) # CHECKSIGVERIFY - tip(33) + self.log.info("Accept a block with the max number of OP_CHECKSIGVERIFY sigops") + self.move_tip(33) lots_of_checksigs = CScript([OP_CHECKSIGVERIFY] * (MAX_BLOCK_SIGOPS - 1)) - b35 = block(35, spend=out[10], script=lots_of_checksigs) - yield accepted() - save_spendable_output() + b35 = self.next_block(35, spend=out[10], script=lots_of_checksigs) + self.send_blocks([b35], True) + self.save_spendable_output() + self.log.info("Reject a block with too many OP_CHECKSIGVERIFY sigops") too_many_checksigs = CScript([OP_CHECKSIGVERIFY] * (MAX_BLOCK_SIGOPS)) - block(36, spend=out[11], script=too_many_checksigs) - yield rejected(RejectResult(16, b'bad-blk-sigops')) - + b36 = self.next_block(36, spend=out[11], script=too_many_checksigs) + self.send_blocks([b36], False, "bad-blk-sigops", reconnect=True) # Check spending of a transaction in a block which failed to connect # @@ -479,17 +396,20 @@ def update_block(block_number, new_transactions): # # save 37's spendable output, but then double-spend out11 to invalidate the block - tip(35) - b37 = block(37, spend=out[11]) + self.log.info("Reject a block spending transaction from a block which failed to connect") + self.move_tip(35) + b37 = self.next_block(37, spend=out[11]) txout_b37 = PreviousSpendableOutput(b37.vtx[1], 0) - tx = create_and_sign_tx(out[11].tx, out[11].n, 0) - b37 = update_block(37, [tx]) - yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent')) + tx = self.create_and_sign_transaction(out[11].tx, out[11].n, 0) + b37 = self.update_block(37, [tx]) + # !TODO: fix expect_disconnect + self.send_blocks([b37], False, "bad-txns-inputs-missingorspent", reconnect=False) # attempt to spend b37's first non-coinbase tx, at which point b37 was still considered valid - tip(35) - block(38, spend=txout_b37) - yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent')) + self.move_tip(35) + b38 = self.next_block(38, spend=txout_b37) + # !TODO: fix expect_disconnect + self.send_blocks([b38], False, "bad-txns-inputs-missingorspent", reconnect=False) # Check P2SH SigOp counting # @@ -502,45 +422,45 @@ def update_block(block_number, new_transactions): # redeem_script = COINBASE_PUBKEY, (OP_2DUP+OP_CHECKSIGVERIFY) * 5, OP_CHECKSIG # p2sh_script = OP_HASH160, ripemd160(sha256(script)), OP_EQUAL # - tip(35) - b39 = block(39) + self.log.info("Check P2SH SIGOPS are correctly counted") + self.move_tip(35) + b39 = self.next_block(39) b39_outputs = 0 b39_sigops_per_output = 6 # Build the redeem script, hash it, use hash to create the p2sh script - redeem_script = CScript([self.coinbase_pubkey] + [OP_2DUP, OP_CHECKSIGVERIFY]*5 + [OP_CHECKSIG]) + redeem_script = CScript([self.coinbase_pubkey] + [OP_2DUP, OP_CHECKSIGVERIFY] * 5 + [OP_CHECKSIG]) redeem_script_hash = hash160(redeem_script) p2sh_script = CScript([OP_HASH160, redeem_script_hash, OP_EQUAL]) # Create a transaction that spends one satoshi to the p2sh_script, the rest to OP_TRUE # This must be signed because it is spending a coinbase spend = out[11] - tx = create_tx(spend.tx, spend.n, 1, p2sh_script) + tx = self.create_tx(spend.tx, spend.n, 1, p2sh_script) tx.vout.append(CTxOut(spend.tx.vout[spend.n].nValue - 1, CScript([OP_TRUE]))) self.sign_tx(tx, spend.tx, spend.n) tx.rehash() - b39 = update_block(39, [tx]) + b39 = self.update_block(39, [tx]) b39_outputs += 1 # Until block is full, add tx's with 1 satoshi to p2sh_script, the rest to OP_TRUE tx_new = None tx_last = tx - total_size=len(b39.serialize()) + total_size = len(b39.serialize()) while(total_size < MAX_BLOCK_BASE_SIZE): - tx_new = create_tx(tx_last, 1, 1, p2sh_script) + tx_new = self.create_tx(tx_last, 1, 1, p2sh_script) tx_new.vout.append(CTxOut(tx_last.vout[1].nValue - 1, CScript([OP_TRUE]))) tx_new.rehash() total_size += len(tx_new.serialize()) if total_size >= MAX_BLOCK_BASE_SIZE: break - b39.vtx.append(tx_new) # add tx to block + b39.vtx.append(tx_new) # add tx to block tx_last = tx_new b39_outputs += 1 - b39 = update_block(39, []) - yield accepted() - save_spendable_output() - + b39 = self.update_block(39, []) + self.send_blocks([b39], True) + self.save_spendable_output() # Test sigops in P2SH redeem scripts # @@ -549,15 +469,16 @@ def update_block(block_number, new_transactions): # # b41 does the same, less one, so it has the maximum sigops permitted. # - tip(39) - b40 = block(40, spend=out[12]) + self.log.info("Reject a block with too many P2SH sigops") + self.move_tip(39) + b40 = self.next_block(40, spend=out[12]) sigops = get_legacy_sigopcount_block(b40) numTxes = (MAX_BLOCK_SIGOPS - sigops) // b39_sigops_per_output assert_equal(numTxes <= b39_outputs, True) lastOutpoint = COutPoint(b40.vtx[1].sha256, 0) new_txs = [] - for i in range(1, numTxes+1): + for i in range(1, numTxes + 1): tx = CTransaction() tx.vout.append(CTxOut(1, CScript([OP_TRUE]))) tx.vin.append(CTxIn(lastOutpoint, b'')) @@ -579,35 +500,35 @@ def update_block(block_number, new_transactions): tx.vout.append(CTxOut(1, CScript([OP_CHECKSIG] * b40_sigops_to_fill))) tx.rehash() new_txs.append(tx) - update_block(40, new_txs) - yield rejected(RejectResult(16, b'bad-blk-sigops')) + self.update_block(40, new_txs) + # !TODO: fix expect_disconnect + self.send_blocks([b40], False, "bad-blk-sigops", reconnect=False) # same as b40, but one less sigop - tip(39) - block(41, spend=None) - update_block(41, b40.vtx[1:-1]) + self.log.info("Accept a block with the max number of P2SH sigops") + self.move_tip(39) + b41 = self.next_block(41, spend=None) + self.update_block(41, b40.vtx[1:-1]) b41_sigops_to_fill = b40_sigops_to_fill - 1 tx = CTransaction() tx.vin.append(CTxIn(lastOutpoint, b'')) tx.vout.append(CTxOut(1, CScript([OP_CHECKSIG] * b41_sigops_to_fill))) tx.rehash() - update_block(41, [tx]) - yield accepted() + self.update_block(41, [tx]) + self.send_blocks([b41], True) # Fork off of b39 to create a constant base again # # b23 (6) -> b30 (7) -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) # \-> b41 (12) # - tip(39) - block(42, spend=out[12]) - yield rejected() - save_spendable_output() - - block(43, spend=out[13]) - yield accepted() - save_spendable_output() + self.move_tip(39) + b42 = self.next_block(42, spend=out[12]) + self.save_spendable_output() + b43 = self.next_block(43, spend=out[13]) + self.save_spendable_output() + self.send_blocks([b42, b43], True) # Test a number of really invalid scenarios # @@ -616,6 +537,7 @@ def update_block(block_number, new_transactions): # The next few blocks are going to be created "by hand" since they'll do funky things, such as having # the first transaction be non-coinbase, etc. The purpose of b44 is to make sure this works. + self.log.info("Build block 44 manually") height = self.block_heights[self.tip.sha256] + 1 coinbase = create_coinbase(height, self.coinbase_pubkey) b44 = CBlock() @@ -628,10 +550,10 @@ def update_block(block_number, new_transactions): self.tip = b44 self.block_heights[b44.sha256] = height self.blocks[44] = b44 - yield accepted() + self.send_blocks([b44], True) - # A block with a non-coinbase as the first tx - non_coinbase = create_tx(out[15].tx, out[15].n, 1) + self.log.info("Reject a block with a non-coinbase as the first tx") + non_coinbase = self.create_tx(out[15].tx, out[15].n, 1) b45 = CBlock() b45.nTime = self.tip.nTime + 1 b45.hashPrevBlock = self.tip.sha256 @@ -640,97 +562,85 @@ def update_block(block_number, new_transactions): b45.hashMerkleRoot = b45.calc_merkle_root() b45.calc_sha256() b45.solve() - self.block_heights[b45.sha256] = self.block_heights[self.tip.sha256]+1 + self.block_heights[b45.sha256] = self.block_heights[self.tip.sha256] + 1 self.tip = b45 self.blocks[45] = b45 - yield rejected(RejectResult(16, b'bad-cb-missing')) + self.send_blocks([b45], False, "bad-cb-missing", reconnect=True) - # A block with no txns - tip(44) + self.log.info("Reject a block with no transactions") + self.move_tip(44) b46 = CBlock() - b46.nTime = b44.nTime+1 + b46.nTime = b44.nTime + 1 b46.hashPrevBlock = b44.sha256 b46.nBits = 0x207fffff b46.vtx = [] b46.hashMerkleRoot = 0 b46.solve() - self.block_heights[b46.sha256] = self.block_heights[b44.sha256]+1 + self.block_heights[b46.sha256] = self.block_heights[b44.sha256] + 1 self.tip = b46 assert 46 not in self.blocks self.blocks[46] = b46 - s = ser_uint256(b46.hashMerkleRoot) - yield rejected(RejectResult(16, b'bad-blk-length')) + self.send_blocks([b46], False, "bad-blk-length", reconnect=True) - # A block with invalid work - tip(44) - b47 = block(47, solve=False) + self.log.info("Reject a block with invalid work") + self.move_tip(44) + b47 = self.next_block(47, solve=False) target = uint256_from_compact(b47.nBits) - while b47.sha256 < target: #changed > to < + while b47.sha256 < target: b47.nNonce += 1 b47.rehash() - yield rejected(RejectResult(16, b'high-hash')) - - # A block with timestamp > 2 hrs in the future - tip(44) - b48 = block(48, solve=False) - b48.nTime = int(time.time()) + 60 * 60 * 3 - b48.solve() - yield rejected(RejectResult(16, b'time-too-new')) - - # A block with an invalid merkle hash - tip(44) - b49 = block(49) + self.send_blocks([b47], False) + + self.log.info("Reject a block with invalid merkle hash") + self.move_tip(44) + b49 = self.next_block(49) b49.hashMerkleRoot += 1 b49.solve() - yield rejected(RejectResult(16, b'bad-txnmrklroot')) + self.send_blocks([b49], False, "bad-txnmrklroot", reconnect=True) - # A block with an incorrect POW limit - tip(44) - b50 = block(50) + self.log.info("Reject a block with incorrect POW limit") + self.move_tip(44) + b50 = self.next_block(50) b50.nBits = b50.nBits - 1 b50.solve() - yield rejected(RejectResult(16, b'bad-diffbits')) + # !TODO: fix expect_disconnect + self.send_blocks([b50], False, reconnect=False) - # A block with two coinbase txns - tip(44) - b51 = block(51) + self.log.info("Reject a block with two coinbase transactions") + self.move_tip(44) + b51 = self.next_block(51) cb2 = create_coinbase(51, self.coinbase_pubkey) - b51 = update_block(51, [cb2]) - yield rejected(RejectResult(16, b'bad-cb-multiple')) + b51 = self.update_block(51, [cb2]) + self.send_blocks([b51], False, "bad-cb-multiple", reconnect=True) - # A block w/ duplicate txns + self.log.info("Reject a block with duplicate transactions") # Note: txns have to be in the right position in the merkle tree to trigger this error - tip(44) - b52 = block(52, spend=out[15]) - tx = create_tx(b52.vtx[1], 0, 1) - b52 = update_block(52, [tx, tx]) - yield rejected(RejectResult(16, b'bad-txns-duplicate')) + self.move_tip(44) + b52 = self.next_block(52, spend=out[15]) + tx = self.create_tx(b52.vtx[1], 0, 1) + b52 = self.update_block(52, [tx, tx]) + self.send_blocks([b52], False, "bad-txns-duplicate", reconnect=True) # Test block timestamps # -> b31 (8) -> b33 (9) -> b35 (10) -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) # \-> b54 (15) # - tip(43) - block(53, spend=out[14]) - yield rejected() # rejected since b44 is at same height - save_spendable_output() + self.move_tip(43) + b53 = self.next_block(53, spend=out[14]) + self.send_blocks([b53], False) + self.save_spendable_output() - # invalid timestamp (b35 is 5 blocks back, so its time is MedianTimePast) - b54 = block(54, spend=out[15]) - b54.nTime = b35.nTime - 1 - b54.solve() - yield rejected(RejectResult(16, b'time-too-old')) + # PIVX: timestamp checks disabled for regtest # valid timestamp - tip(53) - b55 = block(55, spend=out[15]) + self.move_tip(53) + b55 = self.next_block(55, spend=out[15]) b55.nTime = b35.nTime - update_block(55, []) - yield accepted() - save_spendable_output() - + self.update_block(55, []) + self.send_blocks([b55], True) + self.save_spendable_output() - # Test CVE-2012-2459 + # Test Merkle tree malleability # # -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57p2 (16) # \-> b57 (16) @@ -758,46 +668,48 @@ def update_block(block_number, new_transactions): # that the error was caught early, avoiding a DOS vulnerability.) # b57 - a good block with 2 txs, don't submit until end - tip(55) - b57 = block(57) - tx = create_and_sign_tx(out[16].tx, out[16].n, 1) - tx1 = create_tx(tx, 0, 1) - b57 = update_block(57, [tx, tx1]) + self.move_tip(55) + b57 = self.next_block(57) + tx = self.create_and_sign_transaction(out[16].tx, out[16].n, 1) + tx1 = self.create_tx(tx, 0, 1) + b57 = self.update_block(57, [tx, tx1]) # b56 - copy b57, add a duplicate tx - tip(55) + self.log.info("Reject a block with a duplicate transaction in the Merkle Tree (but with a valid Merkle Root)") + self.move_tip(55) b56 = copy.deepcopy(b57) self.blocks[56] = b56 - assert_equal(len(b56.vtx),3) - b56 = update_block(56, [tx1]) + assert_equal(len(b56.vtx), 3) + b56 = self.update_block(56, [tx1]) assert_equal(b56.hash, b57.hash) - yield rejected(RejectResult(16, b'bad-txns-duplicate')) + self.send_blocks([b56], False, "bad-txns-duplicate", reconnect=True) # b57p2 - a good block with 6 tx'es, don't submit until end - tip(55) - b57p2 = block("57p2") - tx = create_and_sign_tx(out[16].tx, out[16].n, 1) - tx1 = create_tx(tx, 0, 1) - tx2 = create_tx(tx1, 0, 1) - tx3 = create_tx(tx2, 0, 1) - tx4 = create_tx(tx3, 0, 1) - b57p2 = update_block("57p2", [tx, tx1, tx2, tx3, tx4]) + self.move_tip(55) + b57p2 = self.next_block("57p2") + tx = self.create_and_sign_transaction(out[16].tx, out[16].n, 1) + tx1 = self.create_tx(tx, 0, 1) + tx2 = self.create_tx(tx1, 0, 1) + tx3 = self.create_tx(tx2, 0, 1) + tx4 = self.create_tx(tx3, 0, 1) + b57p2 = self.update_block("57p2", [tx, tx1, tx2, tx3, tx4]) # b56p2 - copy b57p2, duplicate two non-consecutive tx's - tip(55) + self.log.info("Reject a block with two duplicate transactions in the Merkle Tree (but with a valid Merkle Root)") + self.move_tip(55) b56p2 = copy.deepcopy(b57p2) self.blocks["b56p2"] = b56p2 assert_equal(b56p2.hash, b57p2.hash) - assert_equal(len(b56p2.vtx),6) - b56p2 = update_block("b56p2", [tx3, tx4]) - yield rejected(RejectResult(16, b'bad-txns-duplicate')) + assert_equal(len(b56p2.vtx), 6) + b56p2 = self.update_block("b56p2", [tx3, tx4]) + self.send_blocks([b56p2], False, "bad-txns-duplicate", reconnect=True) - tip("57p2") - yield accepted() + self.move_tip("57p2") + self.send_blocks([b57p2], True) - tip(57) - yield rejected() #rejected because 57p2 seen first - save_spendable_output() + self.move_tip(57) + self.send_blocks([b57], False) # The tip is not updated because 57p2 seen first + self.save_spendable_output() # Test a few invalid tx types # @@ -806,130 +718,117 @@ def update_block(block_number, new_transactions): # # tx with prevout.n out of range - tip(57) - b58 = block(58, spend=out[17]) + self.log.info("Reject a block with a transaction with prevout.n out of range") + self.move_tip(57) + b58 = self.next_block(58, spend=out[17]) tx = CTransaction() assert(len(out[17].tx.vout) < 42) tx.vin.append(CTxIn(COutPoint(out[17].tx.sha256, 42), CScript([OP_TRUE]), 0xffffffff)) - tx.vout.append(CTxOut(0, b"")) + tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) tx.calc_sha256() - b58 = update_block(58, [tx]) - yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent')) - - # tx with output value > input value out of range - tip(57) - b59 = block(59) - tx = create_and_sign_tx(out[17].tx, out[17].n, 51*COIN) - b59 = update_block(59, [tx]) - yield rejected(RejectResult(16, b'bad-txns-in-belowout')) + b58 = self.update_block(58, [tx]) + # !TODO: fix expect_disconnect + self.send_blocks([b58], False, "bad-txns-inputs-missingorspent", reconnect=False) + + # tx with output value > input value + self.log.info("Reject a block with a transaction with outputs > inputs") + self.move_tip(57) + b59 = self.next_block(59) + tx = self.create_and_sign_transaction(out[17].tx, out[17].n, 251 * COIN) + b59 = self.update_block(59, [tx]) + # !TODO: fix expect_disconnect + self.send_blocks([b59], False, "bad-txns-in-belowout", reconnect=False) # reset to good chain - tip(57) - b60 = block(60, spend=out[17]) - yield accepted() - save_spendable_output() + self.move_tip(57) + b60 = self.next_block(60, spend=out[17]) + self.send_blocks([b60], True) + self.save_spendable_output() + # Test BIP30 + # + # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) + # \-> b61 (18) + # + # Blocks are not allowed to contain a transaction whose id matches that of an earlier, + # not-fully-spent transaction in the same chain. To test, make identical coinbases; + # the second one should be rejected. + # + self.log.info("Reject a block with a transaction with a duplicate hash of a previous transaction (BIP30)") + self.move_tip(60) + b61 = self.next_block(61, spend=out[18]) + b61.vtx[0].vin[0].scriptSig = b60.vtx[0].vin[0].scriptSig # Equalize the coinbases + b61.vtx[0].rehash() + b61 = self.update_block(61, []) + assert_equal(b60.vtx[0].serialize(), b61.vtx[0].serialize()) + self.send_blocks([b61], False, "bad-cb-height", reconnect=True) # Test tx.isFinal is properly rejected (not an exhaustive tx.isFinal test, that should be in data-driven transaction tests) # # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) # \-> b62 (18) # - tip(60) - b62 = block(62) + self.log.info("Reject a block with a transaction with a nonfinal locktime") + self.move_tip(60) + b62 = self.next_block(62) tx = CTransaction() - tx.nLockTime = 0xffffffff #this locktime is non-final + tx.nLockTime = 0xffffffff # this locktime is non-final assert(out[18].n < len(out[18].tx.vout)) - tx.vin.append(CTxIn(COutPoint(out[18].tx.sha256, out[18].n))) # don't set nSequence + tx.vin.append(CTxIn(COutPoint(out[18].tx.sha256, out[18].n))) # don't set nSequence tx.vout.append(CTxOut(0, CScript([OP_TRUE]))) assert(tx.vin[0].nSequence < 0xffffffff) tx.calc_sha256() - b62 = update_block(62, [tx]) - yield rejected(RejectResult(16, b'bad-txns-nonfinal')) - + b62 = self.update_block(62, [tx]) + self.send_blocks([b62], False, "bad-txns-nonfinal") # Test a non-final coinbase is also rejected # # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) # \-> b63 (-) # - tip(60) - b63 = block(63) + self.log.info("Reject a block with a coinbase transaction with a nonfinal locktime") + self.move_tip(60) + b63 = self.next_block(63) b63.vtx[0].nLockTime = 0xffffffff b63.vtx[0].vin[0].nSequence = 0xDEADBEEF b63.vtx[0].rehash() - b63 = update_block(63, []) - yield rejected(RejectResult(16, b'bad-txns-nonfinal')) - + b63 = self.update_block(63, []) + self.send_blocks([b63], False, "bad-txns-nonfinal") - # This checks that a block with a bloated VARINT between the block_header and the array of tx such that - # the block is > MAX_BLOCK_BASE_SIZE with the bloated varint, but <= MAX_BLOCK_BASE_SIZE without the bloated varint, - # does not cause a subsequent, identical block with canonical encoding to be rejected. The test does not - # care whether the bloated block is accepted or rejected; it only cares that the second block is accepted. - # - # What matters is that the receiving node should not reject the bloated block, and then reject the canonical - # block on the basis that it's the same as an already-rejected block (which would be a consensus failure.) # # -> b39 (11) -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) - # \ - # b64a (18) - # b64a is a bloated block (non-canonical varint) - # b64 is a good block (same as b64 but w/ canonical varint) - # - tip(60) - regular_block = block("64a", spend=out[18]) - - # make it a "broken_block," with non-canonical serialization - b64a = CBrokenBlock(regular_block) - b64a.initialize(regular_block) - self.blocks["64a"] = b64a - self.tip = b64a - tx = CTransaction() - - # use canonical serialization to calculate size - script_length = MAX_BLOCK_BASE_SIZE - len(b64a.normal_serialize()) - 69 - script_output = CScript([b'\x00' * script_length]) - tx.vout.append(CTxOut(0, script_output)) - tx.vin.append(CTxIn(COutPoint(b64a.vtx[1].sha256, 0))) - b64a = update_block("64a", [tx]) - assert_equal(len(b64a.serialize()), MAX_BLOCK_BASE_SIZE + 8) - yield TestInstance([[self.tip, None]]) - - # comptool workaround: to make sure b64 is delivered, manually erase b64a from blockstore - self.test.block_store.erase(b64a.sha256) - - tip(60) - b64 = CBlock(b64a) - b64.vtx = copy.deepcopy(b64a.vtx) - assert_equal(b64.hash, b64a.hash) - assert_equal(len(b64.serialize()), MAX_BLOCK_BASE_SIZE) - self.blocks[64] = b64 - update_block(64, []) - yield accepted() - save_spendable_output() + # + self.move_tip(60) + b64 = self.next_block(64) + b64 = self.update_block(64, []) + self.send_blocks([b64], True) + self.save_spendable_output() # Spend an output created in the block itself # # -> b42 (12) -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) # - tip(64) - block(65) - tx1 = create_and_sign_tx(out[19].tx, out[19].n, out[19].tx.vout[0].nValue) - tx2 = create_and_sign_tx(tx1, 0, 0) - update_block(65, [tx1, tx2]) - yield accepted() - save_spendable_output() + self.log.info("Accept a block with a transaction spending an output created in the same block") + self.move_tip(64) + b65 = self.next_block(65) + tx1 = self.create_and_sign_transaction(out[19].tx, out[19].n, out[19].tx.vout[0].nValue) + tx2 = self.create_and_sign_transaction(tx1, 0, 0) + b65 = self.update_block(65, [tx1, tx2]) + self.send_blocks([b65], True) + self.save_spendable_output() # Attempt to spend an output created later in the same block # # -> b43 (13) -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) # \-> b66 (20) - tip(65) - block(66) - tx1 = create_and_sign_tx(out[20].tx, out[20].n, out[20].tx.vout[0].nValue) - tx2 = create_and_sign_tx(tx1, 0, 1) - update_block(66, [tx2, tx1]) - yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent')) + self.log.info("Reject a block with a transaction spending an output created later in the same block") + self.move_tip(65) + b66 = self.next_block(66) + tx1 = self.create_and_sign_transaction(out[20].tx, out[20].n, out[20].tx.vout[0].nValue) + tx2 = self.create_and_sign_transaction(tx1, 0, 1) + b66 = self.update_block(66, [tx2, tx1]) + # !TODO: fix expect_disconnect + self.send_blocks([b66], False, "bad-txns-inputs-missingorspent", reconnect=False) # Attempt to double-spend a transaction created in a block # @@ -937,13 +836,15 @@ def update_block(block_number, new_transactions): # \-> b67 (20) # # - tip(65) - block(67) - tx1 = create_and_sign_tx(out[20].tx, out[20].n, out[20].tx.vout[0].nValue) - tx2 = create_and_sign_tx(tx1, 0, 1) - tx3 = create_and_sign_tx(tx1, 0, 2) - update_block(67, [tx1, tx2, tx3]) - yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent')) + self.log.info("Reject a block with a transaction double spending a transaction created in the same block") + self.move_tip(65) + b67 = self.next_block(67) + tx1 = self.create_and_sign_transaction(out[20].tx, out[20].n, out[20].tx.vout[0].nValue) + tx2 = self.create_and_sign_transaction(tx1, 0, 1) + tx3 = self.create_and_sign_transaction(tx1, 0, 2) + b67 = self.update_block(67, [tx1, tx2, tx3]) + # !TODO: fix expect_disconnect + self.send_blocks([b67], False, "bad-txns-inputs-missingorspent", reconnect=False) # More tests of block subsidy # @@ -957,34 +858,38 @@ def update_block(block_number, new_transactions): # b69 - coinbase with extra 10 satoshis, and a tx that gives a 10 satoshi fee # this succeeds # - tip(65) - block(68, additional_coinbase_value=10) - tx = create_and_sign_tx(out[20].tx, out[20].n, out[20].tx.vout[0].nValue-9) - update_block(68, [tx]) - yield rejected(RejectResult(16, b'bad-cb-amount')) - - tip(65) - b69 = block(69, additional_coinbase_value=10) - tx = create_and_sign_tx(out[20].tx, out[20].n, out[20].tx.vout[0].nValue-10) - update_block(69, [tx]) - yield accepted() - save_spendable_output() + self.log.info("Reject a block trying to claim too much subsidy in the coinbase transaction") + self.move_tip(65) + b68 = self.next_block(68, additional_coinbase_value=10) + tx = self.create_and_sign_transaction(out[20].tx, out[20].n, out[20].tx.vout[0].nValue - 9) + b68 = self.update_block(68, [tx]) + # !TODO: fix expect_disconnect + self.send_blocks([b68], False, "bad-blk-amount", reconnect=False) + + self.log.info("Accept a block claiming the correct subsidy in the coinbase transaction") + self.move_tip(65) + b69 = self.next_block(69, additional_coinbase_value=10) + tx = self.create_and_sign_transaction(out[20].tx, out[20].n, out[20].tx.vout[0].nValue - 10) + self.update_block(69, [tx]) + self.send_blocks([b69], True) + self.save_spendable_output() # Test spending the outpoint of a non-existent transaction # # -> b53 (14) -> b55 (15) -> b57 (16) -> b60 (17) -> b64 (18) -> b65 (19) -> b69 (20) # \-> b70 (21) # - tip(69) - block(70, spend=out[21]) + self.log.info("Reject a block containing a transaction spending from a non-existent input") + self.move_tip(69) + b70 = self.next_block(70, spend=out[21]) bogus_tx = CTransaction() bogus_tx.sha256 = uint256_from_str(b"23c70ed7c0506e9178fc1a987f40a33946d4ad4c962b5ae3a52546da53af0c5c") tx = CTransaction() tx.vin.append(CTxIn(COutPoint(bogus_tx.sha256, 0), b"", 0xffffffff)) tx.vout.append(CTxOut(1, b"")) - update_block(70, [tx]) - yield rejected(RejectResult(16, b'bad-txns-inputs-missingorspent')) - + b70 = self.update_block(70, [tx]) + # !TODO: fix expect_disconnect + self.send_blocks([b70], False, "bad-txns-inputs-missingorspent", reconnect=False) # Test accepting an invalid block which has the same hash as a valid one (via merkle tree tricks) # @@ -992,13 +897,13 @@ def update_block(block_number, new_transactions): # \-> b71 (21) # # b72 is a good block. - # b71 is a copy of 72, but re-adds one of its transactions. However, it has the same hash as b71. - # - tip(69) - b72 = block(72) - tx1 = create_and_sign_tx(out[21].tx, out[21].n, 2) - tx2 = create_and_sign_tx(tx1, 0, 1) - b72 = update_block(72, [tx1, tx2]) # now tip is 72 + # b71 is a copy of 72, but re-adds one of its transactions. However, it has the same hash as b72. + self.log.info("Reject a block containing a duplicate transaction but with the same Merkle root (Merkle tree malleability") + self.move_tip(69) + b72 = self.next_block(72) + tx1 = self.create_and_sign_transaction(out[21].tx, out[21].n, 2) + tx2 = self.create_and_sign_transaction(tx1, 0, 1) + b72 = self.update_block(72, [tx1, tx2]) # now tip is 72 b71 = copy.deepcopy(b72) b71.vtx.append(tx2) # add duplicate tx2 self.block_heights[b71.sha256] = self.block_heights[b69.sha256] + 1 # b71 builds off b69 @@ -1008,12 +913,12 @@ def update_block(block_number, new_transactions): assert_equal(len(b72.vtx), 3) assert_equal(b72.sha256, b71.sha256) - tip(71) - yield rejected(RejectResult(16, b'bad-txns-duplicate')) - tip(72) - yield accepted() - save_spendable_output() + self.move_tip(71) + self.send_blocks([b71], False, "bad-txns-duplicate", reconnect=True) + self.move_tip(72) + self.send_blocks([b72], True) + self.save_spendable_output() # Test some invalid scripts and MAX_BLOCK_SIGOPS # @@ -1031,23 +936,23 @@ def update_block(block_number, new_transactions): # bytearray[20,000-20,003]: 521 (max_script_element_size+1, in little-endian format) # bytearray[20,004-20,525]: unread data (script_element) # bytearray[20,526] : OP_CHECKSIG (this puts us over the limit) - # - tip(72) - b73 = block(73) + self.log.info("Reject a block containing too many sigops after a large script element") + self.move_tip(72) + b73 = self.next_block(73) size = MAX_BLOCK_SIGOPS - 1 + MAX_SCRIPT_ELEMENT_SIZE + 1 + 5 + 1 a = bytearray([OP_CHECKSIG] * size) - a[MAX_BLOCK_SIGOPS - 1] = int("4e",16) # OP_PUSHDATA4 + a[MAX_BLOCK_SIGOPS - 1] = int("4e", 16) # OP_PUSHDATA4 element_size = MAX_SCRIPT_ELEMENT_SIZE + 1 a[MAX_BLOCK_SIGOPS] = element_size % 256 - a[MAX_BLOCK_SIGOPS+1] = element_size // 256 - a[MAX_BLOCK_SIGOPS+2] = 0 - a[MAX_BLOCK_SIGOPS+3] = 0 + a[MAX_BLOCK_SIGOPS + 1] = element_size // 256 + a[MAX_BLOCK_SIGOPS + 2] = 0 + a[MAX_BLOCK_SIGOPS + 3] = 0 - tx = create_and_sign_tx(out[22].tx, 0, 1, CScript(a)) - b73 = update_block(73, [tx]) - assert_equal(get_legacy_sigopcount_block(b73), MAX_BLOCK_SIGOPS+1) - yield rejected(RejectResult(16, b'bad-blk-sigops')) + tx = self.create_and_sign_transaction(out[22].tx, 0, 1, CScript(a)) + b73 = self.update_block(73, [tx]) + assert_equal(get_legacy_sigopcount_block(b73), MAX_BLOCK_SIGOPS + 1) + self.send_blocks([b73], False, 'bad-blk-sigops', reconnect=True) # b74/75 - if we push an invalid script element, all prevous sigops are counted, # but sigops after the element are not counted. @@ -1059,45 +964,44 @@ def update_block(block_number, new_transactions): # # b74 fails because we put MAX_BLOCK_SIGOPS+1 before the element # b75 succeeds because we put MAX_BLOCK_SIGOPS before the element - # - # - tip(72) - b74 = block(74) - size = MAX_BLOCK_SIGOPS - 1 + MAX_SCRIPT_ELEMENT_SIZE + 42 # total = 20,561 + self.log.info("Check sigops are counted correctly after an invalid script element") + self.move_tip(72) + b74 = self.next_block(74) + size = MAX_BLOCK_SIGOPS - 1 + MAX_SCRIPT_ELEMENT_SIZE + 42 # total = 20,561 a = bytearray([OP_CHECKSIG] * size) a[MAX_BLOCK_SIGOPS] = 0x4e - a[MAX_BLOCK_SIGOPS+1] = 0xfe - a[MAX_BLOCK_SIGOPS+2] = 0xff - a[MAX_BLOCK_SIGOPS+3] = 0xff - a[MAX_BLOCK_SIGOPS+4] = 0xff - tx = create_and_sign_tx(out[22].tx, 0, 1, CScript(a)) - b74 = update_block(74, [tx]) - yield rejected(RejectResult(16, b'bad-blk-sigops')) - - tip(72) - b75 = block(75) + a[MAX_BLOCK_SIGOPS + 1] = 0xfe + a[MAX_BLOCK_SIGOPS + 2] = 0xff + a[MAX_BLOCK_SIGOPS + 3] = 0xff + a[MAX_BLOCK_SIGOPS + 4] = 0xff + tx = self.create_and_sign_transaction(out[22].tx, 0, 1, CScript(a)) + b74 = self.update_block(74, [tx]) + self.send_blocks([b74], False, 'bad-blk-sigops', reconnect=True) + + self.move_tip(72) + b75 = self.next_block(75) size = MAX_BLOCK_SIGOPS - 1 + MAX_SCRIPT_ELEMENT_SIZE + 42 a = bytearray([OP_CHECKSIG] * size) - a[MAX_BLOCK_SIGOPS-1] = 0x4e + a[MAX_BLOCK_SIGOPS - 1] = 0x4e a[MAX_BLOCK_SIGOPS] = 0xff - a[MAX_BLOCK_SIGOPS+1] = 0xff - a[MAX_BLOCK_SIGOPS+2] = 0xff - a[MAX_BLOCK_SIGOPS+3] = 0xff - tx = create_and_sign_tx(out[22].tx, 0, 1, CScript(a)) - b75 = update_block(75, [tx]) - yield accepted() - save_spendable_output() + a[MAX_BLOCK_SIGOPS + 1] = 0xff + a[MAX_BLOCK_SIGOPS + 2] = 0xff + a[MAX_BLOCK_SIGOPS + 3] = 0xff + tx = self.create_and_sign_transaction(out[22].tx, 0, 1, CScript(a)) + b75 = self.update_block(75, [tx]) + self.send_blocks([b75], True) + self.save_spendable_output() # Check that if we push an element filled with CHECKSIGs, they are not counted - tip(75) - b76 = block(76) + self.move_tip(75) + b76 = self.next_block(76) size = MAX_BLOCK_SIGOPS - 1 + MAX_SCRIPT_ELEMENT_SIZE + 1 + 5 a = bytearray([OP_CHECKSIG] * size) - a[MAX_BLOCK_SIGOPS-1] = 0x4e # PUSHDATA4, but leave the following bytes as just checksigs - tx = create_and_sign_tx(out[23].tx, 0, 1, CScript(a)) - b76 = update_block(76, [tx]) - yield accepted() - save_spendable_output() + a[MAX_BLOCK_SIGOPS - 1] = 0x4e # PUSHDATA4, but leave the following bytes as just checksigs + tx = self.create_and_sign_transaction(out[23].tx, 0, 1, CScript(a)) + b76 = self.update_block(76, [tx]) + self.send_blocks([b76], True) + self.save_spendable_output() # Test transaction resurrection # @@ -1108,7 +1012,7 @@ def update_block(block_number, new_transactions): # # The tx'es must be unsigned and pass the node's mempool policy. It is unsigned for the # rather obscure reason that the Python signature code does not distinguish between - # Low-S and High-S values (whereas the bitcoin code has custom code which does so); + # Low-S and High-S values (whereas the pivx code has custom code which does so); # as a result of which, the odds are 50% that the python code will use the right # value and the transaction will be accepted into the mempool. Until we modify the # test framework to support low-S signing, we are out of luck. @@ -1116,39 +1020,39 @@ def update_block(block_number, new_transactions): # To get around this issue, we construct transactions which are not signed and which # spend to OP_TRUE. If the standard-ness rules change, this test would need to be # updated. (Perhaps to spend to a P2SH OP_TRUE script) - # - tip(76) - block(77) - tx77 = create_and_sign_tx(out[24].tx, out[24].n, 10*COIN) - update_block(77, [tx77]) - yield accepted() - save_spendable_output() - - block(78) - tx78 = create_tx(tx77, 0, 9*COIN) - update_block(78, [tx78]) - yield accepted() - - block(79) - tx79 = create_tx(tx78, 0, 8*COIN) - update_block(79, [tx79]) - yield accepted() + self.log.info("Test transaction resurrection during a re-org") + self.move_tip(76) + b77 = self.next_block(77) + tx77 = self.create_and_sign_transaction(out[24].tx, out[24].n, 10 * COIN) + b77 = self.update_block(77, [tx77]) + self.send_blocks([b77], True) + self.save_spendable_output() + + b78 = self.next_block(78) + tx78 = self.create_tx(tx77, 0, 9 * COIN) + b78 = self.update_block(78, [tx78]) + self.send_blocks([b78], True) + + b79 = self.next_block(79) + tx79 = self.create_tx(tx78, 0, 8 * COIN) + b79 = self.update_block(79, [tx79]) + self.send_blocks([b79], True) # mempool should be empty assert_equal(len(self.nodes[0].getrawmempool()), 0) - tip(77) - block(80, spend=out[25]) - yield rejected() - save_spendable_output() + self.move_tip(77) + b80 = self.next_block(80, spend=out[25]) + self.send_blocks([b80], False) + self.save_spendable_output() - block(81, spend=out[26]) - yield rejected() # other chain is same length - save_spendable_output() + b81 = self.next_block(81, spend=out[26]) + self.send_blocks([b81], False) # other chain is same length + self.save_spendable_output() - block(82, spend=out[27]) - yield accepted() # now this chain is longer, triggers re-org - save_spendable_output() + b82 = self.next_block(82, spend=out[27]) + self.send_blocks([b82], True) # now this chain is longer, triggers re-org + self.save_spendable_output() # now check that tx78 and tx79 have been put back into the peer's mempool mempool = self.nodes[0].getrawmempool() @@ -1156,33 +1060,32 @@ def update_block(block_number, new_transactions): assert(tx78.hash in mempool) assert(tx79.hash in mempool) - # Test invalid opcodes in dead execution paths. # # -> b81 (26) -> b82 (27) -> b83 (28) # - block(83) + self.log.info("Accept a block with invalid opcodes in dead execution paths") + b83 = self.next_block(83) op_codes = [OP_IF, OP_INVALIDOPCODE, OP_ELSE, OP_TRUE, OP_ENDIF] script = CScript(op_codes) - tx1 = create_and_sign_tx(out[28].tx, out[28].n, out[28].tx.vout[0].nValue, script) + tx1 = self.create_and_sign_transaction(out[28].tx, out[28].n, out[28].tx.vout[0].nValue, script) - tx2 = create_and_sign_tx(tx1, 0, 0, CScript([OP_TRUE])) + tx2 = self.create_and_sign_transaction(tx1, 0, 0, CScript([OP_TRUE])) tx2.vin[0].scriptSig = CScript([OP_FALSE]) tx2.rehash() - update_block(83, [tx1, tx2]) - yield accepted() - save_spendable_output() - + b83 = self.update_block(83, [tx1, tx2]) + self.send_blocks([b83], True) + self.save_spendable_output() # Reorg on/off blocks that have OP_RETURN in them (and try to spend them) # # -> b81 (26) -> b82 (27) -> b83 (28) -> b84 (29) -> b87 (30) -> b88 (31) # \-> b85 (29) -> b86 (30) \-> b89a (32) # - # - block(84) - tx1 = create_tx(out[29].tx, out[29].n, 0, CScript([OP_RETURN])) + self.log.info("Test re-orging blocks with OP_RETURN in them") + b84 = self.next_block(84) + tx1 = self.create_tx(out[29].tx, out[29].n, 0, CScript([OP_RETURN])) tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) tx1.vout.append(CTxOut(0, CScript([OP_TRUE]))) @@ -1190,86 +1093,186 @@ def update_block(block_number, new_transactions): tx1.calc_sha256() self.sign_tx(tx1, out[29].tx, out[29].n) tx1.rehash() - tx2 = create_tx(tx1, 1, 0, CScript([OP_RETURN])) + tx2 = self.create_tx(tx1, 1, 0, CScript([OP_RETURN])) tx2.vout.append(CTxOut(0, CScript([OP_RETURN]))) - tx3 = create_tx(tx1, 2, 0, CScript([OP_RETURN])) + tx3 = self.create_tx(tx1, 2, 0, CScript([OP_RETURN])) tx3.vout.append(CTxOut(0, CScript([OP_TRUE]))) - tx4 = create_tx(tx1, 3, 0, CScript([OP_TRUE])) + tx4 = self.create_tx(tx1, 3, 0, CScript([OP_TRUE])) tx4.vout.append(CTxOut(0, CScript([OP_RETURN]))) - tx5 = create_tx(tx1, 4, 0, CScript([OP_RETURN])) + tx5 = self.create_tx(tx1, 4, 0, CScript([OP_RETURN])) - update_block(84, [tx1,tx2,tx3,tx4,tx5]) - yield accepted() - save_spendable_output() + b84 = self.update_block(84, [tx1, tx2, tx3, tx4, tx5]) + self.send_blocks([b84], True) + self.save_spendable_output() - tip(83) - block(85, spend=out[29]) - yield rejected() + self.move_tip(83) + b85 = self.next_block(85, spend=out[29]) + self.send_blocks([b85], False) # other chain is same length - block(86, spend=out[30]) - yield accepted() + b86 = self.next_block(86, spend=out[30]) + self.send_blocks([b86], True) - tip(84) - block(87, spend=out[30]) - yield rejected() - save_spendable_output() + self.move_tip(84) + b87 = self.next_block(87, spend=out[30]) + self.send_blocks([b87], False) # other chain is same length + self.save_spendable_output() - block(88, spend=out[31]) - yield accepted() - save_spendable_output() + b88 = self.next_block(88, spend=out[31]) + self.send_blocks([b88], True) + self.save_spendable_output() # trying to spend the OP_RETURN output is rejected - block("89a", spend=out[32]) - tx = create_tx(tx1, 0, 0, CScript([OP_TRUE])) - update_block("89a", [tx]) - yield rejected() - - - # Test re-org of a week's worth of blocks (1088 blocks) - # This test takes a minute or two and can be accomplished in memory - # - if self.options.runbarelyexpensive: - tip(88) - LARGE_REORG_SIZE = 1088 - test1 = TestInstance(sync_every_block=False) - spend=out[32] - for i in range(89, LARGE_REORG_SIZE + 89): - b = block(i, spend) - tx = CTransaction() - script_length = MAX_BLOCK_BASE_SIZE - len(b.serialize()) - 69 - script_output = CScript([b'\x00' * script_length]) - tx.vout.append(CTxOut(0, script_output)) - tx.vin.append(CTxIn(COutPoint(b.vtx[1].sha256, 0))) - b = update_block(i, [tx]) - assert_equal(len(b.serialize()), MAX_BLOCK_BASE_SIZE) - test1.blocks_and_transactions.append([self.tip, True]) - save_spendable_output() - spend = get_spendable_output() - - yield test1 - chain1_tip = i - - # now create alt chain of same length - tip(88) - test2 = TestInstance(sync_every_block=False) - for i in range(89, LARGE_REORG_SIZE + 89): - block("alt"+str(i)) - test2.blocks_and_transactions.append([self.tip, False]) - yield test2 - - # extend alt chain to trigger re-org - block("alt" + str(chain1_tip + 1)) - yield accepted() - - # ... and re-org back to the first chain - tip(chain1_tip) - block(chain1_tip + 1) - yield rejected() - block(chain1_tip + 2) - yield accepted() - - chain1_tip += 2 + b89a = self.next_block("89a", spend=out[32]) + tx = self.create_tx(tx1, 0, 0, CScript([OP_TRUE])) + b89a = self.update_block("89a", [tx]) + # !TODO: fix expect_disconnect + self.send_blocks([b89a], False, 'bad-txns-inputs-missingorspent', reconnect=False) + + # !TODO: add long-reorg test + + # Helper methods + ################ + + def add_transactions_to_block(self, block, tx_list): + [tx.rehash() for tx in tx_list] + block.vtx.extend(tx_list) + + # this is a little handier to use than the version in blocktools.py + def create_tx(self, spend_tx, n, value, script=CScript([OP_TRUE])): + return create_transaction(spend_tx, n, b"", value, script) + + # sign a transaction, using the key we know about + # this signs input 0 in tx, which is assumed to be spending output n in spend_tx + def sign_tx(self, tx, spend_tx, n): + scriptPubKey = bytearray(spend_tx.vout[n].scriptPubKey) + if (scriptPubKey[0] == OP_TRUE): # an anyone-can-spend + tx.vin[0].scriptSig = CScript() + return + (sighash, err) = SignatureHash(spend_tx.vout[n].scriptPubKey, tx, 0, SIGHASH_ALL) + tx.vin[0].scriptSig = CScript([self.coinbase_key.sign(sighash) + bytes(bytearray([SIGHASH_ALL]))]) + + def create_and_sign_transaction(self, spend_tx, n, value, script=CScript([OP_TRUE])): + tx = self.create_tx(spend_tx, n, value, script) + self.sign_tx(tx, spend_tx, n) + tx.rehash() + return tx + + def next_block(self, number, spend=None, additional_coinbase_value=0, script=CScript([OP_TRUE]), solve=True): + if self.tip is 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, self.coinbase_pubkey) + coinbase.vout[0].nValue += additional_coinbase_value + coinbase.rehash() + if spend is None: + block = create_block(base_block_hash, coinbase, block_time) + else: + coinbase.vout[0].nValue += spend.tx.vout[spend.n].nValue - 1 # all but one satoshi to fees + coinbase.rehash() + block = create_block(base_block_hash, coinbase, block_time) + tx = create_transaction(spend.tx, spend.n, b"", 1, script) # spend 1 satoshi + self.sign_tx(tx, spend.tx, spend.n) + self.add_transactions_to_block(block, [tx]) + block.hashMerkleRoot = block.calc_merkle_root() + if solve: + block.solve() + self.tip = block + self.block_heights[block.sha256] = height + assert number not in self.blocks + self.blocks[number] = block + return block + + # save the current tip so it can be spent by a later block + def save_spendable_output(self): + self.log.debug("saving spendable output %s" % self.tip.vtx[0]) + self.spendable_outputs.append(self.tip) + + # get an output that we previously marked as spendable + def get_spendable_output(self): + self.log.debug("getting spendable output %s" % self.spendable_outputs[0].vtx[0]) + return PreviousSpendableOutput(self.spendable_outputs.pop(0).vtx[0], 0) + + # move the tip back to a previous block + def move_tip(self, number): + self.tip = self.blocks[number] + + # adds transactions to the block and updates state + def update_block(self, block_number, new_transactions): + block = self.blocks[block_number] + self.add_transactions_to_block(block, 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 + + def reconnect_p2p(self): + """Add a 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.""" + + network_thread_join() + self.nodes[0].disconnect_p2ps() + self.nodes[0].add_p2p_connection(P2PDataStore()) + network_thread_start() + self.nodes[0].p2p.wait_for_verack() + + def send_blocks(self, blocks, success=True, reject_reason=None, reconnect=False, timeout=60): + """Sends blocks to test node. Syncs and verifies that tip has advanced to most recent block. + + Call with success = False if the tip shouldn't advance to the most recent block.""" + self.nodes[0].p2p.send_blocks_and_test(blocks, self.nodes[0], success=success, reject_reason=reject_reason, expect_disconnect=reconnect, timeout=timeout) + + if reconnect: + self.reconnect_p2p() + + # PIVX + # create a block with a tx spending a given out, and lots of txes spending the outputs created + # in the first one. Keep the tx size under 150 kB limit. + def create_sized_block(self, block, spend, block_size): + num_of_txes = int(block_size // 150000) + 1 + block.vtx[0].vout[0].nValue += spend.tx.vout[spend.n].nValue - num_of_txes + block.vtx[0].rehash() + tx1 = CTransaction() + tx1.vin.append(CTxIn(COutPoint(spend.tx.sha256, spend.n), b"", 0xffffffff)) + tx1.vout = [CTxOut(1, CScript([OP_TRUE]))] * num_of_txes + tx1.calc_sha256() + self.sign_tx(tx1, spend.tx, spend.n) + self.add_transactions_to_block(block, [tx1]) + + available_space = block_size - len(block.serialize()) + idx = 0 + while available_space > 138 and idx < num_of_txes: + tx = CTransaction() + if 149999 < available_space: + script_length = 149999 - 141 + else: + script_length = available_space - 138 + script_output = CScript([b'\x00' * script_length]) + tx.vout.append(CTxOut(0, script_output)) + tx.vin.append(CTxIn(COutPoint(block.vtx[1].sha256, idx))) + tx.calc_sha256() + self.sign_tx(tx, spend.tx, spend.n) + self.add_transactions_to_block(block, [tx]) + available_space = block_size - len(block.serialize()) + idx += 1 + block.hashMerkleRoot = block.calc_merkle_root() + block.solve() + # Make sure the math above worked out to produce a block_size-sized block + assert len(block.serialize()) in [block_size - 1, block_size] + return block if __name__ == '__main__': - FullBlockTest().main() + FullBlockTest().main() \ No newline at end of file diff --git a/test/functional/p2p_invalid_block.py b/test/functional/p2p_invalid_block.py index 6fda64c59c2f..6aef35f0e35b 100755 --- a/test/functional/p2p_invalid_block.py +++ b/test/functional/p2p_invalid_block.py @@ -15,6 +15,10 @@ from test_framework.blocktools import create_block, create_coinbase, create_transaction from test_framework.messages import COIN from test_framework.mininode import network_thread_start, P2PDataStore +from test_framework.script import ( + CScript, + OP_TRUE, +) from test_framework.test_framework import PivxTestFramework from test_framework.util import assert_equal @@ -24,6 +28,9 @@ def set_test_params(self): self.setup_clean_chain = True self.extra_args = [["-whitelist=127.0.0.1"]] + def create_tx(self, spend_tx, n, value): + return create_transaction(spend_tx, n, b"", value, CScript([OP_TRUE])) + def run_test(self): # Add p2p connection to node0 node = self.nodes[0] # convenience reference to the node @@ -65,9 +72,8 @@ def run_test(self): block2 = create_block(tip, create_coinbase(height), block_time) block_time += 1 - # b'0x51' is OP_TRUE - tx1 = create_transaction(block1.vtx[0], 0, b'\x51', 50 * COIN) - tx2 = create_transaction(tx1, 0, b'\x51', 50 * COIN) + tx1 = self.create_tx(block1.vtx[0], 0, 50 * COIN) + tx2 = self.create_tx(tx1, 0, 50 * COIN) block2.vtx.extend([tx1, tx2]) block2.hashMerkleRoot = block2.calc_merkle_root() @@ -123,7 +129,7 @@ def run_test(self): # Complete testing of CVE-2018-17144, by checking for the inflation bug. # Create a block that spends the output of a tx in a previous block. block4 = create_block(tip, create_coinbase(height), block_time) - tx3 = create_transaction(tx2, 0, b'\x51', 50 * COIN) + tx3 = self.create_tx(tx2, 0, 50 * COIN) # Duplicates input tx3.vin.append(tx3.vin[0]) @@ -137,7 +143,7 @@ def run_test(self): self.log.info("Test output value > input value out of range") # Can be removed when 'feature_block.py' is added to the suite. - tx4 = create_transaction(tx2, 0, b'\x51', 260 * COIN) + tx4 = self.create_tx(tx2, 0, 260 * COIN) block4 = create_block(tip, create_coinbase(height), block_time) block4.vtx.extend([tx4]) block4.hashMerkleRoot = block4.calc_merkle_root() diff --git a/test/functional/p2p_invalid_tx.py b/test/functional/p2p_invalid_tx.py index 3fcdc9473ffd..bb15f25d82d5 100755 --- a/test/functional/p2p_invalid_tx.py +++ b/test/functional/p2p_invalid_tx.py @@ -14,10 +14,14 @@ CTxOut, ) from test_framework.mininode import network_thread_start, P2PDataStore, network_thread_join +from test_framework.script import ( + CScript, + OP_NOTIF, + OP_TRUE, +) from test_framework.test_framework import PivxTestFramework from test_framework.util import ( assert_equal, - wait_until, ) @@ -45,6 +49,18 @@ def reconnect_p2p(self, **kwargs): network_thread_join() self.bootstrap_p2p(**kwargs) + def new_spend_tx(self, prev_hash, prev_n, values): + """Create a CTransaction spending COutPoint(prev_hash, prev_n) + + each amount specified in the 'values' list is sent to an + anyone-can-spend script""" + tx = CTransaction() + tx.vin.append(CTxIn(outpoint=COutPoint(prev_hash, prev_n))) + for value in values: + tx.vout.append(CTxOut(nValue=value, scriptPubKey=CScript([OP_TRUE]))) + tx.calc_sha256() + return tx + def run_test(self): node = self.nodes[0] # convenience reference to the node @@ -67,11 +83,10 @@ def run_test(self): self.log.info("Mature the block.") self.nodes[0].generate(100) - # b'\x64' is OP_NOTIF # Transaction will be rejected with code 16 (REJECT_INVALID) # and we get disconnected immediately self.log.info('Test a transaction that is rejected') - tx1 = create_transaction(block1.vtx[0], 0, b'\x64', 50 * COIN - 12000) + tx1 = create_transaction(block1.vtx[0], 0, CScript([OP_NOTIF]), 50 * COIN - 12000) node.p2p.send_txs_and_test([tx1], node, success=False, expect_disconnect=True) # Make two p2p connections to provide the node with orphans @@ -82,32 +97,19 @@ def run_test(self): self.log.info('Test orphan transaction handling ... ') # Create a root transaction that we withhold until all dependend transactions # are sent out and in the orphan cache - tx_withhold = CTransaction() - tx_withhold.vin.append(CTxIn(outpoint=COutPoint(block1.vtx[0].sha256, 0))) - tx_withhold.vout.append(CTxOut(nValue=50 * COIN - 12000, scriptPubKey=b'\x51')) - tx_withhold.calc_sha256() + tx_withhold = self.new_spend_tx(block1.vtx[0].sha256, 0, [50 * COIN - 12000]) - # Our first orphan tx with some outputs to create further orphan txs - tx_orphan_1 = CTransaction() - tx_orphan_1.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 0))) - tx_orphan_1.vout = [CTxOut(nValue=10 * COIN, scriptPubKey=b'\x51')] * 3 - tx_orphan_1.calc_sha256() + # Our first orphan tx with 3 outputs to create further orphan txs + tx_orphan_1 = self.new_spend_tx(tx_withhold.sha256, 0, [10 * COIN] * 3) # A valid transaction with low fee - tx_orphan_2_no_fee = CTransaction() - tx_orphan_2_no_fee.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 0))) - tx_orphan_2_no_fee.vout.append(CTxOut(nValue=10 * COIN, scriptPubKey=b'\x51')) + tx_orphan_2_no_fee = self.new_spend_tx(tx_orphan_1.sha256, 0, [10 * COIN]) # A valid transaction with sufficient fee - tx_orphan_2_valid = CTransaction() - tx_orphan_2_valid.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 1))) - tx_orphan_2_valid.vout.append(CTxOut(nValue=10 * COIN - 12000, scriptPubKey=b'\x51')) - tx_orphan_2_valid.calc_sha256() + tx_orphan_2_valid = self.new_spend_tx(tx_orphan_1.sha256, 1, [10 * COIN - 12000]) # An invalid transaction with negative fee - tx_orphan_2_invalid = CTransaction() - tx_orphan_2_invalid.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 2))) - tx_orphan_2_invalid.vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=b'\x51')) + tx_orphan_2_invalid = self.new_spend_tx(tx_orphan_1.sha256, 2, [11 * COIN]) self.log.info('Send the orphans ... ') # Send valid orphan txs from p2ps[0] diff --git a/test/functional/sapling_wallet.py b/test/functional/sapling_wallet.py index 9b05430cc947..d57f4a86d656 100755 --- a/test/functional/sapling_wallet.py +++ b/test/functional/sapling_wallet.py @@ -113,7 +113,7 @@ def run_test(self): self.log.info("Good. Not accepted when SPORK_20 is active.") # Try with RPC... - assert_raises_rpc_error(-8, "Invalid parameter, Sapling not active yet", + assert_raises_rpc_error(-8, "SHIELD in maintenance (SPORK 20)", self.nodes[0].shieldsendmany, "from_transparent", recipients, 1, fee) # Disable SPORK_20 and retry diff --git a/test/functional/test_framework/blockstore.py b/test/functional/test_framework/blockstore.py deleted file mode 100644 index 592e99fb0d15..000000000000 --- a/test/functional/test_framework/blockstore.py +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2015-2016 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""BlockStore and TxStore helper classes.""" - -from .mininode import * -from io import BytesIO -import dbm.dumb as dbmd - -logger = logging.getLogger("TestFramework.blockstore") - -class BlockStore(): - """BlockStore helper class. - - BlockStore keeps a map of blocks and implements helper functions for - responding to getheaders and getdata, and for constructing a getheaders - message. - """ - - def __init__(self, datadir): - self.blockDB = dbmd.open(datadir + "/blocks", 'c') - self.currentBlock = 0 - self.headers_map = dict() - - def close(self): - self.blockDB.close() - - def erase(self, blockhash): - del self.blockDB[repr(blockhash)] - - # lookup an entry and return the item as raw bytes - def get(self, blockhash): - value = None - try: - value = self.blockDB[repr(blockhash)] - except KeyError: - return None - return value - - # lookup an entry and return it as a CBlock - def get_block(self, blockhash): - ret = None - serialized_block = self.get(blockhash) - if serialized_block is not None: - f = BytesIO(serialized_block) - ret = CBlock() - ret.deserialize(f) - ret.calc_sha256() - return ret - - def get_header(self, blockhash): - try: - return self.headers_map[blockhash] - except KeyError: - return None - - # Note: this pulls full blocks out of the database just to retrieve - # the headers -- perhaps we could keep a separate data structure - # to avoid this overhead. - def headers_for(self, locator, hash_stop, current_tip=None): - if current_tip is None: - current_tip = self.currentBlock - current_block_header = self.get_header(current_tip) - if current_block_header is None: - return None - - response = msg_headers() - headersList = [ current_block_header ] - maxheaders = 2000 - while (headersList[0].sha256 not in locator.vHave): - prevBlockHash = headersList[0].hashPrevBlock - prevBlockHeader = self.get_header(prevBlockHash) - if prevBlockHeader is not None: - headersList.insert(0, prevBlockHeader) - else: - break - headersList = headersList[:maxheaders] # truncate if we have too many - hashList = [x.sha256 for x in headersList] - index = len(headersList) - if (hash_stop in hashList): - index = hashList.index(hash_stop)+1 - response.headers = headersList[:index] - return response - - def add_block(self, block): - block.calc_sha256() - try: - self.blockDB[repr(block.sha256)] = bytes(block.serialize()) - except TypeError as e: - logger.exception("Unexpected error") - self.currentBlock = block.sha256 - self.headers_map[block.sha256] = CBlockHeader(block) - - def add_header(self, header): - self.headers_map[header.sha256] = header - - # lookup the hashes in "inv", and return p2p messages for delivering - # blocks found. - def get_blocks(self, inv): - responses = [] - for i in inv: - if (i.type == 2): # MSG_BLOCK - data = self.get(i.hash) - if data is not None: - # Use msg_generic to avoid re-serialization - responses.append(msg_generic(b"block", data)) - return responses - - def get_locator(self, current_tip=None): - if current_tip is None: - current_tip = self.currentBlock - r = [] - counter = 0 - step = 1 - lastBlock = self.get_block(current_tip) - while lastBlock is not None: - r.append(lastBlock.hashPrevBlock) - for i in range(step): - lastBlock = self.get_block(lastBlock.hashPrevBlock) - if lastBlock is None: - break - counter += 1 - if counter > 10: - step *= 2 - locator = CBlockLocator() - locator.vHave = r - return locator - -class TxStore(): - def __init__(self, datadir): - self.txDB = dbmd.open(datadir + "/transactions", 'c') - - def close(self): - self.txDB.close() - - # lookup an entry and return the item as raw bytes - def get(self, txhash): - value = None - try: - value = self.txDB[repr(txhash)] - except KeyError: - return None - return value - - def get_transaction(self, txhash): - ret = None - serialized_tx = self.get(txhash) - if serialized_tx is not None: - f = BytesIO(serialized_tx) - ret = CTransaction() - ret.deserialize(f) - ret.calc_sha256() - return ret - - def add_transaction(self, tx): - tx.calc_sha256() - try: - self.txDB[repr(tx.sha256)] = bytes(tx.serialize()) - except TypeError as e: - logger.exception("Unexpected error") - - def get_transactions(self, inv): - responses = [] - for i in inv: - if (i.type == 1): # MSG_TX - tx = self.get(i.hash) - if tx is not None: - responses.append(msg_generic(b"tx", tx)) - return responses diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py index 61980e8c015f..92591bf05f1f 100644 --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -21,7 +21,7 @@ def create_block(hashprev, coinbase, nTime=None, nVersion=None, hashFinalSapling if hashFinalSaplingRoot is not None: block.hashFinalSaplingRoot = hashFinalSaplingRoot block.hashPrevBlock = hashprev - block.nBits = 0x1e0ffff0 # Will break after a difficulty adjustment... + block.nBits = 0x207fffff # difficulty retargeting is disabled in REGTEST chainparams block.vtx.append(coinbase) block.hashMerkleRoot = block.calc_merkle_root() block.calc_sha256() diff --git a/test/functional/test_framework/comptool.py b/test/functional/test_framework/comptool.py deleted file mode 100755 index 2c1e44cf7803..000000000000 --- a/test/functional/test_framework/comptool.py +++ /dev/null @@ -1,397 +0,0 @@ -#!/usr/bin/env python3 -# Copyright (c) 2015-2016 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -"""Compare two or more pivxds to each other. - -To use, create a class that implements get_tests(), and pass it in -as the test generator to TestManager. get_tests() should be a python -generator that returns TestInstance objects. See below for definition. - -TestNode behaves as follows: - Configure with a BlockStore and TxStore - on_inv: log the message but don't request - on_headers: log the chain tip - on_pong: update ping response map (for synchronization) - on_getheaders: provide headers via BlockStore - on_getdata: provide blocks via BlockStore -""" - -from .mininode import * -from .blockstore import BlockStore, TxStore -from .util import p2p_port, wait_until - -import logging - -logger=logging.getLogger("TestFramework.comptool") - -global mininode_lock - -class RejectResult(): - """Outcome that expects rejection of a transaction or block.""" - def __init__(self, code, reason=b''): - self.code = code - self.reason = reason - def match(self, other): - if self.code != other.code: - return False - return other.reason.startswith(self.reason) - def __repr__(self): - return '%i:%s' % (self.code,self.reason or '*') - -class TestNode(P2PInterface): - - def __init__(self, block_store, tx_store): - super().__init__() - self.bestblockhash = None - self.block_store = block_store - self.block_request_map = {} - self.tx_store = tx_store - self.tx_request_map = {} - self.block_reject_map = {} - self.tx_reject_map = {} - - # When the pingmap is non-empty we're waiting for - # a response - self.pingMap = {} - self.lastInv = [] - self.closed = False - - def on_close(self): - self.closed = True - - def on_headers(self, message): - if len(message.headers) > 0: - best_header = message.headers[-1] - best_header.calc_sha256() - self.bestblockhash = best_header.sha256 - - def on_getheaders(self, message): - response = self.block_store.headers_for(message.locator, message.hashstop) - if response is not None: - self.send_message(response) - - def on_getdata(self, message): - [self.send_message(r) for r in self.block_store.get_blocks(message.inv)] - [self.send_message(r) for r in self.tx_store.get_transactions(message.inv)] - - for i in message.inv: - if i.type == 1 or i.type == 1 | (1 << 30): # MSG_TX or MSG_WITNESS_TX - self.tx_request_map[i.hash] = True - elif i.type == 2 or i.type == 2 | (1 << 30): # MSG_BLOCK or MSG_WITNESS_BLOCK - self.block_request_map[i.hash] = True - - def on_inv(self, message): - self.lastInv = [x.hash for x in message.inv] - - def on_pong(self, message): - try: - del self.pingMap[message.nonce] - except KeyError: - raise AssertionError("Got pong for unknown ping [%s]" % repr(message)) - - def on_reject(self, message): - if message.message == b'tx': - self.tx_reject_map[message.data] = RejectResult(message.code, message.reason) - if message.message == b'block': - self.block_reject_map[message.data] = RejectResult(message.code, message.reason) - - def send_inv(self, obj): - mtype = 2 if isinstance(obj, CBlock) else 1 - self.send_message(msg_inv([CInv(mtype, obj.sha256)])) - - def send_getheaders(self): - # We ask for headers from their last tip. - m = msg_getheaders() - m.locator = self.block_store.get_locator(self.bestblockhash) - self.send_message(m) - - def send_header(self, header): - m = msg_headers() - m.headers.append(header) - self.send_message(m) - - # This assumes BIP31 - def send_ping(self, nonce): - self.pingMap[nonce] = True - self.send_message(msg_ping(nonce)) - - def received_ping_response(self, nonce): - return nonce not in self.pingMap - - def send_mempool(self): - self.lastInv = [] - self.send_message(msg_mempool()) - -# TestInstance: -# -# Instances of these are generated by the test generator, and fed into the -# comptool. -# -# "blocks_and_transactions" should be an array of -# [obj, True/False/None, hash/None]: -# - obj is either a CBlock, CBlockHeader, or a CTransaction, and -# - the second value indicates whether the object should be accepted -# into the blockchain or mempool (for tests where we expect a certain -# answer), or "None" if we don't expect a certain answer and are just -# comparing the behavior of the nodes being tested. -# - the third value is the hash to test the tip against (if None or omitted, -# use the hash of the block) -# - NOTE: if a block header, no test is performed; instead the header is -# just added to the block_store. This is to facilitate block delivery -# when communicating with headers-first clients (when withholding an -# intermediate block). -# sync_every_block: if True, then each block will be inv'ed, synced, and -# nodes will be tested based on the outcome for the block. If False, -# then inv's accumulate until all blocks are processed (or max inv size -# is reached) and then sent out in one inv message. Then the final block -# will be synced across all connections, and the outcome of the final -# block will be tested. -# sync_every_tx: analogous to behavior for sync_every_block, except if outcome -# on the final tx is None, then contents of entire mempool are compared -# across all connections. (If outcome of final tx is specified as true -# or false, then only the last tx is tested against outcome.) - -class TestInstance(): - def __init__(self, objects=None, sync_every_block=True, sync_every_tx=False): - self.blocks_and_transactions = objects if objects else [] - self.sync_every_block = sync_every_block - self.sync_every_tx = sync_every_tx - -class TestManager(): - - def __init__(self, testgen, datadir): - self.test_generator = testgen - self.p2p_connections= [] - self.block_store = BlockStore(datadir) - self.tx_store = TxStore(datadir) - self.ping_counter = 1 - - def add_all_connections(self, nodes): - for i in range(len(nodes)): - # Create a p2p connection to each node - node = TestNode(self.block_store, self.tx_store) - node.peer_connect('127.0.0.1', p2p_port(i)) - self.p2p_connections.append(node) - - def clear_all_connections(self): - self.p2p_connections = [] - - def wait_for_disconnections(self): - def disconnected(): - return all(node.closed for node in self.p2p_connections) - wait_until(disconnected, timeout=10, lock=mininode_lock) - - def wait_for_verack(self): - return all(node.wait_for_verack() for node in self.p2p_connections) - - def wait_for_pings(self, counter): - def received_pongs(): - return all(node.received_ping_response(counter) for node in self.p2p_connections) - wait_until(received_pongs, lock=mininode_lock) - - # sync_blocks: Wait for all connections to request the blockhash given - # then send get_headers to find out the tip of each node, and synchronize - # the response by using a ping (and waiting for pong with same nonce). - def sync_blocks(self, blockhash, num_blocks): - def blocks_requested(): - return all( - blockhash in node.block_request_map and node.block_request_map[blockhash] - for node in self.p2p_connections - ) - - # --> error if not requested - wait_until(blocks_requested, attempts=20*num_blocks, lock=mininode_lock) - - # Send getheaders message - [ c.send_getheaders() for c in self.p2p_connections ] - - # Send ping and wait for response -- synchronization hack - [ c.send_ping(self.ping_counter) for c in self.p2p_connections ] - self.wait_for_pings(self.ping_counter) - self.ping_counter += 1 - - # Analogous to sync_block (see above) - def sync_transaction(self, txhash, num_events): - # Wait for nodes to request transaction (50ms sleep * 20 tries * num_events) - def transaction_requested(): - return all( - txhash in node.tx_request_map and node.tx_request_map[txhash] - for node in self.p2p_connections - ) - - # --> error if not requested - wait_until(transaction_requested, attempts=20*num_events, lock=mininode_lock) - - # Get the mempool - [ c.send_mempool() for c in self.p2p_connections ] - - # Send ping and wait for response -- synchronization hack - [ c.send_ping(self.ping_counter) for c in self.p2p_connections ] - self.wait_for_pings(self.ping_counter) - self.ping_counter += 1 - - # Sort inv responses from each node - with mininode_lock: - [ c.lastInv.sort() for c in self.p2p_connections ] - - # Verify that the tip of each connection all agree with each other, and - # with the expected outcome (if given) - def check_results(self, blockhash, outcome): - with mininode_lock: - for c in self.p2p_connections: - if outcome is None: - if c.bestblockhash != self.p2p_connections[0].bestblockhash: - return False - elif isinstance(outcome, RejectResult): # Check that block was rejected w/ code - if c.bestblockhash == blockhash: - return False - if blockhash not in c.block_reject_map: - logger.error('Block not in reject map: %064x' % (blockhash)) - return False - if not outcome.match(c.block_reject_map[blockhash]): - logger.error('Block rejected with %s instead of expected %s: %064x' % (c.block_reject_map[blockhash], outcome, blockhash)) - return False - elif ((c.bestblockhash == blockhash) != outcome): - return False - return True - - # Either check that the mempools all agree with each other, or that - # txhash's presence in the mempool matches the outcome specified. - # This is somewhat of a strange comparison, in that we're either comparing - # a particular tx to an outcome, or the entire mempools altogether; - # perhaps it would be useful to add the ability to check explicitly that - # a particular tx's existence in the mempool is the same across all nodes. - def check_mempool(self, txhash, outcome): - with mininode_lock: - for c in self.p2p_connections: - if outcome is None: - # Make sure the mempools agree with each other - if c.lastInv != self.p2p_connections[0].lastInv: - return False - elif isinstance(outcome, RejectResult): # Check that tx was rejected w/ code - if txhash in c.lastInv: - return False - if txhash not in c.tx_reject_map: - logger.error('Tx not in reject map: %064x' % (txhash)) - return False - if not outcome.match(c.tx_reject_map[txhash]): - logger.error('Tx rejected with %s instead of expected %s: %064x' % (c.tx_reject_map[txhash], outcome, txhash)) - return False - elif ((txhash in c.lastInv) != outcome): - return False - return True - - def run(self): - # Wait until verack is received - self.wait_for_verack() - - test_number = 0 - tests = self.test_generator.get_tests() - for test_instance in tests: - test_number += 1 - logger.info("Running test %d: %s line %s" % (test_number, tests.gi_code.co_filename, tests.gi_frame.f_lineno)) - # We use these variables to keep track of the last block - # and last transaction in the tests, which are used - # if we're not syncing on every block or every tx. - [ block, block_outcome, tip ] = [ None, None, None ] - [ tx, tx_outcome ] = [ None, None ] - invqueue = [] - - for test_obj in test_instance.blocks_and_transactions: - b_or_t = test_obj[0] - outcome = test_obj[1] - # Determine if we're dealing with a block or tx - if isinstance(b_or_t, CBlock): # Block test runner - block = b_or_t - block_outcome = outcome - tip = block.sha256 - # each test_obj can have an optional third argument - # to specify the tip we should compare with - # (default is to use the block being tested) - if len(test_obj) >= 3: - tip = test_obj[2] - - # Add to shared block_store, set as current block - # If there was an open getdata request for the block - # previously, and we didn't have an entry in the - # block_store, then immediately deliver, because the - # node wouldn't send another getdata request while - # the earlier one is outstanding. - first_block_with_hash = True - if self.block_store.get(block.sha256) is not None: - first_block_with_hash = False - with mininode_lock: - self.block_store.add_block(block) - for c in self.p2p_connections: - if first_block_with_hash and block.sha256 in c.block_request_map and c.block_request_map[block.sha256] == True: - # There was a previous request for this block hash - # Most likely, we delivered a header for this block - # but never had the block to respond to the getdata - c.send_message(msg_block(block)) - else: - c.block_request_map[block.sha256] = False - # Either send inv's to each node and sync, or add - # to invqueue for later inv'ing. - if (test_instance.sync_every_block): - # if we expect success, send inv and sync every block - # if we expect failure, just push the block and see what happens. - if outcome == True: - [ c.send_inv(block) for c in self.p2p_connections ] - self.sync_blocks(block.sha256, 1) - else: - [ c.send_message(msg_block(block)) for c in self.p2p_connections ] - [ c.send_ping(self.ping_counter) for c in self.p2p_connections ] - self.wait_for_pings(self.ping_counter) - self.ping_counter += 1 - if (not self.check_results(tip, outcome)): - raise AssertionError("Test failed at test %d" % test_number) - else: - invqueue.append(CInv(2, block.sha256)) - elif isinstance(b_or_t, CBlockHeader): - block_header = b_or_t - self.block_store.add_header(block_header) - [ c.send_header(block_header) for c in self.p2p_connections ] - - else: # Tx test runner - assert(isinstance(b_or_t, CTransaction)) - tx = b_or_t - tx_outcome = outcome - # Add to shared tx store and clear map entry - with mininode_lock: - self.tx_store.add_transaction(tx) - for c in self.p2p_connections: - c.tx_request_map[tx.sha256] = False - # Again, either inv to all nodes or save for later - if (test_instance.sync_every_tx): - [ c.send_inv(tx) for c in self.p2p_connections ] - self.sync_transaction(tx.sha256, 1) - if (not self.check_mempool(tx.sha256, outcome)): - raise AssertionError("Test failed at test %d" % test_number) - else: - invqueue.append(CInv(1, tx.sha256)) - # Ensure we're not overflowing the inv queue - if len(invqueue) == MAX_INV_SZ: - [ c.send_message(msg_inv(invqueue)) for c in self.p2p_connections ] - invqueue = [] - - # Do final sync if we weren't syncing on every block or every tx. - if (not test_instance.sync_every_block and block is not None): - if len(invqueue) > 0: - [ c.send_message(msg_inv(invqueue)) for c in self.p2p_connections ] - invqueue = [] - self.sync_blocks(block.sha256, len(test_instance.blocks_and_transactions)) - if (not self.check_results(tip, block_outcome)): - raise AssertionError("Block test failed at test %d" % test_number) - if (not test_instance.sync_every_tx and tx is not None): - if len(invqueue) > 0: - [ c.send_message(msg_inv(invqueue)) for c in self.p2p_connections ] - invqueue = [] - self.sync_transaction(tx.sha256, len(test_instance.blocks_and_transactions)) - if (not self.check_mempool(tx.sha256, tx_outcome)): - raise AssertionError("Mempool test failed at test %d" % test_number) - - [ c.disconnect_node() for c in self.p2p_connections ] - self.wait_for_disconnections() - self.block_store.close() - self.tx_store.close() diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py index 0796d09344f8..ffd5d42bfe4a 100644 --- a/test/functional/test_framework/messages.py +++ b/test/functional/test_framework/messages.py @@ -32,8 +32,8 @@ MY_RELAY = 1 # from version 70001 onwards, fRelay should be appended to version messages (BIP37) MAX_INV_SZ = 50000 -MAX_BLOCK_BASE_SIZE = 1000000 -CURRENT_BLK_VERSION = 7 +MAX_BLOCK_BASE_SIZE = 2000000 +CURRENT_BLK_VERSION = 9 COIN = 100000000 # 1 btc in satoshis diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py index 0659811aba72..160be8fc5431 100755 --- a/test/functional/test_framework/mininode.py +++ b/test/functional/test_framework/mininode.py @@ -482,6 +482,17 @@ def on_getdata(self, message): else: logger.debug('getdata message type {} received.'.format(hex(inv.type))) + def on_getblocks(self, message): + """Check for blocks in our stores, reply with inv messages.""" + msg_blocks = [] + blockhashes = self.block_store.keys() + blockhash = message.hashstop + while blockhash in blockhashes and blockhash not in message.locator.vHave: + msg_blocks.append(msg_block(self.block_store[blockhash])) + blockhash = self.block_store[blockhash].hashPrevBlock + for msg in reversed(msg_blocks): + self.send_message(msg) + def send_blocks_and_test(self, blocks, node, success=True, reject_reason=None, expect_disconnect=False, timeout=60): """Send blocks to test node and test whether the tip advances. diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index dc1135588dd9..64d362bd6112 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -1108,36 +1108,6 @@ def setupMasternode(self, # return the collateral id return collateralTxId -### ------------------------------------------------------ - -class ComparisonTestFramework(PivxTestFramework): - """Test framework for doing p2p comparison testing - - Sets up some pivxd binaries: - - 1 binary: test binary - - 2 binaries: 1 test binary, 1 ref binary - - n>2 binaries: 1 test binary, n-1 ref binaries""" - - def set_test_params(self): - self.num_nodes = 2 - self.setup_clean_chain = True - - def add_options(self, parser): - parser.add_option("--testbinary", dest="testbinary", - default=os.getenv("BITCOIND", "pivxd"), - help="pivxd binary to test") - parser.add_option("--refbinary", dest="refbinary", - default=os.getenv("BITCOIND", "pivxd"), - help="pivxd binary to use for reference nodes (if any)") - - def setup_network(self): - extra_args = [['-whitelist=127.0.0.1']] * self.num_nodes - if hasattr(self, "extra_args"): - extra_args = self.extra_args - self.add_nodes(self.num_nodes, extra_args, - binary=[self.options.testbinary] + - [self.options.refbinary] * (self.num_nodes - 1)) - self.start_nodes() class SkipTest(Exception): """This exception is raised to skip a test""" diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 9fbbfea92d7b..a52a68bba0c9 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -223,7 +223,7 @@ def assert_debug_log(self, expected_msgs): print_log = " - " + "\n - ".join(log.splitlines()) for expected_msg in expected_msgs: if re.search(re.escape(expected_msg), log, flags=re.MULTILINE) is None: - self._raise_assertion_error('Expected message "{}" does not partially match log:\n\n{}\n\n'.format(expected_msg, print_log)) + raise AssertionError('Expected message "{}" does not partially match log:\n\n{}\n\n'.format(expected_msg, print_log)) def node_encrypt_wallet(self, passphrase): """"Encrypts the wallet. diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index c6f1681872dc..7ba290508061 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -77,6 +77,7 @@ 'rpc_spork.py', # ~ 144 sec 'wallet_txn_doublespend.py --mineblock', # ~ 143 sec 'wallet_txn_clone.py --mineblock', # ~ 143 sec + 'feature_block.py', # ~ 140 sec 'feature_proxy.py', # ~ 138 sec 'rpc_rawtransaction.py', # ~ 134 sec 'mining_pos_reorg.py', # ~ 128 sec @@ -125,7 +126,6 @@ # Don't append tests at the end to avoid merge conflicts # Put them in a random line within the section that fits their approximate run-time - # 'feature_block.py', # 'mempool_limit.py', # We currently don't limit our mempool_reorg # 'rpc_getchaintips.py', # 'mining_prioritisetransaction.py', @@ -182,6 +182,7 @@ LEGACY_SKIP_TESTS = [ # These tests are not run when the flag --legacywallet is used + 'feature_block.py', 'feature_blockindexstats.py', 'feature_config_args.py', 'feature_help.py',