Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

polls, voting, staking: Enhance voting consensus rules to allow for changeable magnitude weight factor and enhance CBR #2781

Open
wants to merge 20 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
c14b4fd
Add several parameters to blockchain consensus
jamescowens Oct 22, 2024
b92139d
Extend fraction class to construct from string
jamescowens Oct 22, 2024
a67deb8
Add TryLastBeforeTimestamp to Protocol Registry
jamescowens Oct 22, 2024
a9b3309
Rewrite GRC::GetConstantBlockReward
jamescowens Oct 22, 2024
56d4d1c
Introduce changeable magnitude weight factors for polling
jamescowens Oct 22, 2024
cdde86c
Additional changes to wire in magnitude weight factor
jamescowens Oct 23, 2024
d6db2a2
Fix for AVW not correct with modified magnitude weight factor
jamescowens Oct 24, 2024
5bf009b
Change WaitMessage() to PollWaitMessage()
jamescowens Oct 24, 2024
c337cdf
Document overflow analysis for magnitude weight factor as fraction
jamescowens Oct 25, 2024
00991d7
Ensure ResolveMagnitudeWeightFactor returns Fraction(100, 567) prior …
jamescowens Oct 25, 2024
3108194
Implement GRC::GetAvgNetworkWeight
jamescowens Oct 26, 2024
b0ed11e
Add thread safety keywords to difficulty functions
jamescowens Oct 27, 2024
76572b7
Fix segmentation fault in GetMagnitudeWeightFactor
jamescowens Oct 27, 2024
c30caf1
Ensure GetActiveNetworkWeight behavior is correct for all poll weight…
jamescowens Oct 27, 2024
6e27dc8
Implement protocol entry changeable magnitude unit
jamescowens Dec 16, 2024
68b6735
Correct contract version for protocol contracts for block V13+
jamescowens Dec 16, 2024
0c39f7f
Remove hardcoded accrual limit in UI warning tooltip
jamescowens Dec 18, 2024
d3b3a43
Enhance Fraction class FromString() and add tests
jamescowens Dec 28, 2024
c1a67bf
Add MaxMagnitudeUnit to chain parameters
jamescowens Dec 28, 2024
0ce7aeb
Enforce MaxMagnitudeUnit clamp in GetMagnitudeUnit()
jamescowens Dec 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 12 additions & 13 deletions src/chainparams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,17 +70,16 @@ class CMainParams : public CChainParams {
consensus.BlockV13Height = std::numeric_limits<int>::max();
consensus.PollV3Height = 2671700;
consensus.ProjectV2Height = 2671700;
// Immediately post zero payment interval fees 40% for mainnet
consensus.DefaultConstantBlockReward = 10 * COIN;
consensus.ConstantBlockRewardFloor = 0;
consensus.ConstantBlockRewardCeiling = 200 * COIN;
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 = Fraction(1, 4);
// The "standard" contract replay lookback for those contract types
// that do not have a registry db.
consensus.DefaultMagnitudeUnit = Fraction(1, 4);
consensus.MaxMagnitudeUnit = Fraction(5, 1);
consensus.DefaultMagnitudeWeightFactor = Fraction(100, 567);
consensus.StandardContractReplayLookback = 180 * 24 * 60 * 60;
// "standard" scrypt target limit for proof of work, results in 0,000244140625 proof-of-work difficulty.
// Equivalent to ~arith_uint256() >> 20 or 1e0fffff in compact notation.
consensus.powLimit = uint256S("00000fffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
/**
* The message start string is designed to be unlikely to occur in normal data.
Expand Down Expand Up @@ -187,16 +186,16 @@ class CTestNetParams : public CChainParams {
consensus.BlockV13Height = std::numeric_limits<int>::max();
consensus.PollV3Height = 1944820;
consensus.ProjectV2Height = 1944820;
// Immediately post zero payment interval fees 40% for testnet, the same as mainnet
consensus.DefaultConstantBlockReward = 10 * COIN;
consensus.ConstantBlockRewardFloor = 0;
consensus.ConstantBlockRewardCeiling = 100 * COIN;
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 = Fraction(1, 4);
// The "standard" contract replay lookback for those contract types
// that do not have a registry db.
consensus.DefaultMagnitudeUnit = Fraction(1, 4);
consensus.MaxMagnitudeUnit = Fraction(5, 1);
consensus.DefaultMagnitudeWeightFactor = Fraction(100, 567);
consensus.StandardContractReplayLookback = 180 * 24 * 60 * 60;
// Equivalent to ~arith_uint256() >> 16 or 1f00ffff in compact notation.
consensus.powLimit = uint256S("0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");

pchMessageStart[0] = 0xcd;
Expand Down
46 changes: 43 additions & 3 deletions src/consensus/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,25 @@ struct Params {
int PollV3Height;
/** Block height at which project v2 contracts are allowed */
int ProjectV2Height;
/**
* @brief The default GRC paid for a constant block reward.
*
* Note that the GRC paid for CBR can be specified by an administrative protocol entry with the key name "blockreward1" for
* V13+ blocks. The value is specified in HALFORDS.
*/
int64_t DefaultConstantBlockReward;
/**
* @brief The minimum GRC that can be set by administrative contract for a constant block reward (clamp floor). This is valid
* for block v13+. Note that this is typed as int64_t rather than CAmount to avoid the extra include.
*/
int64_t ConstantBlockRewardFloor;
/**
* @brief The maximum GRC that can be set by administrative contract for a constant block reward (clamp ceiling). This is valid
* for block v13+. Note that this is typed as int64_t rather than CAmount to avoid the extra include.
*/
int64_t ConstantBlockRewardCeiling;
/** The fraction of rewards taken as fees in an MRC after the zero payment interval. Only consesnus critical
* at BlockV12Height or above.
* at BlockV12Height or above. Note that this is typed as int64_t rather than CAmount to avoid the extra include.
*/
Fraction InitialMRCFeeFractionPostZeroInterval;
/** The amount of time from the last reward payment to a researcher where submitting an MRC will resort in 100%
Expand All @@ -51,9 +68,32 @@ struct Params {
* @brief The maximum allocation (as a Fraction) that can be used by all of the mandatory sidestakes
*/
Fraction MaxMandatorySideStakeTotalAlloc;

/**
* @brief The multiplier applied to network magnitude to determine the rate of accrual. Nominally 1/4 from Fern onwards.
*
* Note that the magnitude unit can be set by an administrative protocol entry with the key name "magnitudeunit" for
* V13+ blocks. The value is specified as a whole number or fraction. For example, 0.25 would be "1/4", 5 would be "5".
*/
Fraction DefaultMagnitudeUnit;
/**
* @brief The maximum magnitude unit allowed to be specified. This is an upper clamp that is set at 5.
*/
Fraction MaxMagnitudeUnit;
/**
* @brief The multiplier applied to (money supply / network magnitude) to scale the network magnitude into equivalent GRC
* for purposes of computing voting weight. Nominally 1 / 5.67 from Fern onwards.
*
* The magnitude weight factor can be set by an administrative protocol entry with the key name "magnitudeweightfactor" for
* V13+ blocks. The value is specified as a whole number or fraction. For example, 1 / 5.67 would be "100/567", 2 would be "2".
*/
Fraction DefaultMagnitudeWeightFactor;
/** The "standard" contract replay lookback for those contract types that do not have a registry db.
*/
int64_t StandardContractReplayLookback;

/**
* "standard" scrypt target limit for proof of work, results in 0,000244140625 proof-of-work difficulty.
* Equivalent to ~arith_uint256() >> 20 or 1e0fffff in compact notation.
*/
uint256 powLimit;
};
} // namespace Consensus
Expand Down
117 changes: 95 additions & 22 deletions src/gridcoin/accrual/snapshot.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
#include "gridcoin/accrual/computer.h"
#include "gridcoin/beacon.h"
#include "gridcoin/cpid.h"
#include "gridcoin/protocol.h"
#include "gridcoin/sidestake.h"
#include "gridcoin/superblock.h"
#include "gridcoin/support/block_finder.h"
#include "gridcoin/support/filehash.h"
#include "node/blockstorage.h"
#include "serialize.h"
Expand All @@ -29,18 +32,6 @@ namespace {
using namespace GRC;
using LogFlags = BCLog::LogFlags;

//!
//! \brief Numerator of the static magnitude unit coefficient for snapshot
//! accrual (block version 11 and greater).
//!
constexpr int64_t MAG_UNIT_NUMERATOR = 1;

//!
//! \brief Denominator of the static magnitude unit coefficient for snapshot
//! accrual (block version 11 and greater).
//!
constexpr int64_t MAG_UNIT_DENOMINATOR = 4;

//!
//! \brief Calculates the current accrual for a CPID by adding the snapshot of
//! accrued research rewards of the CPID's research account to rewards accrued
Expand All @@ -61,6 +52,86 @@ class SnapshotCalculator
{
}

//!
//! \brief Returns the current magnitude unit allocation fraction as of the provided superblock. Prior to block v13 this is
//! fixed at 1/4.
//!
//! The magnitude unit here will only change along superblock boundaries. Whatever the active value of the magnitude unit is
//! at the superblock provided will be used for accruals.
//!
//! \return Allocation Fraction representing Magnitude Unit.
//!
Allocation GetMagnitudeUnit() const
{
// Fern+ and before V13 magnitude unit is fixed at 1/4.
if (!IsV13Enabled(m_superblock.m_height)) {
return Allocation(1, 4);
}

Allocation magnitude_unit = Params().GetConsensus().DefaultMagnitudeUnit;
Allocation max_magnitude_unit = Params().GetConsensus().MaxMagnitudeUnit;

// Find the current protocol entry value for Magnitude Weight Factor, if it exists.
ProtocolEntryOption protocol_entry = GetProtocolRegistry().TryLastBeforeTimestamp("magnitudeunit", m_superblock.m_timestamp);

// If their is an entry prior or equal in timestamp to the superblock and it is active then set the magnitude unit
// to that value. If the last entry is not active (i.e. deleted), then leave at the default.
if (protocol_entry != nullptr && protocol_entry->m_status == ProtocolEntryStatus::ACTIVE) {
magnitude_unit = Fraction().FromString(protocol_entry->m_value);
}

// Clamp to MaxMagnitudeUnit if necessary
if (magnitude_unit > max_magnitude_unit) {
magnitude_unit = max_magnitude_unit;
LogPrintf("WARN: %s: Magnitude Unit specified by protocol is greater than %s. Clamping to %s.",
__func__,
max_magnitude_unit.ToString(),
max_magnitude_unit.ToString());
}

return magnitude_unit;
}

//!
//! \brief Static version of GetMagnitudeUnit used in Tally class.
//!
//! \param index for which to return the current magnitude unit.
//!
//! \return Allocation fraction representing magnitude unit.
//!
static Allocation GetMagnitudeUnit(CBlockIndex* index)
{
CBlockIndex* sb_index = BlockFinder::FindLatestSuperblock(index);

// Before V13 magnitude unit is 1/4.
if (!IsV13Enabled(sb_index->nHeight)) {
return Allocation(1, 4);
}

Allocation magnitude_unit = Params().GetConsensus().DefaultMagnitudeUnit;
Allocation max_magnitude_unit = Params().GetConsensus().MaxMagnitudeUnit;

// Find the current protocol entry value for Magnitude Weight Factor, if it exists.
ProtocolEntryOption protocol_entry = GetProtocolRegistry().TryLastBeforeTimestamp("magnitudeunit", sb_index->GetBlockTime());

// If their is an entry prior or equal in timestamp to the superblock and it is active then set the magnitude unit
// to that value. If the last entry is not active (i.e. deleted), then leave at the default.
if (protocol_entry != nullptr && protocol_entry->m_status == ProtocolEntryStatus::ACTIVE) {
magnitude_unit = Fraction().FromString(protocol_entry->m_value);
}

// Clamp to MaxMagnitudeUnit if necessary
if (magnitude_unit > max_magnitude_unit) {
magnitude_unit = max_magnitude_unit;
LogPrintf("WARN: %s: Magnitude Unit specified by protocol is greater than %s. Clamping to %s.",
__func__,
max_magnitude_unit.ToString(),
max_magnitude_unit.ToString());
}

return magnitude_unit;
}

//!
//! \brief Get the magnitude unit factored into the reward calculation.
//!
Expand All @@ -72,15 +143,15 @@ class SnapshotCalculator
//!
//! \return Amount paid per unit of magnitude per day in units of GRC.
//!
static double MagnitudeUnit()
double MagnitudeUnit() const
{
// Superblock-based accrual calculations do not rely on the rolling
// two-week network payment average. Instead, we calculate research
// rewards using the magnitude unit that represents the equilibrium
// quantity of the formula used to determine the magnitude unit for
// the legacy research age accrual calculations.
//
// Where...
// Where (prior to block v13) ...
//
// blocks_per_day = 960
// grc_per_block = 50
Expand All @@ -95,7 +166,8 @@ class SnapshotCalculator
//
// ...rounded-up to 0.25:
//
return static_cast<double>(MAG_UNIT_NUMERATOR) / MAG_UNIT_DENOMINATOR;
// V13+, the magnitude unit can be set by protocol entry.
return GetMagnitudeUnit().ToDouble();
}

//!
Expand Down Expand Up @@ -142,7 +214,7 @@ class SnapshotCalculator
//
const uint64_t base_accrual = accrual_timespan
* CurrentMagnitude(cpid).Scaled()
* MAG_UNIT_NUMERATOR;
* GetMagnitudeUnit().GetNumerator();

// If the accrual calculation will overflow a 64-bit integer, we need
// more bits. Arithmetic with the big integer type is much slower and
Expand All @@ -155,15 +227,15 @@ class SnapshotCalculator
accrual_bn *= COIN;
accrual_bn /= 86400;
accrual_bn /= Magnitude::SCALE_FACTOR;
accrual_bn /= MAG_UNIT_DENOMINATOR;
accrual_bn /= GetMagnitudeUnit().GetDenominator();

accrual = accrual_bn.GetLow64();
} else {
accrual = base_accrual
* COIN
/ 86400
/ Magnitude::SCALE_FACTOR
/ MAG_UNIT_DENOMINATOR;
/ GetMagnitudeUnit().GetDenominator();
}

LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: CPID = %s, LastRewardHeight() = %u, accrual_timespan = %" PRId64 ", "
Expand Down Expand Up @@ -263,7 +335,7 @@ class SnapshotAccrualComputer : public IAccrualComputer, SnapshotCalculator
// the amount of accrual that a CPID can collect over two days when the
// CPID achieves the maximum magnitude value supported in a superblock.
//
// Where...
// Where... (for block versions below V13)
//
// max_magnitude = 32767
// magnitude_unit = 0.25
Expand All @@ -272,9 +344,10 @@ class SnapshotAccrualComputer : public IAccrualComputer, SnapshotCalculator
//
// max_magnitude * magnitude_unit * 2 = max_accrual = 16383.5
//
// ...rounded-up to:
// ...rounded-up to 16384.
//
return 16384 * COIN;
// For V13+, the magnitude unit can be set by protocol entry.
return (GetMagnitudeUnit() * 32768 * 2 * COIN).ToCAmount();
}

//!
Expand All @@ -290,7 +363,7 @@ class SnapshotAccrualComputer : public IAccrualComputer, SnapshotCalculator
//!
double MagnitudeUnit() const override
{
return SnapshotCalculator::MagnitudeUnit();
return GetMagnitudeUnit().ToDouble();
}

int64_t AccrualAge() const override
Expand Down
28 changes: 21 additions & 7 deletions src/gridcoin/mrc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,27 @@ CAmount MRC::ComputeMRCFee() const
//
// If the MRC is exactly at the end of the zero_payout_interval, the fees are effectively
// fee_fraction * m_research_subsidy.
//
// Overflow analysis. The accrual limit in the snapshot computer is 16384. For a zero_payout_interval of 14 days,
// m_research_subsidy * zero_payout_interval = 19818086400. std::numeric_limits<int64_t>::max() / 19818086400
// = 465401747207, which means the numerator of the fee_fraction would have to be that number or higher to cause an
// overflow of the below. This is not likely to happen for any reasonable choice of the fee_fraction.
fee = m_research_subsidy * zero_payout_interval * fee_fraction.GetNumerator()
/ mrc_payment_interval / fee_fraction.GetDenominator();

