From b09c23e28c6d21788645dc1511882c697812c86e Mon Sep 17 00:00:00 2001 From: Alessandro Rezzi Date: Thu, 27 Jul 2023 18:13:59 +0200 Subject: [PATCH] Shield staking Functional test --- src/blockassembler.cpp | 4 +- src/consensus/upgrades.cpp | 124 +++++++++--------- src/kernel.cpp | 22 +++- src/kernel.h | 11 +- src/primitives/block.h | 36 +++-- src/primitives/transaction.cpp | 2 +- src/rpc/blockchain.cpp | 4 + src/sapling/sapling_validation.cpp | 57 ++++---- src/sapling/saplingscriptpubkeyman.cpp | 72 ++++++++++ src/wallet/wallet.cpp | 15 ++- src/wallet/wallet.h | 3 +- test/functional/mining_pos_coldStaking.py | 12 +- test/functional/mining_shield_pos.py | 76 +++++++++++ .../test_framework/test_framework.py | 6 +- test/functional/test_runner.py | 1 + 15 files changed, 334 insertions(+), 111 deletions(-) create mode 100755 test/functional/mining_shield_pos.py diff --git a/src/blockassembler.cpp b/src/blockassembler.cpp index ea3c9f769e6fb2..1577f12c2a964d 100644 --- a/src/blockassembler.cpp +++ b/src/blockassembler.cpp @@ -108,7 +108,9 @@ bool SolveProofOfStake(CBlock* pblock, CBlockIndex* pindexPrev, CMutableTransact if (pblock->IsProofOfShieldStake()) { auto& shieldStake = *static_cast(pStake); - pwallet->ComputeShieldStakeProof(*pblock, shieldStake, shieldStake.note.value()); + if (!pwallet->GetSaplingScriptPubKeyMan()->ComputeShieldStakeProof(*pblock, shieldStake, shieldStake.suggestedValue)) { + return false; + } } return true; } diff --git a/src/consensus/upgrades.cpp b/src/consensus/upgrades.cpp index e313d831c94785..917511c7e9016d 100644 --- a/src/consensus/upgrades.cpp +++ b/src/consensus/upgrades.cpp @@ -13,66 +13,70 @@ * We are using it in the -nuparams startup arg and input it with spaces is just ugly. */ const struct NUInfo NetworkUpgradeInfo[Consensus::MAX_NETWORK_UPGRADES] = { - { - /*.strName =*/ "Base", - /*.strInfo =*/ "PIVX network", - }, - { - /*.strName =*/ "PoS", - /*.strInfo =*/ "Proof of Stake Consensus activation", - }, - { - /*.strName =*/ "PoS_v2", - /*.strInfo =*/ "New selection for stake modifier", - }, - { - /*.strName =*/ "Zerocoin", - /*.strInfo =*/ "ZeroCoin protocol activation - start block v4", - }, - { - /*.strName =*/ "Zerocoin_v2", - /*.strInfo =*/ "New zerocoin serials and zPOS start", - }, - { - /*.strName =*/ "BIP65", - /*.strInfo =*/ "CLTV (BIP65) activation - start block v5", - }, - { - /*.strName =*/ "Zerocoin_Public", - /*.strInfo =*/ "Activation of zerocoin public spends (spend v3)", - }, - { - /*.strName =*/ "PIVX_v3.4", - /*.strInfo =*/ "New 256-bit stake modifier - start block v6", - }, - { - /*.strName =*/ "PIVX_v4.0", - /*.strInfo =*/ "New message sigs - start block v7 - time protocol - zc spend v4", - }, - { - /*.strName =*/ "v5_shield", - /*.strInfo =*/ "Sapling Shield - start block v8 - start transaction v3", - }, - { - /*.strName =*/ "PIVX_v5.2", - /*.strInfo =*/ "New cold-staking rules", - }, - { - /*.strName =*/ "PIVX_v5.3", - /*.strInfo =*/ "New staking rules", - }, - { - /*.strName =*/ "PIVX_v5.5", - /*.strInfo =*/ "New rewards structure", - }, - { - /*.strName =*/ "v6_evo", - /*.strInfo =*/ "Deterministic Masternodes", - }, - { - /*.strName =*/ "Test_dummy", - /*.strInfo =*/ "Test dummy info", - }, + { + /*.strName =*/"Base", + /*.strInfo =*/"PIVX network", + }, + { + /*.strName =*/"PoS", + /*.strInfo =*/"Proof of Stake Consensus activation", + }, + { + /*.strName =*/"PoS_v2", + /*.strInfo =*/"New selection for stake modifier", + }, + { + /*.strName =*/"Zerocoin", + /*.strInfo =*/"ZeroCoin protocol activation - start block v4", + }, + { + /*.strName =*/"Zerocoin_v2", + /*.strInfo =*/"New zerocoin serials and zPOS start", + }, + { + /*.strName =*/"BIP65", + /*.strInfo =*/"CLTV (BIP65) activation - start block v5", + }, + { + /*.strName =*/"Zerocoin_Public", + /*.strInfo =*/"Activation of zerocoin public spends (spend v3)", + }, + { + /*.strName =*/"PIVX_v3.4", + /*.strInfo =*/"New 256-bit stake modifier - start block v6", + }, + { + /*.strName =*/"PIVX_v4.0", + /*.strInfo =*/"New message sigs - start block v7 - time protocol - zc spend v4", + }, + { + /*.strName =*/"v5_shield", + /*.strInfo =*/"Sapling Shield - start block v8 - start transaction v3", + }, + { + /*.strName =*/"PIVX_v5.2", + /*.strInfo =*/"New cold-staking rules", + }, + { + /*.strName =*/"PIVX_v5.3", + /*.strInfo =*/"New staking rules", + }, + { + /*.strName =*/"PIVX_v5.5", + /*.strInfo =*/"New rewards structure", + }, + { + /*.strName =*/"v6_evo", + /*.strInfo =*/"Deterministic Masternodes", + }, + { + /*.strName =*/"shield_staking", + /*.strInfo =*/"Shield Staking", + }, + { + /*.strName =*/"Test_dummy", + /*.strInfo =*/"Test dummy info", + }, }; UpgradeState NetworkUpgradeState( diff --git a/src/kernel.cpp b/src/kernel.cpp index d6c397162e4b2a..7d66fafb471378 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -7,6 +7,7 @@ #include "kernel.h" +#include "arith_uint256.h" #include "chainparams.h" #include "consensus/params.h" #include "consensus/validation.h" @@ -70,7 +71,7 @@ uint256 CStakeKernel::GetHash() const } // Check that the kernel hash meets the target required -bool CStakeKernel::CheckKernelHash(bool fSkipLog) const +bool CStakeKernel::CheckKernelHash(bool fSkipLog) { // Get weighted target arith_uint256 bnTarget; @@ -80,7 +81,8 @@ bool CStakeKernel::CheckKernelHash(bool fSkipLog) const // Check PoS kernel hash const arith_uint256& hashProofOfStake = UintToArith256(GetHash()); const bool res = hashProofOfStake < bnTarget; - + suggestedValue = ComputeSuggestedValue(stakeValue, bnTarget, hashProofOfStake); + LogPrintf("%d\n", suggestedValue); if (!fSkipLog || res) { LogPrint(BCLog::STAKING, "%s : Proof Of Stake:" "\nstakeModifier=%s" @@ -97,6 +99,14 @@ bool CStakeKernel::CheckKernelHash(bool fSkipLog) const return res; } +CAmount CStakeKernel::ComputeSuggestedValue(CAmount stakevalue, const arith_uint256& bnTarget, const arith_uint256& hashProofOfStake) const +{ + arith_uint256 diff; + diff.SetCompact(nBits); + auto total = static_cast((((hashProofOfStake) / diff).Get64() + 1) * 100); + if (total > stakevalue) return stakevalue; + return total; +} /* * PoS Validation @@ -135,7 +145,7 @@ static bool LoadStakeInput(const CBlock& block, std::unique_ptr& st * @param[in] nTimeTx new blocktime * @return bool true if stake kernel hash meets target protocol */ -bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, int64_t& nTimeTx) +bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, int64_t& nTimeTx, CAmount* suggestedValue) { if (!stakeInput) return false; @@ -146,7 +156,11 @@ bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int // Verify Proof Of Stake CStakeKernel stakeKernel(pindexPrev, stakeInput, nBits, nTimeTx); - return stakeKernel.CheckKernelHash(true); + bool check = stakeKernel.CheckKernelHash(true); + if (suggestedValue) + *suggestedValue = stakeKernel.GetSuggestedValue(); + + return check; } // This checks if the provided note value is valid diff --git a/src/kernel.h b/src/kernel.h index 8e2118d7123825..8a13de0ced1d53 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -8,6 +8,7 @@ #ifndef PIVX_KERNEL_H #define PIVX_KERNEL_H +#include "arith_uint256.h" #include "stakeinput.h" class CStakeKernel { @@ -26,7 +27,11 @@ class CStakeKernel { uint256 GetHash() const; // Check that the kernel hash meets the target required - bool CheckKernelHash(bool fSkipLog = false) const; + bool CheckKernelHash(bool fSkipLog = false); + CAmount GetSuggestedValue() const + { + return suggestedValue; + } private: // kernel message hashed @@ -37,6 +42,8 @@ class CStakeKernel { // hash target unsigned int nBits{0}; // difficulty for the target CAmount stakeValue{0}; // target multiplier + CAmount suggestedValue{0}; + CAmount ComputeSuggestedValue(CAmount stakevalue, const arith_uint256& bnTarget, const arith_uint256& hashProofOfStake) const; }; /* PoS Validation */ @@ -50,7 +57,7 @@ class CStakeKernel { * @param[in] nTimeTx new blocktime * @return bool true if stake kernel hash meets target protocol */ -bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, int64_t& nTimeTx); +bool Stake(const CBlockIndex* pindexPrev, CStakeInput* stakeInput, unsigned int nBits, int64_t& nTimeTx, CAmount* suggestedValue = nullptr); /* * CheckProofOfStake Check if block has valid proof of stake diff --git a/src/primitives/block.h b/src/primitives/block.h index 5779ed2d7e7f8c..6512f52ec90af9 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -81,22 +81,42 @@ class ShieldStakeProof { public: CAmount amount; - SpendDescription input; - OutputDescription output; - std::vector bindingSig; + uint256 inputCv; + uint256 rk; + SpendDescription::spend_auth_sig_t spendSig; + libzcash::GrothProof inputProof = {{0}}; + + uint256 outputCv; + uint256 epk; + uint256 cmu; + libzcash::GrothProof outputProof = {{0}}; + libzcash::GrothProof sig = {{0}}; void SetNull() { amount = 0; - output = OutputDescription(); - bindingSig.clear(); + inputCv.SetNull(); + spendSig = {{0}}; + rk.SetNull(); + inputProof = {{0}}; + outputCv.SetNull(); + epk.SetNull(); + cmu.SetNull(); + outputProof = {{0}}; } SERIALIZE_METHODS(ShieldStakeProof, obj) { READWRITE(obj.amount); - READWRITE(obj.output); - READWRITE(obj.bindingSig); + READWRITE(obj.inputCv); + READWRITE(obj.rk); + READWRITE(obj.spendSig); + READWRITE(obj.inputProof); + READWRITE(obj.epk); + READWRITE(obj.cmu); + READWRITE(obj.outputCv); + READWRITE(obj.outputProof); + READWRITE(obj.sig); } }; @@ -134,7 +154,7 @@ class CBlock : public CBlockHeader READWRITE(obj.vchBlockSig); // Shield Staking Proof - if (obj.nVersion >= 12 && obj.IsProofOfStake()) { + if (obj.nVersion >= 12 && obj.IsProofOfShieldStake()) { READWRITE(obj.shieldStakeProof); } } diff --git a/src/primitives/transaction.cpp b/src/primitives/transaction.cpp index 1621f0002006c2..4dff921b796569 100644 --- a/src/primitives/transaction.cpp +++ b/src/primitives/transaction.cpp @@ -135,7 +135,7 @@ bool CTransaction::IsCoinStake() const { if (vin.empty()) return false; - if (!sapData->vShieldedSpend.empty()) + if (sapData && !sapData->vShieldedSpend.empty()) return false; bool fAllowNull = vin[0].IsZerocoinSpend(); if (vin[0].prevout.IsNull() && !fAllowNull) diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index c6767903d7d4c4..045dc84ceba5b4 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -168,6 +168,10 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn result.pushKV("bits", strprintf("%08x", block.nBits)); result.pushKV("difficulty", GetDifficulty(blockindex)); result.pushKV("chainwork", blockindex->nChainWork.GetHex()); + if (block.IsProofOfShieldStake()) { + auto& p = block.shieldStakeProof; + result.pushKV("shieldproofamount", p.amount); + } if (blockindex->pprev) result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); diff --git a/src/sapling/sapling_validation.cpp b/src/sapling/sapling_validation.cpp index b2251e62564ceb..8eec33b9ab3403 100644 --- a/src/sapling/sapling_validation.cpp +++ b/src/sapling/sapling_validation.cpp @@ -232,36 +232,47 @@ bool CheckShieldStake(const CBlock& block, CValidationState& state, const CChain if (!block.IsProofOfShieldStake()) { return false; } - return true; - // TODO: check rest - // In addition to the regular checks for a tx, we also have to ensure that the provided - // shieldStakeAmount is valid. To do this without creating a new circuit, the staker inserts - // a dummy note in the proof, so that Input - DummyNote = shieldStakeAmount. - // To prevent others from potentially spending that note, we change the output proof sighash - auto* saplingCtx = librustzcash_sapling_verification_ctx_init(); + LogPrintf("%d", block.shieldStakeProof.amount); + const auto& saplingData = block.vtx[1].get()->sapData.get(); - const auto& spend = saplingData.vShieldedSpend[0]; + auto ctx = librustzcash_sapling_verification_ctx_init(); + const auto& inputNote = saplingData.vShieldedSpend[0]; + const auto& p = block.shieldStakeProof; + const int DOS_LEVEL_BLOCK = 100; + uint256 dataToBeSigned; - // TODO: get the sighash - if (!librustzcash_sapling_check_spend(saplingCtx, spend.cv.begin(), spend.anchor.begin(), spend.nullifier.begin(), spend.rk.begin(), spend.zkproof.begin(), spend.spendAuthSig.begin(), dataToBeSigned.begin())) { - librustzcash_sapling_verification_ctx_free(saplingCtx); - return false; - } - const auto& output = block.shieldStakeProof.output; - if (!librustzcash_sapling_check_output(saplingCtx, output.cv.begin(), output.cmu.begin(), output.ephemeralKey.begin(), output.zkproof.begin())) { - librustzcash_sapling_verification_ctx_free(saplingCtx); - return false; + try { + // TODO: write signature for shield + // dataToBeSigned = SignatureHash(scriptCode, tx, NOT_AN_INPUT, SIGHASH_ALL, 0, SIGVERSION_SAPLING); + } catch (const std::logic_error& ex) { + // A logic error should never occur because we pass NOT_AN_INPUT and + // SIGHASH_ALL to SignatureHash(). + return state.DoS(100, error("%s: error computing signature hash", __func__), + REJECT_INVALID, "error-computing-signature-hash"); } - // dataToBeSigned = ...; + if (!librustzcash_sapling_check_spend(ctx, p.inputCv.begin(), inputNote.anchor.begin(), inputNote.nullifier.begin(), p.rk.begin(), p.inputProof.begin(), p.spendSig.begin(), dataToBeSigned.begin())) { + librustzcash_sapling_verification_ctx_free(ctx); + return state.DoS( + DOS_LEVEL_BLOCK, + error("%s: Sapling spend description invalid", __func__), + REJECT_INVALID, "bad-txns-sapling-spend-description-invalid"); + } - if (!librustzcash_sapling_final_check(saplingCtx, block.shieldStakeProof.amount, block.shieldStakeProof.bindingSig.data(), dataToBeSigned.begin())) { - librustzcash_sapling_verification_ctx_free(saplingCtx); - return false; + if (!librustzcash_sapling_check_output(ctx, p.outputCv.begin(), p.cmu.begin(), p.epk.begin(), p.outputProof.begin())) { + librustzcash_sapling_verification_ctx_free(ctx); + return state.DoS(100, error("%s: Sapling output description invalid", __func__), + REJECT_INVALID, "bad-txns-sapling-output-description-invalid"); } - librustzcash_sapling_verification_ctx_free(saplingCtx); - LogPrintf("Phonenuix"); + if (!librustzcash_sapling_final_check(ctx, block.shieldStakeProof.amount, block.shieldStakeProof.sig.data(), dataToBeSigned.begin())) { + librustzcash_sapling_verification_ctx_free(ctx); + return state.DoS( + 100, + error("%s: Sapling binding signature invalid", __func__), + REJECT_INVALID, "bad-txns-sapling-binding-signature-invalid"); + } + librustzcash_sapling_verification_ctx_free(ctx); return true; } diff --git a/src/sapling/saplingscriptpubkeyman.cpp b/src/sapling/saplingscriptpubkeyman.cpp index ce6de3d66cbb70..b460a453fca96e 100644 --- a/src/sapling/saplingscriptpubkeyman.cpp +++ b/src/sapling/saplingscriptpubkeyman.cpp @@ -10,6 +10,7 @@ #include "sapling/sapling_transaction.h" #include "validation.h" // for ReadBlockFromDisk() #include "wallet/wallet.h" +#include void SaplingScriptPubKeyMan::AddToSaplingSpends(const uint256& nullifier, const uint256& wtxid) { @@ -1340,6 +1341,77 @@ bool SaplingScriptPubKeyMan::ComputeShieldStakeProof(CBlock& block, CStakeableSh { assert(block.IsProofOfShieldStake()); assert(note.note.value() >= suggestedValue); + + const auto& spendNote = block.vtx[1]->sapData->vShieldedSpend[0]; + auto* ctx = librustzcash_sapling_proving_ctx_init(); + libzcash::SaplingExtendedSpendingKey sk; + if (!wallet->GetSaplingExtendedSpendingKey(note.address, sk)) { + return false; + } + + uint256 alpha; + uint256 anchor; + uint256 dataToBeSigned; + std::vector> witnesses; + std::vector noteop; + noteop.emplace_back(note.op); + GetSaplingNoteWitnesses(noteop, witnesses, anchor); + + librustzcash_sapling_generate_r(alpha.begin()); + CDataStream ss(SER_NETWORK, PROTOCOL_VERSION); + ss << witnesses[0]->path(); + std::vector witness(ss.begin(), ss.end()); + assert(anchor == spendNote.anchor); + librustzcash_sapling_spend_sig( + sk.expsk.ask.begin(), + alpha.begin(), + dataToBeSigned.begin(), + block.shieldStakeProof.spendSig.data()); + + if (!librustzcash_sapling_spend_proof(ctx, sk.expsk.full_viewing_key().ak.begin(), + sk.expsk.nsk.begin(), + note.note.d.data(), + note.note.r.begin(), + alpha.begin(), + note.note.value(), + anchor.begin(), + witness.data(), + block.shieldStakeProof.inputCv.begin(), + block.shieldStakeProof.rk.begin(), + block.shieldStakeProof.inputProof.begin())) { + librustzcash_sapling_proving_ctx_free(ctx); + return false; + } + uint256 dummyEsk; + + CAmount amount = note.note.value() - suggestedValue; + uint256 rcm; + librustzcash_sapling_generate_r(rcm.begin()); + libzcash::SaplingPaymentAddress paymentAddress(note.address); + const std::array emptyMemo = {{0xF6}}; + libzcash::SaplingNote dummyNote(paymentAddress.d, paymentAddress.pk_d, amount, rcm); + libzcash::SaplingNotePlaintext notePlaintext(dummyNote, emptyMemo); + auto res = notePlaintext.encrypt(dummyNote.pk_d); + if (!res) return false; + auto& encryptor = res->second; + + ss = CDataStream(SER_NETWORK, PROTOCOL_VERSION); + ss << paymentAddress; + std::vector addressBytes(ss.begin(), ss.end()); + + if (!librustzcash_sapling_output_proof(ctx, encryptor.get_esk().begin(), addressBytes.data(), rcm.begin(), amount, block.shieldStakeProof.outputCv.begin(), block.shieldStakeProof.outputProof.begin())) { + librustzcash_sapling_proving_ctx_free(ctx); + return false; + } + block.shieldStakeProof.cmu = *dummyNote.cmu(); + block.shieldStakeProof.epk = encryptor.get_epk(); + + if (!librustzcash_sapling_binding_sig(ctx, suggestedValue, dataToBeSigned.data(), block.shieldStakeProof.sig.begin())) { + librustzcash_sapling_proving_ctx_free(ctx); + return false; + } + librustzcash_sapling_proving_ctx_free(ctx); block.shieldStakeProof.amount = suggestedValue; + LogPrintf("%s : Shield Stake proof generated with value %d\n", __func__, suggestedValue); return true; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 40ffcdea567c92..d8d7a53207a804 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -8,9 +8,16 @@ #include "consensus/params.h" #include "optional.h" #include "primitives/transaction.h" +#include "sapling/address.h" +#include "sapling/incrementalmerkletree.h" +#include "sapling/note.h" +#include "sapling/sapling_transaction.h" +#include "sapling/sapling_util.h" #include "sapling/saplingscriptpubkeyman.h" #include "sapling/zip32.h" +#include "serialize.h" #include "stakeinput.h" +#include "version.h" #include #include #if defined(HAVE_CONFIG_H) @@ -3425,7 +3432,12 @@ CStakeableInterface* CWallet::CreateCoinStake(const CBlockIndex& indexPrev, unsi } ++nAttempts; - bool fKernelFound = Stake(&indexPrev, &*stakeInput, nBits, nTxNewTime); + CAmount suggestedValue; + bool fKernelFound = Stake(&indexPrev, &*stakeInput, nBits, nTxNewTime, &suggestedValue); + // Refactor maybe + if (stakeInput->IsShieldPIV()) { + static_cast(stakeOutput.get())->suggestedValue = suggestedValue; + } // update staker status (time, attemps) pStakerStatus->SetLastTime(nTxNewTime); @@ -3465,6 +3477,7 @@ bool CWallet::CreateShieldReward(const CBlockIndex& indexPrev, const CStakeableS noteop.emplace_back(shieldNote.op); m_sspk_man->GetSaplingNoteWitnesses(noteop, witnesses, anchor); txBuilder.AddSaplingSpend(sk.expsk, shieldNote.note, anchor, witnesses[0].get()); + const auto& txTrial = txBuilder.Build().GetTx(); if (txTrial) { txNew = CMutableTransaction(*txTrial); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 350d2ef7e46a8b..1355cf683a399f 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1131,7 +1131,6 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface // Shielded balances CAmount GetAvailableShieldedBalance(bool fUseCache = true) const; CAmount GetUnconfirmedShieldedBalance() const; - static CFeeRate minTxFee; size_t KeypoolCountExternalKeys(); @@ -1345,7 +1344,7 @@ class CStakeableShieldNote : public SaplingNoteEntry, public CStakeableInterface { public: uint256 nullifier; - + CAmount suggestedValue = 0; explicit CStakeableShieldNote(const SaplingNoteEntry& _note, uint256 _nullifier) : SaplingNoteEntry(_note), nullifier(_nullifier) {} std::unique_ptr ToInput() const override { diff --git a/test/functional/mining_pos_coldStaking.py b/test/functional/mining_pos_coldStaking.py index c073da2563cd47..9306d6d90d9860 100755 --- a/test/functional/mining_pos_coldStaking.py +++ b/test/functional/mining_pos_coldStaking.py @@ -92,7 +92,7 @@ def run_test(self): assert self.nodes[1].lockunspent(False, True, [{"txid": x['txid'], "vout": x['vout']}]) # check that it cannot stake sleep(1) - assert_equal(self.nodes[1].getstakingstatus()["stakeablecoins"], 0) + assert_equal(self.nodes[1].getstakingstatus()["transparent_stakeable_coins"], 0) # create shielded balance for node 0 self.log.info("Shielding some coins for node0...") self.nodes[0].shieldsendmany("from_transparent", [{"address": self.nodes[0].getnewshieldaddress(), @@ -205,7 +205,7 @@ def run_test(self): # ----------------------------------------------------------- print("*** 7 ***") self.log.info("Trying to generate a cold-stake block before whitelisting the owner...") - assert_equal(self.nodes[1].getstakingstatus()["stakeablecoins"], 0) + assert_equal(self.nodes[1].getstakingstatus()["transparent_stakeable_coins"], 0) assert_equal(self.nodes[1].listdelegators(), []) self.log.info("Nice. Cold staker was NOT able to create the block yet.") @@ -232,7 +232,7 @@ def run_test(self): # 9) check that the staker can use the coins to stake a block with internal miner. # -------------------------------------------------------------------------------- print("*** 9 ***") - assert_equal(self.nodes[1].getstakingstatus()["stakeablecoins"], NUM_OF_INPUTS-1) + assert_equal(self.nodes[1].getstakingstatus()["transparent_stakeable_coins"], NUM_OF_INPUTS-1) self.log.info("Generating one valid cold-stake block...") self.mocktime = self.generate_pos(1, self.mocktime) self.log.info("New block created by cold-staking. Trying to submit...") @@ -338,12 +338,12 @@ def run_test(self): assert ret assert_equal(self.nodes[1].listdelegators(), []) assert_equal(self.nodes[1].listdelegators(True)[0]["address"], owner_address) - assert_equal(self.nodes[1].getstakingstatus()["stakeablecoins"], 0) + assert_equal(self.nodes[1].getstakingstatus()["transparent_stakeable_coins"], 0) self.log.info("Re-enable delegation") ret = self.nodes[1].delegatoradd(owner_address) assert ret assert_equal(self.nodes[1].listdelegators()[0]["address"], owner_address) - assert_equal(self.nodes[1].getstakingstatus()["stakeablecoins"], len(stakeable_coins)) + assert_equal(self.nodes[1].getstakingstatus()["transparent_stakeable_coins"], len(stakeable_coins)) self.log.info("Cancel the stake delegation spending the delegated utxos...") delegated_utxos = getDelegatedUtxos(self.nodes[0].listunspent()) # remove one utxo to spend later @@ -379,7 +379,7 @@ def run_test(self): # ----------------------------------------------------------- print("*** 14 ***") self.log.info("Trying to generate one cold-stake block again...") - assert_equal(self.nodes[1].getstakingstatus()["stakeablecoins"], 0) + assert_equal(self.nodes[1].getstakingstatus()["transparent_stakeable_coins"], 0) self.log.info("Good. Cold staker was NOT able to create any more blocks.") # 15) check balances when mature. diff --git a/test/functional/mining_shield_pos.py b/test/functional/mining_shield_pos.py new file mode 100755 index 00000000000000..7a4d287c534242 --- /dev/null +++ b/test/functional/mining_shield_pos.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +# Copyright (c) 2023 The PIVX Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +from test_framework.test_framework import PivxTestFramework +from test_framework.util import ( + assert_equal, + assert_raises_rpc_error, + disconnect_nodes, +) + + +class PIVX_ShieldStakingTest(PivxTestFramework): + + def set_test_params(self): + self.num_nodes = 3 + self.setup_clean_chain = True + + def run_test(self): + self.log.info("Shield stake functional tests") + + # Mine enough blocks to activate shield staking" + a bonus so node0 has enough pivs to mine the final block + self.nodes[0].generate(800) + self.sync_all() + + # Send some shielded notes to node1 + saplingAddr1 = self.nodes[1].getnewshieldaddress() + txid = self.nodes[0].sendtoaddress(saplingAddr1, 40000, "", "", True) + + # Generate more blocks so the shield notes becomes stakeable + self.nodes[0].generate(20) + self.sync_all() + + # Sanity check, shield staking is activated + assert_equal(self.nodes[0].getblockchaininfo()['upgrades']['v5 shield']['status'], 'active') + assert_equal(self.nodes[0].getblockchaininfo()['upgrades']['v6 evo']['status'], 'active') + assert_equal(self.nodes[0].getblockchaininfo()['upgrades']['shield staking']['status'], 'active') + + # Node1 has exactly one shield stakeable note + assert_equal(self.nodes[1].getstakingstatus()['shield_stakeables_notes'], 1) + assert_equal(self.nodes[1].getstakingstatus()['transparent_stakeable_coins'], 0) + + # Before generating the shield stake block isolate node 2: + disconnect_nodes(self.nodes[0], 2) + disconnect_nodes(self.nodes[2], 0) + disconnect_nodes(self.nodes[1], 2) + disconnect_nodes(self.nodes[2], 1) + + # Generate the block and check that reward is 4 shield PIVs + initialBalance = self.nodes[1].getshieldbalance() + shieldStakeBlockHash = self.nodes[1].generate(1)[0] + assert_equal(self.nodes[1].getshieldbalance()-initialBalance, 4) + + # Check that a loose coinShieldStakeTx cannot go in mempool + coinShieldStakeTxHash = self.nodes[1].getblock(shieldStakeBlockHash)['tx'][1] + coinShieldStakeTxHex = self.nodes[1].gettransaction(coinShieldStakeTxHash)['hex'] + assert_raises_rpc_error(-26, "coinshieldstake", self.nodes[2].sendrawtransaction, coinShieldStakeTxHex) + + # Check that node0 accepted the block: + self.sync_all(self.nodes[0:2]) + assert_equal(self.nodes[1].getblockhash(821), shieldStakeBlockHash) + assert_equal(self.nodes[0].getblockhash(821), shieldStakeBlockHash) + + # Finally verify that the reward can be spent: + recipient = [{"address": self.nodes[0].getnewshieldaddress(), "amount": (initialBalance+2)}] + txid = self.nodes[1].shieldsendmany("from_shield", recipient) + self.sync_all(self.nodes[0:2]) + blockHash = self.nodes[0].generate(1)[0] + self.sync_all(self.nodes[0:2]) + assert (txid in self.nodes[0].getblock(blockHash)['tx']) + assert_equal(self.nodes[1].getblockhash(822), blockHash) + + +if __name__ == '__main__': + PIVX_ShieldStakingTest().main() diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 99067f6988c820..20ef615a8df682 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -886,8 +886,8 @@ def generate_pos(self, node_id, btime=None): rpc_conn = self.nodes[node_id] ss = rpc_conn.getstakingstatus() assert ss["walletunlocked"] - assert ss["stakeablecoins"] > 0 - assert ss["stakingbalance"] > 0.0 + assert ss["transparent_stakeable_coins"] > 0 + assert ss["transparent_staking_balance"] > 0.0 if btime is not None: next_btime = btime + 60 fStaked = False @@ -902,7 +902,7 @@ def generate_pos(self, node_id, btime=None): # couldn't generate block. check that this node can still stake (after 60 failures) if failures > 60: ss = rpc_conn.getstakingstatus() - if not (ss["walletunlocked"] and ss["stakeablecoins"] > 0 and ss["stakingbalance"] > 0.0): + if not (ss["walletunlocked"] and ss["transparent_stakeable_coins"] > 0 and ss["transparent_staking_balance"] > 0.0): raise AssertionError("Node %d unable to stake!" % node_id) # try to stake one sec in the future if btime is not None: diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index bcec57836aecba..83270d9d29a0d7 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -94,6 +94,7 @@ 'rpc_bind.py --nonloopback', # ~ 126 sec 'feature_uacomment.py', # ~ 125 sec 'interface_rest.py', # ~ 120 sec + 'mining_shield_pos.py', # ~ 120 sec # vv Tests less than 2m vv 'wallet_upgrade.py', # ~ 119 sec