From e8ec02577a2ca33337d3151886f5fdf57a870d14 Mon Sep 17 00:00:00 2001 From: "James C. Owens" Date: Wed, 27 Dec 2023 19:23:51 -0500 Subject: [PATCH] Implement protocol rule for maximum mandatory sidestake allocation total --- src/chainparams.cpp | 4 ++++ src/consensus/params.h | 4 ++++ src/gridcoin/contract/message.cpp | 20 +++++++++++++--- src/gridcoin/sidestake.cpp | 38 +++++++++++++++++++++++-------- src/gridcoin/sidestake.h | 8 ++++++- src/miner.cpp | 16 +++++++++++++ 6 files changed, 77 insertions(+), 13 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 257bf80803..f69ff806ce 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -74,6 +74,8 @@ class CMainParams : public CChainParams { consensus.InitialMRCFeeFractionPostZeroInterval = Fraction(2, 5); // Zero day interval is 14 days on mainnet consensus.MRCZeroPaymentInterval = 14 * 24 * 60 * 60; + // The maximum ratio of rewards that can be allocated to all of the mandatory sidestakes. + consensus.MaxMandatorySideStakeTotalAlloc = 0.25; // The "standard" contract replay lookback for those contract types // that do not have a registry db. consensus.StandardContractReplayLookback = 180 * 24 * 60 * 60; @@ -187,6 +189,8 @@ class CTestNetParams : public CChainParams { consensus.InitialMRCFeeFractionPostZeroInterval = Fraction(2, 5); // Zero day interval is 10 minutes on testnet. The very short interval facilitates testing. consensus.MRCZeroPaymentInterval = 10 * 60; + // The maximum ratio of rewards that can be allocated to all of the mandatory sidestakes. + consensus.MaxMandatorySideStakeTotalAlloc = 0.25; // The "standard" contract replay lookback for those contract types // that do not have a registry db. consensus.StandardContractReplayLookback = 180 * 24 * 60 * 60; diff --git a/src/consensus/params.h b/src/consensus/params.h index 83325378c7..10f5ab230f 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -47,6 +47,10 @@ struct Params { * forfeiture of fees to the staker and/or foundation. Only consensus critical at BlockV12Height or above. */ int64_t MRCZeroPaymentInterval; + /** + * @brief The maximum allocation (as a floating point) that can be used by all of the mandatory sidestakes + */ + double MaxMandatorySideStakeTotalAlloc; int64_t StandardContractReplayLookback; diff --git a/src/gridcoin/contract/message.cpp b/src/gridcoin/contract/message.cpp index 7309afeb68..a08ff23e4f 100644 --- a/src/gridcoin/contract/message.cpp +++ b/src/gridcoin/contract/message.cpp @@ -5,6 +5,7 @@ #include "amount.h" #include "gridcoin/contract/message.h" #include "gridcoin/contract/contract.h" +#include "gridcoin/sidestake.h" #include "script.h" #include "wallet/wallet.h" @@ -143,16 +144,29 @@ std::string SendContractTx(CWalletTx& wtx_new) if (balance < COIN || balance < burn_fee + nTransactionFee) { std::string strError = _("Balance too low to create a contract."); - LogPrintf("%s: %s", __func__, strError); + error("%s: %s", __func__, strError); return strError; } if (!CreateContractTx(wtx_new, reserve_key, burn_fee)) { std::string strError = _("Error: Transaction creation failed."); - LogPrintf("%s: %s", __func__, strError); + error("%s: %s", __func__, strError); return strError; } + for (const auto& pool_tx : mempool.mapTx) { + for (const auto& pool_tx_contract : pool_tx.second.GetContracts()) { + if (pool_tx_contract.m_type == GRC::ContractType::SIDESTAKE) { + std::string strError = _( + "Error: The mandatory sidestake transaction was rejected. " + "There is already a mandatory sidestake transaction in the mempool. " + "Wait until that transaction is bound in a block."); + error("%s: %s", __func__, strError); + return strError; + } + } + } + if (!pwalletMain->CommitTransaction(wtx_new, reserve_key)) { std::string strError = _( "Error: The transaction was rejected. This might happen if some of " @@ -160,7 +174,7 @@ std::string SendContractTx(CWalletTx& wtx_new) "a copy of wallet.dat and coins were spent in the copy but not " "marked as spent here."); - LogPrintf("%s: %s", __func__, strError); + error("%s: %s", __func__, strError); return strError; } diff --git a/src/gridcoin/sidestake.cpp b/src/gridcoin/sidestake.cpp index 2cffd1bcda..8017cc5c03 100644 --- a/src/gridcoin/sidestake.cpp +++ b/src/gridcoin/sidestake.cpp @@ -365,7 +365,7 @@ const std::vector SideStakeRegistry::SideStakeEntries() const } const std::vector SideStakeRegistry::ActiveSideStakeEntries(const bool& local_only, - const bool& include_zero_alloc) + const bool& include_zero_alloc) const { std::vector sidestakes; double allocation_sum = 0.0; @@ -382,7 +382,7 @@ const std::vector SideStakeRegistry::ActiveSideStakeEntries(const for (const auto& entry : m_mandatory_sidestake_entries) { if (entry.second->m_status == MandatorySideStake::MandatorySideStakeStatus::MANDATORY - && allocation_sum + entry.second->m_allocation <= 1.0) { + && allocation_sum + entry.second->m_allocation <= Params().GetConsensus().MaxMandatorySideStakeTotalAlloc) { if ((include_zero_alloc && entry.second->m_allocation == 0.0) || entry.second->m_allocation > 0.0) { sidestakes.push_back(std::make_shared(entry.second)); allocation_sum += entry.second->m_allocation; @@ -658,6 +658,17 @@ bool SideStakeRegistry::Validate(const Contract& contract, const CTransaction& t return false; } + double allocation = payload->m_entry.m_allocation; + + // Contracts that would result in a total active mandatory sidestake allocation greater than the maximum allowed by consensus + // protocol must be rejected. Note that this is not a perfect validation, because there could be more than one sidestake + // contract transaction in the memory pool, and this is using already committed sidestake contracts (i.e. in blocks already + // accepted) as a basis. + if (GetMandatoryAllocationsTotal() + allocation > Params().GetConsensus().MaxMandatorySideStakeTotalAlloc) { + DoS = 25; + return false; + } + return true; } @@ -793,13 +804,8 @@ void SideStakeRegistry::LoadLocalSideStakesFromConfig() } } - // First, determine allocation already taken by mandatory sidestakes, because they must be allocated first. - for (const auto& entry : SideStakeEntries()) { - if (entry->IsMandatory() - && std::get(entry->GetStatus()) == MandatorySideStake::MandatorySideStakeStatus::MANDATORY) { - dSumAllocation += entry->GetAllocation(); - } - } + // First, add the allocation already taken by mandatory sidestakes, because they must be allocated first. + dSumAllocation += GetMandatoryAllocationsTotal(); LOCK(cs_lock); @@ -919,6 +925,20 @@ bool SideStakeRegistry::SaveLocalSideStakesToConfig() return status; } +double SideStakeRegistry::GetMandatoryAllocationsTotal() const +{ + std::vector sidestakes = ActiveSideStakeEntries(false, false); + double allocation_total = 0.0; + + for (const auto& entry : sidestakes) { + if (entry->IsMandatory()) { + allocation_total += entry->GetAllocation(); + } + } + + return allocation_total; +} + void SideStakeRegistry::SubscribeToCoreSignals() { uiInterface.RwSettingsUpdated_connect(std::bind(RwSettingsUpdated, this)); diff --git a/src/gridcoin/sidestake.h b/src/gridcoin/sidestake.h index cf6eab9ae9..8c8a222097 100644 --- a/src/gridcoin/sidestake.h +++ b/src/gridcoin/sidestake.h @@ -558,7 +558,7 @@ class SideStakeRegistry : public IContractHandler //! //! \return A vector of smart pointers to sidestake entries. //! - const std::vector ActiveSideStakeEntries(const bool& local_only, const bool& include_zero_alloc); + const std::vector ActiveSideStakeEntries(const bool& local_only, const bool& include_zero_alloc) const; //! //! \brief Get the current sidestake entry for the specified destination. @@ -746,6 +746,12 @@ class SideStakeRegistry : public IContractHandler //! bool SaveLocalSideStakesToConfig(); + //! + //! \brief Provides the total allocation for all active mandatory sidestakes as a floating point fraction. + //! \return total active mandatory sidestake allocation as a double. + //! + double GetMandatoryAllocationsTotal() const; + void SubscribeToCoreSignals(); void UnsubscribeFromCoreSignals(); diff --git a/src/miner.cpp b/src/miner.cpp index 13f68de631..d53a2951d5 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -337,6 +337,10 @@ bool CreateRestOfTheBlock(CBlock &block, CBlockIndex* pindexPrev, const GRC::ResearcherPtr researcher = GRC::Researcher::Get(); const GRC::CpidOption cpid = researcher->Id().TryCpid(); + // This boolean will be used to ensure that there is only one mandatory sidestake transaction bound into a block. This + // in combination with the transaction level validation for the maximum mandatory allocation perfects that rule. + bool mandatory_sidestake_bound = false; + // Largest block you're willing to create: unsigned int nBlockMaxSize = gArgs.GetArg("-blockmaxsize", MAX_BLOCK_SIZE_GEN/2); // Limit to between 1K and MAX_BLOCK_SIZE-1K for sanity: @@ -601,6 +605,18 @@ bool CreateRestOfTheBlock(CBlock &block, CBlockIndex* pindexPrev, } //TryCpid() } // output limit } // contract type is MRC + + // If a mandatory sidestake contract has not already been bound into the block, then set mandatory_sidestake_bound + // to true. The ignore_transaction flag is still false, so this mandatory sidestake contract will be bound into the + // block. Any more mandatory sidestakes in the transaction loop will be ignored because the ignore_transaction flag + // will be set to true in the second and succeeding iterations in the loop. + if (contract.m_type == GRC::ContractType::SIDESTAKE) { + if (!mandatory_sidestake_bound) { + mandatory_sidestake_bound = true; + } else { + ignore_transaction = true; + } + } // contract type is SIDESTAKE } // contracts not empty if (ignore_transaction) continue;