// To accommodate V13+ blocks which can have much larger magnitude units (up to 5), we have changed the fee calculation
// to switch to 256 bit integer calculations if necessary similar to what is in AccrualDelta(). The rationale for the overflow
// test is as follows. The m_research_subsidy converted back to GRC (which divides by 100,000,000 and therefore rounds down) + 1
// to get the equivalent of rounding up, then multipled by the zero_payout_interval and the fee_fraction numerator is checked
// against the max numeric limit of CAmount divided by 100,000,000. If it is greater than this, then 256 bit integer arithmetic
// is used else the original calculation is used.
CAmount overflow_test = (m_research_subsidy / COIN + 1) * zero_payout_interval * fee_fraction.GetNumerator();

if (overflow_test > std::numeric_limits<CAmount>::max() / COIN) {
arith_uint256 fee_bn = m_research_subsidy;
fee_bn *= zero_payout_interval;
fee_bn *= fee_fraction.GetNumerator();
fee_bn /= mrc_payment_interval;
fee_bn /= fee_fraction.GetDenominator();

fee = fee_bn.GetLow64();
} else {
fee = m_research_subsidy * zero_payout_interval * fee_fraction.GetNumerator()
/ mrc_payment_interval / fee_fraction.GetDenominator();
}

return fee;
}
Expand Down
39 changes: 39 additions & 0 deletions src/gridcoin/protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,45 @@ ProtocolEntryOption ProtocolRegistry::TryActive(const std::string& key) const
return nullptr;
}

