From 33a4d8355832858872aae0e86d0c5a48bc7b8ed4 Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Thu, 31 Aug 2023 14:46:59 +0200 Subject: [PATCH] Unit tests --- src/sapling/transaction_builder.cpp | 11 +- src/sapling/transaction_builder.h | 3 + src/test/evo_deterministicmns_tests.cpp | 373 +++++++++++++++++++++- src/wallet/test/pos_test_fixture.cpp | 86 +++++ src/wallet/test/pos_test_fixture.h | 9 + src/wallet/test/pos_validations_tests.cpp | 54 ---- 6 files changed, 468 insertions(+), 68 deletions(-) diff --git a/src/sapling/transaction_builder.cpp b/src/sapling/transaction_builder.cpp index 09ba15c20a638c..113256e929e32f 100644 --- a/src/sapling/transaction_builder.cpp +++ b/src/sapling/transaction_builder.cpp @@ -5,10 +5,11 @@ #include "sapling/transaction_builder.h" -#include "script/sign.h" -#include "utilmoneystr.h" #include "consensus/upgrades.h" #include "policy/policy.h" +#include "primitives/transaction.h" +#include "script/sign.h" +#include "utilmoneystr.h" #include "validation.h" #include @@ -447,3 +448,9 @@ TransactionBuilderResult TransactionBuilder::Build(bool fDummySig) return fDummySig ? AddDummySignatures() : ProveAndSign(); } + +// WARNING: This function must be used only for testing +TransactionBuilderResult TransactionBuilder::BuildWithoutConstraints() +{ + return ProveAndSign(); +} diff --git a/src/sapling/transaction_builder.h b/src/sapling/transaction_builder.h index 6de366cc4eb130..62d8b9601461d6 100644 --- a/src/sapling/transaction_builder.h +++ b/src/sapling/transaction_builder.h @@ -137,6 +137,9 @@ class TransactionBuilder void SendChangeTo(const CTxDestination& changeAddr); + // WARNING: This function must be used only for testing + TransactionBuilderResult BuildWithoutConstraints(); + TransactionBuilderResult Build(bool fDummySig = false); // Add Sapling Spend/Output descriptions, binding sig, and transparent signatures TransactionBuilderResult ProveAndSign(); diff --git a/src/test/evo_deterministicmns_tests.cpp b/src/test/evo_deterministicmns_tests.cpp index 13083f936deb09..31c49242fbaa5e 100644 --- a/src/test/evo_deterministicmns_tests.cpp +++ b/src/test/evo_deterministicmns_tests.cpp @@ -3,13 +3,18 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include "test/test_pivx.h" + +#include "chainparams.h" +#include "consensus/validation.h" +#include "sapling/transaction_builder.h" +#include "sync.h" +#include "wallet/test/pos_test_fixture.h" #include "blockassembler.h" #include "consensus/merkle.h" #include "consensus/params.h" -#include "evo/specialtx_validation.h" #include "evo/deterministicmns.h" +#include "evo/specialtx_validation.h" #include "llmq/quorums_blockprocessor.h" #include "llmq/quorums_commitment.h" #include "llmq/quorums_utils.h" @@ -28,6 +33,14 @@ #include typedef std::map> SimpleUTXOMap; +class CWallet; + +enum MasternodeType { + EMPTY, + TRANSPARENT, + SHIELD, + BOTH +}; // static 0.1 PIV fee used for the special txes in these tests static const CAmount fee = 10000000; @@ -135,18 +148,65 @@ static CBLSSecretKey GetRandomBLSKey() return sk; } +// Create a good/fake ProRegShieldProof +static bool CreateProRegShieldProof(ProRegPL& pl, const SaplingNoteEntry& note, CWallet* pwallet, CAmount amount, bool useRogueBuilder, bool emptySig) +{ + TransactionBuilder txBuilder(Params().GetConsensus(), pwallet); + txBuilder.SetFee(0); + libzcash::SaplingExtendedSpendingKey sk; + if (!pwallet->GetSaplingExtendedSpendingKey(note.address, sk)) { + return false; + } + uint256 anchor; + std::vector> witnesses; + std::vector noteop; + noteop.emplace_back(note.op); + pwallet->GetSaplingScriptPubKeyMan()->GetSaplingNoteWitnesses(noteop, witnesses, anchor); + txBuilder.AddSaplingSpend(sk.expsk, note.note, anchor, witnesses[0].get()); + auto newAddress = pwallet->getNewAddress(""); + if (!newAddress.getRes()) { + return false; + } + txBuilder.AddTransparentOutput(*newAddress.getObjResult(), amount); + auto txTrial = useRogueBuilder ? txBuilder.BuildWithoutConstraints() : txBuilder.Build(); + if (txTrial.IsError()) { + return false; + } + auto txFinal = *txTrial.GetTx(); + pl.shieldCollateral.input = (*txFinal.sapData).vShieldedSpend[0]; + pl.shieldCollateral.output = txFinal.vout[0]; + if (!emptySig) copy(begin((*txFinal.sapData).bindingSig), end((*txFinal.sapData).bindingSig), back_inserter(pl.shieldCollateral.bindingSig)); + return true; +} + // Creates a ProRegTx. // - if optCollateralOut is nullopt, generate a new collateral in the first output of the tx // - otherwise reference *optCollateralOut as external collateral static CMutableTransaction CreateProRegTx(Optional optCollateralOut, - SimpleUTXOMap& utxos, int port, const CScript& scriptPayout, const CKey& coinbaseKey, - const CKey& ownerKey, - const CBLSPublicKey& operatorPubKey, - uint16_t operatorReward = 0, - bool fInvalidCollateral = false) + SimpleUTXOMap& utxos, + int port, + const CScript& scriptPayout, + const CKey& coinbaseKey, + const CKey& ownerKey, + const CBLSPublicKey& operatorPubKey, + uint16_t operatorReward = 0, + bool fInvalidCollateral = false, + MasternodeType masternodeType = TRANSPARENT, + const SaplingNoteEntry* note = nullptr, + CWallet* pwallet = nullptr, + CAmount amount = Params().GetConsensus().nMNCollateralAmt, + bool useRogueBuilder = true, + bool emptySig = false) { ProRegPL pl; - pl.collateralOutpoint = (optCollateralOut ? *optCollateralOut : COutPoint(UINT256_ZERO, 0)); + if (masternodeType == TRANSPARENT) { + pl.collateralOutpoint = (optCollateralOut ? *optCollateralOut : COutPoint(UINT256_ZERO, 0)); + } else if (masternodeType == SHIELD) { + BOOST_CHECK(CreateProRegShieldProof(pl, *note, pwallet, amount, useRogueBuilder, emptySig)); + } else if (masternodeType == BOTH) { + pl.collateralOutpoint = (optCollateralOut ? *optCollateralOut : COutPoint(UINT256_ZERO, 0)); + BOOST_CHECK(CreateProRegShieldProof(pl, *note, pwallet, amount, useRogueBuilder, emptySig)); + } pl.addr = LookupNumeric("1.1.1.1", port); pl.keyIDOwner = ownerKey.GetPubKey().GetID(); pl.pubKeyOperator = operatorPubKey; @@ -157,10 +217,15 @@ static CMutableTransaction CreateProRegTx(Optional optCollateralOut, CMutableTransaction tx; tx.nVersion = CTransaction::TxVersion::SAPLING; tx.nType = CTransaction::TxType::PROREG; - FundTransaction(tx, utxos, scriptPayout, - GetScriptForDestination(coinbaseKey.GetPubKey().GetID()), - (optCollateralOut ? 0 : Params().GetConsensus().nMNCollateralAmt - (fInvalidCollateral ? 1 : 0))); - + if (masternodeType == TRANSPARENT) { + FundTransaction(tx, utxos, scriptPayout, + GetScriptForDestination(coinbaseKey.GetPubKey().GetID()), + (optCollateralOut ? 0 : Params().GetConsensus().nMNCollateralAmt - (fInvalidCollateral ? 1 : 0))); + } else { + FundTransaction(tx, utxos, scriptPayout, + GetScriptForDestination(coinbaseKey.GetPubKey().GetID()), + 0); + } pl.inputsHash = CalcTxInputsHash(tx); SetTxPayload(tx, pl); SignTransaction(tx, coinbaseKey); @@ -913,6 +978,290 @@ BOOST_FIXTURE_TEST_CASE(dip3_protx, TestChain400Setup) UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, Consensus::NetworkUpgrade::NO_ACTIVATION_HEIGHT); } +void ProcessBlockAndUpdateUtxos(std::shared_ptr pblock, SimpleUTXOMap& utxos) +{ + BOOST_CHECK(ProcessNewBlock(pblock, nullptr)); + COutPoint spentOpt = pblock.get()->vtx[1]->vin[0].prevout; + int count = utxos.count(spentOpt); + if (count > 0) { + BOOST_ASSERT(count == 1); + utxos.erase(spentOpt); + } +} + +BOOST_FIXTURE_TEST_CASE(dip3_shield, TestPoSChainSetup) +{ + // Verify that we are at block 251 + int nHeight = WITH_LOCK(cs_main, return chainActive.Tip()->nHeight); + BOOST_CHECK_EQUAL(nHeight, 250); + SyncWithValidationInterfaceQueue(); + + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V5_0, nHeight + 1); + UpdateNetworkUpgradeParameters(Consensus::UPGRADE_V6_0, nHeight + 2); + + auto utxos = BuildSimpleUtxoMap(coinbaseTxns); + + // Mint the block before v6 + { + std::shared_ptr pblock = CreateBlockInternal(pwalletMain.get()); + ProcessBlockAndUpdateUtxos(pblock, utxos); + nHeight += 1; + } + + // Create a pro reg tx and check its validity + int port = 1; + const CKey& ownerKeyTransp = GetRandomKey(); + const CBLSSecretKey& operatorKeyTransp = GetRandomBLSKey(); + auto tx = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, ownerKeyTransp, operatorKeyTransp.GetPublicKey()); + + CValidationState dummyState; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + + BOOST_CHECK(WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, dummyState);)); + BOOST_CHECK(CheckTransactionSignature(tx)); + + // Commit the transaction, mint another block and the DMN should be on chain! + CReserveKey reservekey(&*pwalletMain); + pwalletMain->CommitTransaction(MakeTransactionRef(tx), reservekey, nullptr); + std::shared_ptr pblock = CreateBlockInternal(pwalletMain.get(), {tx}); + ProcessBlockAndUpdateUtxos(pblock, utxos); + BOOST_CHECK(deterministicMNManager->GetListAtChainTip().HasMN(tx.GetHash())); + nHeight += 1; + + // force mnsync complete and enable spork 8 + { + g_tiertwo_sync_state.SetCurrentSyncPhase(MASTERNODE_SYNC_FINISHED); + int64_t nTime = GetTime() - 10; + const CSporkMessage& sporkMnPayment = CSporkMessage(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT, nTime + 1, nTime); + sporkManager.AddOrUpdateSporkMessage(sporkMnPayment); + BOOST_CHECK(sporkManager.IsSporkActive(SPORK_8_MASTERNODE_PAYMENT_ENFORCEMENT)); + } + + // Let's create a few more PoS blocks + for (int i = 0; i < 10; i++) { + std::shared_ptr pblock = CreateBlockInternal(pwalletMain.get()); + ProcessBlockAndUpdateUtxos(pblock, utxos); + nHeight += 1; + } + + // Create a bunch of sapling notes, with which we will create our SDMNs + for (int i = 0; i < 2; i++) { + SaplingOperation operation = CreateOperationAndBuildTx(pwalletMain, Params().GetConsensus().nMNCollateralAmt, true); + pwalletMain->CommitTransaction(operation.getFinalTxRef(), reservekey, nullptr); + } + std::shared_ptr shieldBlockNormal = CreateBlockInternal(pwalletMain.get(), {}, nullptr, {}, false, true); + ProcessBlockAndUpdateUtxos(shieldBlockNormal, utxos); + nHeight += 1; + { + SaplingOperation operation = CreateOperationAndBuildTx(pwalletMain, Params().GetConsensus().nMNCollateralAmt * 2, true); + pwalletMain->CommitTransaction(operation.getFinalTxRef(), reservekey, nullptr); + } + std::shared_ptr shieldBlockBig = CreateBlockInternal(pwalletMain.get(), {}, nullptr, {}, false, true); + ProcessBlockAndUpdateUtxos(shieldBlockBig, utxos); + nHeight += 1; + { + SaplingOperation operation = CreateOperationAndBuildTx(pwalletMain, Params().GetConsensus().nMNCollateralAmt * 0.5, true); + pwalletMain->CommitTransaction(operation.getFinalTxRef(), reservekey, nullptr); + } + std::shared_ptr shieldBlockSmall = CreateBlockInternal(pwalletMain.get(), {}, nullptr, {}, false, true); + ProcessBlockAndUpdateUtxos(shieldBlockSmall, utxos); + nHeight += 1; + + // Sanity check on the blocks created + BOOST_CHECK(shieldBlockNormal->vtx.size() == 4); + BOOST_CHECK(shieldBlockNormal->vtx[2]->sapData->vShieldedOutput.size() == 1 && shieldBlockNormal->vtx[3]->sapData->vShieldedOutput.size() == 1); + BOOST_CHECK(shieldBlockBig->vtx.size() == 3); + BOOST_CHECK(shieldBlockBig->vtx[2]->sapData->vShieldedOutput.size() == 1); + BOOST_CHECK(shieldBlockSmall->vtx.size() == 3); + BOOST_CHECK(shieldBlockSmall->vtx[2]->sapData->vShieldedOutput.size() == 1); + + // Finally create a SDMN, or at least try! spork21 is not active yet + const CKey& ownerKeyShield = GetRandomKey(); + const CBLSSecretKey& operatorKeyShield = GetRandomBLSKey(); + std::vector collateralNote1; + pwalletMain.get()->GetSaplingScriptPubKeyMan()->GetNotes({SaplingOutPoint(shieldBlockNormal->vtx[2]->GetHash(), 0)}, collateralNote1); + BOOST_CHECK(collateralNote1.size() == 1); + + std::vector collateralNote2; + pwalletMain.get()->GetSaplingScriptPubKeyMan()->GetNotes({SaplingOutPoint(shieldBlockNormal->vtx[3]->GetHash(), 0)}, collateralNote2); + BOOST_CHECK(collateralNote2.size() == 1); + CMutableTransaction txShield; + { + txShield = CreateProRegTx(nullopt, utxos, port++, GenerateRandomAddress(), coinbaseKey, ownerKeyShield, operatorKeyShield.GetPublicKey(), 0, false, SHIELD, &collateralNote1[0], pwalletMain.get()); + CValidationState state; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + WITH_LOCK(cs_main, return CheckSpecialTx(txShield, chainTip, view, state);); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "spork-21-inactive"); + + // Finally activate spork 21 and recheck the tx + int64_t nTime = GetTime() - 10; + const CSporkMessage& sporkLegacyObs = CSporkMessage(SPORK_21_LEGACY_MNS_MAX_HEIGHT, nHeight, nTime); + sporkManager.AddOrUpdateSporkMessage(sporkLegacyObs); + BOOST_CHECK(sporkManager.IsSporkActive(SPORK_21_LEGACY_MNS_MAX_HEIGHT)); + + state = CValidationState(); + BOOST_CHECK(WITH_LOCK(cs_main, return CheckSpecialTx(txShield, chainTip, view, dummyState);)); + BOOST_CHECK(CheckTransactionSignature(txShield)); + + pwalletMain->CommitTransaction(MakeTransactionRef(txShield), reservekey, nullptr); + std::shared_ptr pblock = CreateBlockInternal(pwalletMain.get(), {txShield}, nullptr, {}, true, true); + ProcessBlockAndUpdateUtxos(pblock, utxos); + nHeight += 1; + BOOST_CHECK(deterministicMNManager->GetListAtChainTip().HasMN(txShield.GetHash())); + } + + // Next, verify payments in this mixture of transparent and shield masternodes: + std::map mapPayments; + for (size_t i = 0; i < 20; i++) { + auto mnList = deterministicMNManager->GetListAtChainTip(); + BOOST_CHECK_EQUAL(mnList.GetValidMNsCount(), 2); + BOOST_CHECK_EQUAL(mnList.GetHeight(), nHeight); + + // get next payee + auto dmnExpectedPayee = mnList.GetMNPayee(); + std::shared_ptr block = CreateBlockInternal(pwalletMain.get()); + ProcessBlockAndUpdateUtxos(block, utxos); + chainTip = chainActive.Tip(); + BOOST_ASSERT(!block.get()->vtx.empty()); + BOOST_CHECK(IsMNPayeeInBlock(*block.get(), dmnExpectedPayee->pdmnState->scriptPayout)); + mapPayments[dmnExpectedPayee->proTxHash]++; + BOOST_CHECK_EQUAL(chainTip->nHeight, ++nHeight); + } + // 20 blocks, 2 masternodes. Must have been paid 10 times each. + CheckPayments(mapPayments, 2, 10); + + // Try to register another SDMN with the same address of the first one: + { + auto tx = CreateProRegTx(nullopt, utxos, port - 1, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey(), 0, false, SHIELD, &collateralNote2[0], pwalletMain.get()); + CValidationState state; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state);); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-IP-address"); + } + + // Try to register another SDMN with the same operator key of the first one: + { + auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), operatorKeyShield.GetPublicKey(), 0, false, SHIELD, &collateralNote2[0], pwalletMain.get()); + CValidationState state; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state);); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-operator-key"); + } + + // Try to register another SDMN with the same owner key of the first one: + { + auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, ownerKeyShield, GetRandomBLSKey().GetPublicKey(), 0, false, SHIELD, &collateralNote2[0], pwalletMain.get()); + CValidationState state; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state);); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-dup-owner-key"); + } + + // Try to create a SDMN with incorrect input note (note value too big) + std::vector collateralNoteBig1; + pwalletMain.get()->GetSaplingScriptPubKeyMan()->GetNotes({SaplingOutPoint(shieldBlockBig->vtx[2]->GetHash(), 0)}, collateralNoteBig1); + BOOST_CHECK(collateralNoteBig1.size() == 1); + { + auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey(), 0, false, SHIELD, &collateralNoteBig1[0], pwalletMain.get(), Params().GetConsensus().nMNCollateralAmt); + CValidationState state; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state);); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-sapling-spend-description-invalid"); + } + + // Try to create a SDMN with correct input note, but broken proof (wrong output amount) + { + auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey(), 0, false, SHIELD, &collateralNote2[0], pwalletMain.get(), Params().GetConsensus().nMNCollateralAmt - 1); + CValidationState state; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state);); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-proofCollateralOutput-amt"); + } + + // Try to create a SDMN with incorrect input note (note value too small) + std::vector collateralNoteSmall1; + pwalletMain.get()->GetSaplingScriptPubKeyMan()->GetNotes({SaplingOutPoint(shieldBlockSmall->vtx[2]->GetHash(), 0)}, collateralNoteSmall1); + BOOST_CHECK(collateralNoteSmall1.size() == 1); + { + auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey(), 0, false, SHIELD, &collateralNoteSmall1[0], pwalletMain.get(), Params().GetConsensus().nMNCollateralAmt); + CValidationState state; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state);); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-sapling-spend-description-invalid"); + } + + // Try to create a SDMN by using a big note as input but this time get a shield change as additional output (200 -> 100 + 100) + { + auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey(), 0, false, SHIELD, &collateralNoteBig1[0], pwalletMain.get(), Params().GetConsensus().nMNCollateralAmt, false); + CValidationState state; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state);); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-sapling-spend-description-invalid"); + } + + // Try to create a SDMN with a wrong bindig sig length: + { + auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey(), 0, false, SHIELD, &collateralNote2[0], pwalletMain.get(), Params().GetConsensus().nMNCollateralAmt, false, true); + CValidationState state; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state);); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-bindingSig-len"); + } + + // Try to create a Masternode with both normal collateral and shield collateral non-empty: + { + auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey(), 0, false, BOTH, &collateralNote2[0], pwalletMain.get(), Params().GetConsensus().nMNCollateralAmt, false, true); + CValidationState state; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state);); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-collaterals-non-empty"); + } + + // Try to create a Masternode with both normal collateral and shield collateral empty: + { + auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey(), 0, false, EMPTY, &collateralNote2[0], pwalletMain.get(), Params().GetConsensus().nMNCollateralAmt, false, true); + CValidationState state; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state);); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-protx-collaterals-empty"); + } + + // Spend ALL shield notes (4.3 masternode cost instead of 4.5 to pay fees): + { + WITH_LOCK(pwalletMain->cs_wallet, pwalletMain->UnlockAllNotes()); + SaplingOperation operation = CreateOperationAndBuildTx(pwalletMain, Params().GetConsensus().nMNCollateralAmt * 4.3, false); + pwalletMain->CommitTransaction(operation.getFinalTxRef(), reservekey, nullptr); + std::shared_ptr pblock = CreateBlockInternal(pwalletMain.get(), {}, nullptr, {}, false, true); + ProcessBlockAndUpdateUtxos(pblock, utxos); + nHeight += 1; + } + + // Verify that the SDMN is gone + BOOST_CHECK(!deterministicMNManager->GetListAtChainTip().HasMN(txShield.GetHash())); + + // Try to create a SDMN with a spent collateral: + { + auto tx = CreateProRegTx(nullopt, utxos, port, GenerateRandomAddress(), coinbaseKey, GetRandomKey(), GetRandomBLSKey().GetPublicKey(), 0, false, SHIELD, &collateralNote2[0], pwalletMain.get(), Params().GetConsensus().nMNCollateralAmt, false, false); + CValidationState state; + CBlockIndex* chainTip = chainActive.Tip(); + CCoinsViewCache* view = pcoinsTip.get(); + WITH_LOCK(cs_main, return CheckSpecialTx(tx, chainTip, view, state);); + BOOST_CHECK_EQUAL(state.GetRejectReason(), "bad-txns-shielded-requirements-not-met"); + } +} + // Dummy commitment where the DKG shares are replaced with the operator keys of each member. // members at index skeys.size(), ..., llmqType.size - 1 are invalid static llmq::CFinalCommitment CreateFinalCommitment(std::vector& pkeys, diff --git a/src/wallet/test/pos_test_fixture.cpp b/src/wallet/test/pos_test_fixture.cpp index 3a8d4260235eba..27f4cbc23bf3f2 100644 --- a/src/wallet/test/pos_test_fixture.cpp +++ b/src/wallet/test/pos_test_fixture.cpp @@ -3,8 +3,13 @@ // file COPYING or https://www.opensource.org/licenses/mit-license.php. #include "wallet/test/pos_test_fixture.h" +#include "consensus/merkle.h" #include "wallet/wallet.h" +#include "blockassembler.h" +#include "blocksignature.h" +#include "sapling/address.h" + #include TestPoSChainSetup::TestPoSChainSetup() : TestChainSetup(0) @@ -38,3 +43,84 @@ TestPoSChainSetup::~TestPoSChainSetup() SyncWithValidationInterfaceQueue(); UnregisterValidationInterface(pwalletMain.get()); } + +// Util functions that are useful in all unit tests that use this POS chain +// Create a sapling operation that can build a shield tx +SaplingOperation CreateOperationAndBuildTx(std::unique_ptr& pwallet, + CAmount amount, + bool selectTransparentCoins) +{ + // Create the operation + libzcash::SaplingPaymentAddress pa = pwallet->GenerateNewSaplingZKey("s1"); + std::vector recipients; + recipients.emplace_back(pa, amount, "", false); + SaplingOperation operation(Params().GetConsensus(), pwallet.get()); + operation.setMinDepth(1); + auto operationResult = operation.setRecipients(recipients) + ->setSelectTransparentCoins(selectTransparentCoins) + ->setSelectShieldedCoins(!selectTransparentCoins) + ->build(); + std::cout << operationResult.getError() << std::endl; + BOOST_ASSERT_MSG(operationResult, operationResult.getError().c_str()); + + CValidationState state; + BOOST_ASSERT_MSG( + CheckTransaction(operation.getFinalTx(), state, true), + "Invalid Sapling transaction"); + return operation; +} + +bool IsSpentOnFork(const COutput& coin, std::initializer_list> forkchain) +{ + for (const auto& block : forkchain) { + const auto& usedOutput = block->vtx[1]->vin.at(0).prevout; + if (coin.tx->GetHash() == usedOutput.hash && coin.i == (int)usedOutput.n) { + // spent on fork + return true; + } + } + return false; +} + +std::shared_ptr CreateBlockInternal(CWallet* pwalletMain, const std::vector& txns, CBlockIndex* customPrevBlock, std::initializer_list> forkchain, bool fNoMempoolTx, bool testValidity) +{ + std::vector availableCoins; + BOOST_CHECK(pwalletMain->StakeableCoins(&availableCoins)); + + // Remove any utxo which is not deeper than 120 blocks (for the same reasoning + // used when selecting tx inputs in CreateAndCommitTx) + // Also, as the wallet is not prepared to follow several chains at the same time, + // need to manually remove from the stakeable utxo set every already used + // coinstake inputs on the previous blocks of the parallel chain so they + // are not used again. + for (auto it = availableCoins.begin(); it != availableCoins.end();) { + if (it->nDepth <= 120 || IsSpentOnFork(*it, forkchain)) { + it = availableCoins.erase(it); + } else { + it++; + } + } + + std::unique_ptr pblocktemplate = BlockAssembler( + Params(), false) + .CreateNewBlock(CScript(), + pwalletMain, + true, + &availableCoins, + fNoMempoolTx, + testValidity, + customPrevBlock, + false); + BOOST_ASSERT(pblocktemplate); + auto pblock = std::make_shared(pblocktemplate->block); + if (!txns.empty()) { + for (const auto& tx : txns) { + pblock->vtx.emplace_back(MakeTransactionRef(tx)); + } + pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); + const int nHeight = (customPrevBlock != nullptr ? customPrevBlock->nHeight + 1 : WITH_LOCK(cs_main, return chainActive.Height()) + 1); + pblock->hashFinalSaplingRoot = CalculateSaplingTreeRoot(&*pblock, nHeight, Params()); + assert(SignBlock(*pblock, *pwalletMain)); + } + return pblock; +} diff --git a/src/wallet/test/pos_test_fixture.h b/src/wallet/test/pos_test_fixture.h index 433e1d39d8530b..1b7cbd548d56ff 100644 --- a/src/wallet/test/pos_test_fixture.h +++ b/src/wallet/test/pos_test_fixture.h @@ -7,6 +7,9 @@ #include "test/test_pivx.h" + +#include "sapling/sapling_operation.h" + class CWallet; /* @@ -21,4 +24,10 @@ struct TestPoSChainSetup: public TestChainSetup ~TestPoSChainSetup(); }; +SaplingOperation CreateOperationAndBuildTx(std::unique_ptr& pwallet, + CAmount amount, + bool selectTransparentCoins); +bool IsSpentOnFork(const COutput& coin, std::initializer_list> forkchain = {}); +std::shared_ptr CreateBlockInternal(CWallet* pwalletMain, const std::vector& txns = {}, CBlockIndex* customPrevBlock = nullptr, std::initializer_list> forkchain = {}, bool fNoMempoolTx = true, bool testValidity = false); + #endif // PIVX_POS_TEST_FIXTURE_H diff --git a/src/wallet/test/pos_validations_tests.cpp b/src/wallet/test/pos_validations_tests.cpp index e07d89d1c58829..706e596f8dd4ab 100644 --- a/src/wallet/test/pos_validations_tests.cpp +++ b/src/wallet/test/pos_validations_tests.cpp @@ -126,60 +126,6 @@ COutPoint GetOutpointWithAmount(const CTransaction& tx, CAmount outpointValue) return {}; } -static bool IsSpentOnFork(const COutput& coin, std::initializer_list> forkchain = {}) -{ - for (const auto& block : forkchain) { - const auto& usedOutput = block->vtx[1]->vin.at(0).prevout; - if (coin.tx->GetHash() == usedOutput.hash && coin.i == (int)usedOutput.n) { - // spent on fork - return true; - } - } - return false; -} - -std::shared_ptr CreateBlockInternal(CWallet* pwalletMain, const std::vector& txns = {}, - CBlockIndex* customPrevBlock = nullptr, - std::initializer_list> forkchain = {}) -{ - std::vector availableCoins; - BOOST_CHECK(pwalletMain->StakeableCoins(&availableCoins)); - - // Remove any utxo which is not deeper than 120 blocks (for the same reasoning - // used when selecting tx inputs in CreateAndCommitTx) - // Also, as the wallet is not prepared to follow several chains at the same time, - // need to manually remove from the stakeable utxo set every already used - // coinstake inputs on the previous blocks of the parallel chain so they - // are not used again. - for (auto it = availableCoins.begin(); it != availableCoins.end() ;) { - if (it->nDepth <= 120 || IsSpentOnFork(*it, forkchain)) { - it = availableCoins.erase(it); - } else { - it++; - } - } - - std::unique_ptr pblocktemplate = BlockAssembler( - Params(), false).CreateNewBlock(CScript(), - pwalletMain, - true, - &availableCoins, - true, - false, - customPrevBlock, - false); - BOOST_ASSERT(pblocktemplate); - auto pblock = std::make_shared(pblocktemplate->block); - if (!txns.empty()) { - for (const auto& tx : txns) { - pblock->vtx.emplace_back(MakeTransactionRef(tx)); - } - pblock->hashMerkleRoot = BlockMerkleRoot(*pblock); - assert(SignBlock(*pblock, *pwalletMain)); - } - return pblock; -} - static COutput GetUnspentCoin(CWallet* pwallet, std::initializer_list> forkchain = {}) { std::vector availableCoins;