Skip to content

Commit

Permalink
Implement protocol rule for maximum mandatory sidestake allocation total
Browse files Browse the repository at this point in the history
  • Loading branch information
jamescowens committed Dec 28, 2023
1 parent 1341322 commit e8ec025
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 13 deletions.
4 changes: 4 additions & 0 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
20 changes: 17 additions & 3 deletions src/gridcoin/contract/message.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -143,24 +144,37 @@ 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 "
"the coins in your wallet were already spent, such as if you used "
"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;
}

Expand Down
38 changes: 29 additions & 9 deletions src/gridcoin/sidestake.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ const std::vector<SideStake_ptr> SideStakeRegistry::SideStakeEntries() const
}

const std::vector<SideStake_ptr> SideStakeRegistry::ActiveSideStakeEntries(const bool& local_only,
const bool& include_zero_alloc)
const bool& include_zero_alloc) const
{
std::vector<SideStake_ptr> sidestakes;
double allocation_sum = 0.0;
Expand All @@ -382,7 +382,7 @@ const std::vector<SideStake_ptr> 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<SideStake>(entry.second));
allocation_sum += entry.second->m_allocation;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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<MandatorySideStake::Status>(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);

Expand Down Expand Up @@ -919,6 +925,20 @@ bool SideStakeRegistry::SaveLocalSideStakesToConfig()
return status;
}

double SideStakeRegistry::GetMandatoryAllocationsTotal() const
{
std::vector<SideStake_ptr> 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));
Expand Down
8 changes: 7 additions & 1 deletion src/gridcoin/sidestake.h
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ class SideStakeRegistry : public IContractHandler
//!
//! \return A vector of smart pointers to sidestake entries.
//!
const std::vector<SideStake_ptr> ActiveSideStakeEntries(const bool& local_only, const bool& include_zero_alloc);
const std::vector<SideStake_ptr> ActiveSideStakeEntries(const bool& local_only, const bool& include_zero_alloc) const;

//!
//! \brief Get the current sidestake entry for the specified destination.
Expand Down Expand Up @@ -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();

Expand Down
16 changes: 16 additions & 0 deletions src/miner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit e8ec025

Please sign in to comment.