ProtocolEntryOption ProtocolRegistry::TryLastBeforeTimestamp(const std::string& key, const int64_t& timestamp)
{
LOCK(cs_lock);

// Find the latest protocol entry by key if it exists.
const auto iter = m_protocol_entries.find(key);

// If there is no current entry, return nullptr. If there is no current entry, then there is also no historical entry for the same
// key.
if (iter == m_protocol_entries.end()) {
return nullptr;
}

// If the latest (current) protocol entry for the given key has a timestamp before or equal to the provided timestamp then
// return the latest entry.
if (iter->second->m_timestamp <= timestamp) {
return iter->second;
}

// If the latest (current) active protocol entry does not satisfy the above conditions, and there is no preceding entry,
// then return nullptr.
if (iter->second->m_previous_hash.IsNull()) {
return nullptr;
}

// Walk the revision history chain for the protocol entries with the provided key to find an active historical entry
// with a timestamp that is equal to or less than the provided timestamp, if it exists.
HistoricalProtocolEntryMap::iterator hist_protocol_entry_iter = m_protocol_db.find(iter->second->m_previous_hash);

while (hist_protocol_entry_iter != m_protocol_db.end()
&& !hist_protocol_entry_iter->second->m_previous_hash.IsNull()
&& hist_protocol_entry_iter->second->m_timestamp > timestamp)
{
hist_protocol_entry_iter = m_protocol_db.find(hist_protocol_entry_iter->second->m_previous_hash);
}

return hist_protocol_entry_iter->second;
}

void ProtocolRegistry::Reset()
{
LOCK(cs_lock);
Expand Down
Loading
Loading