diff --git a/src/chainparams.cpp b/src/chainparams.cpp index a2d2d8d790..181db5cbe4 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -70,17 +70,16 @@ class CMainParams : public CChainParams { consensus.BlockV13Height = std::numeric_limits::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. @@ -187,16 +186,16 @@ class CTestNetParams : public CChainParams { consensus.BlockV13Height = std::numeric_limits::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; diff --git a/src/consensus/params.h b/src/consensus/params.h index b4e5e1a82e..6de6f3eba2 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -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% @@ -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 diff --git a/src/gridcoin/accrual/snapshot.h b/src/gridcoin/accrual/snapshot.h index b54549b5d7..78a43cb9c8 100644 --- a/src/gridcoin/accrual/snapshot.h +++ b/src/gridcoin/accrual/snapshot.h @@ -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" @@ -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 @@ -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. //! @@ -72,7 +143,7 @@ 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 @@ -80,7 +151,7 @@ class SnapshotCalculator // 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 @@ -95,7 +166,8 @@ class SnapshotCalculator // // ...rounded-up to 0.25: // - return static_cast(MAG_UNIT_NUMERATOR) / MAG_UNIT_DENOMINATOR; + // V13+, the magnitude unit can be set by protocol entry. + return GetMagnitudeUnit().ToDouble(); } //! @@ -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 @@ -155,7 +227,7 @@ 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 { @@ -163,7 +235,7 @@ class SnapshotCalculator * COIN / 86400 / Magnitude::SCALE_FACTOR - / MAG_UNIT_DENOMINATOR; + / GetMagnitudeUnit().GetDenominator(); } LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: CPID = %s, LastRewardHeight() = %u, accrual_timespan = %" PRId64 ", " @@ -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 @@ -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(); } //! @@ -290,7 +363,7 @@ class SnapshotAccrualComputer : public IAccrualComputer, SnapshotCalculator //! double MagnitudeUnit() const override { - return SnapshotCalculator::MagnitudeUnit(); + return GetMagnitudeUnit().ToDouble(); } int64_t AccrualAge() const override diff --git a/src/gridcoin/mrc.cpp b/src/gridcoin/mrc.cpp index ef8fcc7205..2250483a00 100644 --- a/src/gridcoin/mrc.cpp +++ b/src/gridcoin/mrc.cpp @@ -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::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::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; } diff --git a/src/gridcoin/protocol.cpp b/src/gridcoin/protocol.cpp index bd32a6838d..52336fd7e7 100644 --- a/src/gridcoin/protocol.cpp +++ b/src/gridcoin/protocol.cpp @@ -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); diff --git a/src/gridcoin/protocol.h b/src/gridcoin/protocol.h index 3e8f400e17..f3d302f7fc 100644 --- a/src/gridcoin/protocol.h +++ b/src/gridcoin/protocol.h @@ -460,6 +460,17 @@ class ProtocolRegistry : public IContractHandler //! ProtocolEntryOption TryActive(const std::string& key) const; + //! + //! \brief Get the last protocol entry for the specified key string at or before the specified timestamp + //! + //! \param key The key string of the protocol entry. + //! \param timestamp The desired timestamp to limit the find. + //! + //! \return An object that either contains a reference to some protocol entry if it exists + //! for the key at or before the provided timestamp or does not. + //! + ProtocolEntryOption TryLastBeforeTimestamp(const std::string& key, const int64_t& timestamp); + //! //! \brief Destroy the contract handler state in case of an error in loading //! the protocol entry registry state from LevelDB to prepare for reload from contract diff --git a/src/gridcoin/staking/difficulty.cpp b/src/gridcoin/staking/difficulty.cpp index b2cb8f097a..24426177c9 100644 --- a/src/gridcoin/staking/difficulty.cpp +++ b/src/gridcoin/staking/difficulty.cpp @@ -23,7 +23,7 @@ constexpr int64_t TARGET_TIMESPAN = 16 * 60; // 16 mins in seconds const arith_uint256 PROOF_OF_STAKE_LIMIT = ~arith_uint256() >> 20; // ppcoin: find last block index up to pindex -const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake) +const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfStake) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { while (pindex && pindex->pprev && (pindex->IsProofOfStake() != fProofOfStake)) pindex = pindex->pprev; @@ -35,7 +35,7 @@ const CBlockIndex* GetLastBlockIndex(const CBlockIndex* pindex, bool fProofOfSta // Functions // ----------------------------------------------------------------------------- -unsigned int GRC::GetNextTargetRequired(const CBlockIndex* pindexLast) +unsigned int GRC::GetNextTargetRequired(const CBlockIndex* pindexLast) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { if (pindexLast == nullptr) { return PROOF_OF_STAKE_LIMIT.GetCompact(); // genesis block @@ -89,7 +89,7 @@ unsigned int GRC::GetNextTargetRequired(const CBlockIndex* pindexLast) return bnNew.GetCompact(); } -double GRC::GetDifficulty(const CBlockIndex* blockindex) +double GRC::GetDifficulty(const CBlockIndex* blockindex) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { // Floating point number that is a multiple of the minimum difficulty, // minimum difficulty = 1.0. @@ -124,18 +124,18 @@ double GRC::GetBlockDifficulty(unsigned int nBits) return dDiff; } -double GRC::GetCurrentDifficulty() +double GRC::GetCurrentDifficulty() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return GetDifficulty(GetLastBlockIndex(pindexBest, true)); } -double GRC::GetTargetDifficulty() +double GRC::GetTargetDifficulty() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { return GetBlockDifficulty(GetNextTargetRequired(pindexBest)); } // This requires a lock on cs_main when called. -double GRC::GetAverageDifficulty(unsigned int nPoSInterval) +double GRC::GetAverageDifficulty(unsigned int nPoSInterval) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { /* * Diff is inversely related to Target (without the coinweight multiplier), but proportional to the @@ -182,7 +182,7 @@ double GRC::GetAverageDifficulty(unsigned int nPoSInterval) } // This requires a lock on cs_main when called. -double GRC::GetSmoothedDifficulty(int64_t nStakeableBalance) +double GRC::GetSmoothedDifficulty(int64_t nStakeableBalance) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { // The smoothed difficulty is derived via a two step process. There is a coupling between the desired block // span to compute difficulty and essentially the stakeable balance: If the balance is low, the ETTS is @@ -255,7 +255,7 @@ uint64_t GRC::GetStakeWeight(const CWallet& wallet) return weight; } -double GRC::GetEstimatedNetworkWeight(unsigned int nPoSInterval) +double GRC::GetEstimatedNetworkWeight(unsigned int nPoSInterval) EXCLUSIVE_LOCKS_REQUIRED(cs_main) { // The number of stakes to include in the average has been reduced to 40 (default) from 72. 72 stakes represented 1.8 hours at // standard spacing. This is too long. 40 blocks is nominally 1 hour. @@ -270,6 +270,98 @@ double GRC::GetEstimatedNetworkWeight(unsigned int nPoSInterval) return result; } +uint64_t GRC::GetAvgNetworkWeight(const unsigned int& block_interval, CBlockIndex* index_start) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + CBlockIndex* pindex = nullptr; + arith_uint256 weight_sum = 0; + unsigned int blocks = 0; + + auto block_net_weight = [](CBlockIndex* index) + { + arith_uint256 target; + + target.SetCompact(index->nBits); + + return ~arith_uint256() / arith_uint256(450) / target * arith_uint256(COIN); + }; + + if (index_start != nullptr) { + pindex = index_start; + } + + while (pindex && blocks < block_interval) + { + if (pindex->IsProofOfStake()) + { + weight_sum += block_net_weight(pindex); + ++blocks; + } + + pindex = pindex->pprev; + } + + return blocks ? (weight_sum / blocks).GetLow64() : 0; +} + +uint64_t GRC::GetAvgNetworkWeight(CBlockIndex* index_start, CBlockIndex* index_end) EXCLUSIVE_LOCKS_REQUIRED(cs_main) +{ + auto block_net_weight = [](CBlockIndex* index) + { + arith_uint256 target; + + target.SetCompact(index->nBits); + + return ~arith_uint256() / arith_uint256(450) / target * arith_uint256(COIN); + }; + + // These conditionals are ordered so that the no argument case quickly evaluates to the net weight of the current block. + if (index_end == nullptr) { + if (index_start == nullptr) { + return block_net_weight(pindexBest).GetLow64(); + } else { + return block_net_weight(index_start).GetLow64(); + } + } else { + if (index_start != nullptr) { + { + CBlockIndex* index; + arith_uint256 weight_sum; + unsigned int block_count = 0; + + // If we are here, both index_start and index_end are not nullptrs. + + if (!index_start->IsInMainChain()) { + throw std::invalid_argument("Specified start index is not in main chain."); + } + + if (!index_end->IsInMainChain()) { + throw std::invalid_argument("Specified end index is not in main chain."); + } + + if (index_start->nHeight >= index_end-> nHeight) { + throw std::invalid_argument("End index if specified must be at a higher height than start index."); + } + + for (index = index_start; index != index_end; index = index->pnext) + { + if (index->IsProofOfStake()) { + weight_sum += block_net_weight(index); + ++block_count; + } + } + + // Get last block for inclusive interval + weight_sum += block_net_weight(index->pnext); + ++block_count; + + return (weight_sum / arith_uint256(block_count)).GetLow64(); + } + } else { + throw std::invalid_argument("End index was specified with start index = nullptr."); + } + } +} + double GRC::GetEstimatedTimetoStake(bool ignore_staking_status, double dDiff, double dConfidence) { /* diff --git a/src/gridcoin/staking/difficulty.h b/src/gridcoin/staking/difficulty.h index a002d8ed90..5706a4c552 100644 --- a/src/gridcoin/staking/difficulty.h +++ b/src/gridcoin/staking/difficulty.h @@ -6,6 +6,7 @@ #ifndef GRIDCOIN_STAKING_DIFFICULTY_H #define GRIDCOIN_STAKING_DIFFICULTY_H +#include class CBlockIndex; class CWallet; #include @@ -26,6 +27,40 @@ double GetSmoothedDifficulty(int64_t nStakeableBalance); uint64_t GetStakeWeight(const CWallet& wallet); double GetEstimatedNetworkWeight(unsigned int nPoSInterval = 40); + +//! +//! \brief This returns the precise average network weight in units of Halfords as a 64 bit unsigned integer. This form takes +//! two arguments: the number of blocks to lookback and include in the average, and the ending point CBlockIndex pointer. Note +//! if the index pointer is not specified the lookback start is the current (best) block. +//! +//! Please refer to https://gridcoin.us/assets/docs/grc-bluepaper-section-1.pdf equations 1 and 16 and footnote 5. +//! This method of computing net_weight is from first principles using the target from the nBits representation +//! recorded in the index, rather than the GetEstimatedNetworkWeight() function, which uses double fp arithmetic. +//! +//! \param block_interval The number of blocks looking back from the index_start to include in the average. +//! \param index_start The CBlockIndex pointer to the starting index with which to take the average. +//! +//! \return uint64_t of the average network weight in Halford units. +//! +uint64_t GetAvgNetworkWeight(const unsigned int& block_interval, CBlockIndex* index_start = nullptr); + +//! +//! \brief This returns the precise average network weight in units of Halfords as a 64 bit unsigned integer. The starting index +//! and the ending index are defaulted to nullptr if not provided. If neither is provided, the network weight for the current (best) +//! block will be returned. If only the starting index pointer is provided, the network weight of that block will be returned. +//! If both are provided, the network weight average will be returned over the interval of blocks inclusive of both start and end. +//! Both indexes, if specified, must be in the main chain. +//! +//! Please refer to https://gridcoin.us/assets/docs/grc-bluepaper-section-1.pdf equations 1 and 16 and footnote 5. +//! This method of computing net_weight is from first principles using the target from the nBits representation +//! recorded in the index, rather than the GetEstimatedNetworkWeight() function, which uses double fp arithmetic. +//! +//! \param index_start The CBlockIndex pointer to the starting index with which to take the average. Note that this is inclusive. +//! \param index_end The BClockIndex pointer to the ending index with which to take the average. Note that this is inclusive. +//! +//! \return uint64_t of the average network weight in Halford units. +//! +uint64_t GetAvgNetworkWeight(CBlockIndex* index_start = nullptr, CBlockIndex* index_end = nullptr); double GetEstimatedTimetoStake(bool ignore_staking_status = false, double dDiff = 0.0, double dConfidence = DEFAULT_ETTS_CONFIDENCE); } // namespace GRC diff --git a/src/gridcoin/staking/reward.cpp b/src/gridcoin/staking/reward.cpp index 1055bed52a..ea72cf90ac 100644 --- a/src/gridcoin/staking/reward.cpp +++ b/src/gridcoin/staking/reward.cpp @@ -3,7 +3,6 @@ // file COPYING or https://opensource.org/licenses/mit-license.php. #include "amount.h" -#include "gridcoin/appcache.h" #include "gridcoin/protocol.h" #include "gridcoin/staking/reward.h" #include "main.h" @@ -33,25 +32,64 @@ CAmount GetCoinYearReward(int64_t nTime) CAmount GRC::GetConstantBlockReward(const CBlockIndex* index) { + CAmount reward = 0; + CAmount MIN_CBR = 0; + CAmount MAX_CBR = 0; + + // GetConstantBlockReward is called with a CBlockIndex pointer as an argument, which means it is expected to + // return the correct value at that position in the chain. The TryActive call in the protocol registry returns + // the CURRENT active value if that exists. This is not what we need if index is not actually pIndexBest. + // Here we find the last protocol entry in the historical linked list of protocol entries for the key "blockreward1" + // that has a timestamp that is less than or equal to the block time. This will filter for the correct historical + // value of blockreward1 that was valid at the index given. One could argue there is some fuzziness here in using + // the block time versus the transaction context time implied by the protocol registry entry; however the actual + // change of CBR must occur on a block boundary anyway, and as long as there is consistent application for consensus + // for V13+ we are safe. + ProtocolEntryOption reward_entry = GetProtocolRegistry().TryLastBeforeTimestamp("blockreward1", + index->GetBlockTime()); + // The constant block reward is set to a default, voted on value, but this can // be overridden using an admin message. This allows us to change the reward // amount without having to release a mandatory with updated rules. In the case - // there is a breach or leaked admin keys the rewards are clamped to twice that - // of the default value. - const CAmount MIN_CBR = 0; - const CAmount MAX_CBR = DEFAULT_CBR * 2; + // there is a breach or leaked admin keys the rewards are clamped. - CAmount reward = DEFAULT_CBR; - AppCacheEntry oCBReward = GetProtocolRegistry().GetProtocolEntryByKeyLegacy("blockreward1"); + // Note that the use of the IsV13Enabled test here on the block index does not consider the possibility of + // an administrative contract entry prior to v13 that has aged out from the lookback window which could affect consensus. + // However this possibility is actually covered because 5.4.8.0 was actually a mandatory due to a problem with + // message contracts, and this included the registry for protocol entries, eliminating the lookback window limitation. + // There has been no administrative entry to change the CBR in mainnet prior to 5.4.8.0, so in fact this is a moot issue. + if (IsV13Enabled(index->nHeight)) { + // The default and the clamp for block v13+ is controlled by blockchain consensus parameters. + reward = Params().GetConsensus().DefaultConstantBlockReward; + MIN_CBR = Params().GetConsensus().ConstantBlockRewardFloor; + MAX_CBR = Params().GetConsensus().ConstantBlockRewardCeiling; + + if (reward_entry != nullptr && reward_entry->m_status == ProtocolEntryStatus::ACTIVE) { + // There is no contract lookback limitation v13+. + if (!ParseInt64(reward_entry->m_value, &reward)) { + error("%s: Cannot parse constant block reward from protocol entry: %s", + __func__, reward_entry->m_value); + } + } + } else { + // This is the default and clamp pre block v13. Note that an administrative entry to change the CBR prior to block v13 + // that is outside the pre v13 clamp rules could cause the CBR to change at the v13 height as the new clamp rules above + // would then apply on the existing protocol entry. + reward = 10 * COIN; + MIN_CBR = 0; + MAX_CBR = Params().GetConsensus().DefaultConstantBlockReward * 2; - //TODO: refactor the expire checking to subroutine - //Note: time constant is same as GetBeaconPublicKey - if ((index->nTime - oCBReward.timestamp) <= (60 * 24 * 30 * 6 * 60)) { - // This is a little slippery, because if we ever change CAmount from a int64_t, this will - // break. It is unlikely to ever change, however, and avoids an extra copy/implicit cast. - if (!ParseInt64(oCBReward.value, &reward)) { - error("%s: Cannot parse constant block reward from protocol entry: %s", - __func__, oCBReward.value); + if (reward_entry != nullptr && reward_entry->m_status == ProtocolEntryStatus::ACTIVE) { + // The contract lookback window here for block version less than v13 is the same as the standard contract + // replay lookback. + if (index->nTime - reward_entry->m_timestamp <= Params().GetConsensus().StandardContractReplayLookback) { + // This is a little slippery, because if we ever change CAmount from a int64_t, this will + // break. It is unlikely to ever change, however, and avoids an extra copy/implicit cast. + if (!ParseInt64(reward_entry->m_value, &reward)) { + error("%s: Cannot parse constant block reward from protocol entry: %s", + __func__, reward_entry->m_value); + } + } } } diff --git a/src/gridcoin/support/block_finder.cpp b/src/gridcoin/support/block_finder.cpp index c31d5b06e1..6cea9a97ea 100644 --- a/src/gridcoin/support/block_finder.cpp +++ b/src/gridcoin/support/block_finder.cpp @@ -57,11 +57,13 @@ CBlockIndex* BlockFinder::FindByMinTime(int64_t time) } // The arguments are passed by value on purpose. -CBlockIndex* BlockFinder::FindByMinTimeFromGivenIndex(int64_t time, CBlockIndex* index) +CBlockIndex* BlockFinder::FindByMinTimeFromGivenIndex(int64_t time, CBlockIndex* const index_in) { + CBlockIndex* index = index_in; + // If no starting index is provided (i.e. second parameter is omitted or nullptr is passed in, // then start at the Genesis Block. This is in general expensive and should be avoided. - if (!index) { + if (index == nullptr) { index = pindexGenesisBlock; } @@ -71,3 +73,23 @@ CBlockIndex* BlockFinder::FindByMinTimeFromGivenIndex(int64_t time, CBlockIndex* return index; } + +CBlockIndex* BlockFinder::FindLatestSuperblock(CBlockIndex* const index_in) +{ + CBlockIndex* index = index_in; + + // If no input index is provided, start at the head of the chain. + if (index == nullptr) { + index = pindexBest; + } + + while (index && index->pprev) { + if (index->IsSuperblock()) { + break; + } + + index = index->pprev; + } + + return index; +} diff --git a/src/gridcoin/support/block_finder.h b/src/gridcoin/support/block_finder.h index 9d7ea552ef..4401f3852e 100644 --- a/src/gridcoin/support/block_finder.h +++ b/src/gridcoin/support/block_finder.h @@ -48,7 +48,14 @@ class BlockFinder //! \return CBlockIndex pointing to the youngest block which is not older than \p time, or //! the head of the chain if it is older than \p time. //! - static CBlockIndex* FindByMinTimeFromGivenIndex(int64_t time, CBlockIndex* index = nullptr); + static CBlockIndex* FindByMinTimeFromGivenIndex(int64_t time, CBlockIndex* const index_in = nullptr); + + //! + //! \brief Find the last superblock at or before the provided index. + //! \param CBlockIndex from where to start. + //! \return CBlockIndex pointing to the block containing the last superblock contract. + //! + static CBlockIndex* FindLatestSuperblock(CBlockIndex * const index_in = nullptr); }; } // namespace GRC diff --git a/src/gridcoin/tally.cpp b/src/gridcoin/tally.cpp index 0f92700364..48c6da144b 100644 --- a/src/gridcoin/tally.cpp +++ b/src/gridcoin/tally.cpp @@ -1063,10 +1063,10 @@ CAmount Tally::MaxEmission(const int64_t payment_time) return NetworkTally::MaxEmission(payment_time) * COIN; } -double Tally::GetMagnitudeUnit(const CBlockIndex* const pindex) +double Tally::GetMagnitudeUnit(CBlockIndex* const pindex) { if (pindex->nVersion >= 11) { - return SnapshotCalculator::MagnitudeUnit(); + return SnapshotCalculator::GetMagnitudeUnit(pindex).ToDouble(); } return g_network_tally.GetMagnitudeUnit(pindex->nTime); @@ -1158,7 +1158,9 @@ CAmount Tally::GetNewbieSuperblockAccrualCorrection(const Cpid& cpid, const Supe const auto tally_accrual_period = [&]( const int64_t low_time, const int64_t high_time, - const GRC::Magnitude magnitude) + const GRC::Magnitude magnitude, + const Allocation magnitude_unit, + CAmount max_reward) { int64_t time_interval = high_time - low_time; @@ -1175,7 +1177,7 @@ CAmount Tally::GetNewbieSuperblockAccrualCorrection(const Cpid& cpid, const Supe // correctly in the bignumber calculations. const uint64_t base_accrual = abs_time_interval * magnitude.Scaled() - * MAG_UNIT_NUMERATOR; + * magnitude_unit.GetNumerator(); int64_t period = 0; @@ -1184,7 +1186,7 @@ CAmount Tally::GetNewbieSuperblockAccrualCorrection(const Cpid& cpid, const Supe accrual_bn *= COIN; accrual_bn /= 86400; accrual_bn /= Magnitude::SCALE_FACTOR; - accrual_bn /= MAG_UNIT_DENOMINATOR; + accrual_bn /= magnitude_unit.GetDenominator(); period = accrual_bn.GetLow64() * (int64_t) sign; } @@ -1194,14 +1196,11 @@ CAmount Tally::GetNewbieSuperblockAccrualCorrection(const Cpid& cpid, const Supe * COIN / 86400 / Magnitude::SCALE_FACTOR - / MAG_UNIT_DENOMINATOR; + / magnitude_unit.GetDenominator(); } accrual += period; - // TODO: Change this to refer to MaxReward() from the snapshot computer. - int64_t max_reward = 16384 * COIN; - if (accrual > max_reward) { int64_t overage = accrual - max_reward; @@ -1267,7 +1266,12 @@ CAmount Tally::GetNewbieSuperblockAccrualCorrection(const Cpid& cpid, const Supe // Stop the accrual when we get to a superblock that is before the beacon advertisement. if (pindex->nTime < beacon_ptr->m_timestamp) break; - CAmount period = tally_accrual_period(pindex->nTime, pindex_high->nTime, magnitude); + // We are only going to use the accrual computer here for the magnitude unit and max_reward. + AccrualComputer computer = GetSnapshotComputer(cpid, pindex->GetBlockTime(), pindex); + + int64_t max_reward = computer->MaxReward(); + + CAmount period = tally_accrual_period(pindex->nTime, pindex_high->nTime, magnitude, computer->MagnitudeUnit(), max_reward); LogPrint(BCLog::LogFlags::ACCRUAL, "INFO %s: period_num = %u, " "low height = %i, high height = %u, magnitude at low height SB = %f, " diff --git a/src/gridcoin/tally.h b/src/gridcoin/tally.h index ed4b6c6b0a..5c73555dbb 100644 --- a/src/gridcoin/tally.h +++ b/src/gridcoin/tally.h @@ -99,7 +99,7 @@ class Tally //! //! \return Current magnitude unit adjusted for the specified block. //! - static double GetMagnitudeUnit(const CBlockIndex* const pindex); + static double GetMagnitudeUnit(CBlockIndex * const pindex); //! //! \brief Get a traversable object for the research accounts stored in diff --git a/src/gridcoin/voting/poll.cpp b/src/gridcoin/voting/poll.cpp index db197501eb..896fccedc0 100644 --- a/src/gridcoin/voting/poll.cpp +++ b/src/gridcoin/voting/poll.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. +#include "gridcoin/protocol.h" #include "main.h" #include "gridcoin/support/xml.h" #include "gridcoin/voting/poll.h" @@ -92,7 +93,12 @@ Poll::Poll() , m_weight_type(PollWeightType::UNKNOWN) , m_response_type(PollResponseType::UNKNOWN) , m_duration_days(0) + , m_title({}) + , m_url({}) + , m_question({}) + , m_choices(ChoiceList()) , m_timestamp(0) + , m_magnitude_weight_factor(Fraction()) { } @@ -105,7 +111,8 @@ Poll::Poll( std::string url, std::string question, ChoiceList choices, - int64_t timestamp) + int64_t timestamp, + Fraction magnitude_weight_factor) : m_type(type) , m_weight_type(weight_type) , m_response_type(response_type) @@ -115,6 +122,7 @@ Poll::Poll( , m_question(std::move(question)) , m_choices(std::move(choices)) , m_timestamp(timestamp) + , m_magnitude_weight_factor(magnitude_weight_factor) { } @@ -130,7 +138,8 @@ Poll Poll::Parse(const std::string& title, const std::string& contract) ExtractXML(contract, "", ""), ExtractXML(contract, "", ""), ParseChoices(ExtractXML(contract, "", "")), - 0); + 0, + Params().GetConsensus().DefaultMagnitudeWeightFactor); } bool Poll::WellFormed() const @@ -206,6 +215,31 @@ int64_t Poll::Expiration() const return m_timestamp + (m_duration_days * 86400); } +Fraction Poll::ResolveMagnitudeWeightFactor(CBlockIndex *index) const +{ + if (index == nullptr) { + return Fraction(); + } + + Fraction magnitude_weight_factor = Params().GetConsensus().DefaultMagnitudeWeightFactor; + + // Before V13 magnitude weight factor is 1 / 5.67. + if (!IsV13Enabled(index->nHeight)) { + return Fraction(100, 567); + } + + // Find the current protocol entry value for Magnitude Weight Factor, if it exists. + ProtocolEntryOption protocol_entry = GetProtocolRegistry().TryLastBeforeTimestamp("magnitudeweightfactor", m_timestamp); + + // If their is an entry prior or equal in timestemp to the start of the poll and it is active then set the magnitude weight + // factor 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_weight_factor = Fraction().FromString(protocol_entry->m_value); + } + + return magnitude_weight_factor; +} + const Poll::ChoiceList& Poll::Choices() const { if (m_response_type == PollResponseType::YES_NO_ABSTAIN) { diff --git a/src/gridcoin/voting/poll.h b/src/gridcoin/voting/poll.h index 1decaae541..f88044eea6 100644 --- a/src/gridcoin/voting/poll.h +++ b/src/gridcoin/voting/poll.h @@ -9,6 +9,7 @@ #include "gridcoin/voting/fwd.h" #include "serialize.h" #include "uint256.h" +#include "util.h" #include #include @@ -398,7 +399,8 @@ class Poll AdditionalFieldList m_additional_fields; //!< The set of additional fields for the poll. // Memory only: - int64_t m_timestamp; //!< Time of the poll's containing transaction. + int64_t m_timestamp; //!< Time of the poll's containing transaction. + Fraction m_magnitude_weight_factor; //!< Magnitude weight factor for the poll (defined at poll start). //! //! \brief Initialize an empty, invalid poll instance. @@ -408,26 +410,27 @@ class Poll //! //! \brief Initialize a poll instance with data from a contract. //! - //! \param type Type of the poll. - //! \param weight_type Method used to weigh votes. - //! \param response_type Method for choosing poll answers. - //! \param duration_days Number of days the poll remains active. - //! \param title UTF-8 title of the poll. - //! \param url UTF-8 URL of the poll discussion webpage. - //! \param question UTF-8 prompt that voters shall answer. - //! \param choices The set of possible answers to the poll. - //! \param timestamp Timestamp of the poll's containing transaction. - //! - Poll( - PollType type, - PollWeightType weight_type, - PollResponseType response_type, - uint32_t duration_days, - std::string title, - std::string url, - std::string question, - ChoiceList choices, - int64_t timestamp); + //! \param type Type of the poll. + //! \param weight_type Method used to weigh votes. + //! \param response_type Method for choosing poll answers. + //! \param duration_days Number of days the poll remains active. + //! \param title UTF-8 title of the poll. + //! \param url UTF-8 URL of the poll discussion webpage. + //! \param question UTF-8 prompt that voters shall answer. + //! \param choices The set of possible answers to the poll. + //! \param timestamp Timestamp of the poll's containing transaction. + //! \param magnitude_weight_factor The magnitude weight factor for the poll, valid at poll start. + //! + Poll(PollType type, + PollWeightType weight_type, + PollResponseType response_type, + uint32_t duration_days, + std::string title, + std::string url, + std::string question, + ChoiceList choices, + int64_t timestamp, + Fraction magnitude_weight_factor); //! //! \brief Initialize a poll instance from a contract that contains poll @@ -505,6 +508,16 @@ class Poll //! int64_t Expiration() const; + //! + //! \brief Fetch the applicable magnitude factor for the poll. This is the magnitude factor of the last entry in the + //! protocol registry database that has a timestamp at or before the poll start timestamp. + //! + //! \param index CBlockIndex of block containing poll transaction (poll start). + //! + //! \return Fraction Magnitude factor expressed as a Fraction. + //! + Fraction ResolveMagnitudeWeightFactor(CBlockIndex* index) const; + //! //! \brief Get the set of possible answers to the poll. //! diff --git a/src/gridcoin/voting/registry.cpp b/src/gridcoin/voting/registry.cpp index 5c016d8a2d..d6e745486b 100644 --- a/src/gridcoin/voting/registry.cpp +++ b/src/gridcoin/voting/registry.cpp @@ -331,6 +331,7 @@ PollReference::PollReference() , m_timestamp(0) , m_duration_days(0) , m_votes({}) + , m_magnitude_weight_factor(Fraction()) { } @@ -346,7 +347,22 @@ PollOption PollReference::TryReadFromDisk(CTxDB& txdb) const for (auto& contract : tx.PullContracts()) { if (contract.m_type == ContractType::POLL) { auto payload = contract.PullPayloadAs(); - payload.m_poll.m_timestamp = m_timestamp; + // The time for the poll is the time of the containing transaction. + payload.m_poll.m_timestamp = tx.nTime; + m_timestamp = tx.nTime; + + // This is a critical initialization, because the magnitude weight factor is only stored + // in memory and is not serialized. + payload.m_poll.m_magnitude_weight_factor = payload.m_poll.ResolveMagnitudeWeightFactor(GetStartingBlockIndexPtr()); + m_magnitude_weight_factor = payload.m_poll.m_magnitude_weight_factor; + m_weight_type = payload.m_poll.m_weight_type.Value(); + + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: reference.m_timestamp = %" PRId64 " , poll.m_timestamp = %" PRId64 " , " + "poll.m_magnitude_weight_factor = %s", + __func__, + m_timestamp, + payload.m_poll.m_timestamp, + payload.m_poll.m_magnitude_weight_factor.ToString()); return std::move(payload.m_poll); } @@ -476,6 +492,11 @@ std::optional PollReference::GetEndingHeight() const EXCLUSIVE_LOCKS_REQUIR return std::nullopt; } +Fraction PollReference::GetMagnitudeWeightFactor() const +{ + return m_magnitude_weight_factor; +} + std::optional PollReference::GetActiveVoteWeight(const PollResultOption& result) const { // Instrument this so we can log real time performance. @@ -507,6 +528,36 @@ std::optional PollReference::GetActiveVoteWeight(const PollResultOption __func__, pindex_end->nHeight); } + arith_uint256 active_vote_weight_tally = 0; + unsigned int blocks = 0; + + // If poll weight type is balance only, then simply sum up the netweights over the block range of the poll. + if (m_weight_type == PollWeightType::BALANCE) { + for (CBlockIndex* pindex = pindex_start; pindex ; pindex = pindex->pnext) { + + arith_uint256 net_weight = GetAvgNetworkWeight(pindex); + + active_vote_weight_tally += net_weight; + ++blocks; + + if (pindex == pindex_end) { + break; + } + } + + if (!blocks) { + return std::nullopt; + } + + return (active_vote_weight_tally / blocks).GetLow64(); + + // If it is not balance only (handled by above), and is not BALANCE_AND_MAGNITUDE (everything else) then return nullopt. + // In reality all weight types other than BALANCE and BALANCE_AND_MAGNITUDE are deprecated in Fern onwards, so this + // only applies for legacy polls. + } else if (m_weight_type != PollWeightType::BALANCE_AND_MAGNITUDE) { + return std::nullopt; + } + // determine the pools that did NOT vote in the poll (via the result passed in). Only pools that did not // vote contribute to the magnitude correction for pools. std::vector pools_not_voting; @@ -536,10 +587,8 @@ std::optional PollReference::GetActiveVoteWeight(const PollResultOption // Note we must use bignums here, because the second term of the active_vote_weight_tally will overflow // otherwise. We are also avoiding floating point calculations, because avw will be used in consensus rules in // the future. - arith_uint256 active_vote_weight_tally = 0; arith_uint256 scaled_pool_magnitude = 0; arith_uint256 scaled_network_magnitude = 0; - unsigned int blocks = 0; // Lambda for active_vote_weight tally. const auto tally_active_vote_weight = [&]( @@ -548,9 +597,14 @@ std::optional PollReference::GetActiveVoteWeight(const PollResultOption const arith_uint256 scaled_pool_magnitude, const arith_uint256 scaled_network_magnitude) { + // Unlike the complementary calculation in EnableMagnitudeWeight for vote weights, this calculation is not + // subject to overflow because it uses 256 bit integers. In the end it the RESULT is cast to a 64 bit integer, but + // by then all of the multiplication and division are done. m_magnitude_weight_factor as a fraction something that is + // not expected to stray any farther than the interval [1/10, 1/1], where the current default value of 100 / 567 is + // in that interval. So we should never have an overflow problem here. active_vote_weight_tally += net_weight + money_supply * (scaled_network_magnitude - scaled_pool_magnitude) - * arith_uint256(100) - / arith_uint256(567) + * arith_uint256(m_magnitude_weight_factor.GetNumerator()) + / arith_uint256(m_magnitude_weight_factor.GetDenominator()) / scaled_network_magnitude; ++blocks; @@ -632,13 +686,7 @@ std::optional PollReference::GetActiveVoteWeight(const PollResultOption } } - // Please refer to https://gridcoin.us/assets/docs/grc-bluepaper-section-1.pdf equations 1 and 16 and footnote 5. - // This method of computing net_weight is from first principles using the target from the nBits representation - // recorded in the index, rather than the GetEstimatedNetworkWeight() function, which uses double fp arithmetic. - arith_uint256 target; - target.SetCompact(pindex->nBits); - - arith_uint256 net_weight = ~arith_uint256() / arith_uint256(450) / target * arith_uint256(COIN); + arith_uint256 net_weight = GetAvgNetworkWeight(pindex); arith_uint256 money_supply = pindex->nMoneySupply; @@ -647,13 +695,15 @@ std::optional PollReference::GetActiveVoteWeight(const PollResultOption // If voting logging category is active, log the first block and every superblock if (blocks == 1 || pindex->IsSuperblock()) { LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: tally_active_vote_weight: net_weight = %f, money_supply = %f, " - "pool_magnitude = %f, network_magnitude = %f, block height = %i, " - "blocks = %u, active_vote_weight_tally = %f, active_vote_weight = %f.", + "pool_magnitude = %f, network_magnitude = %f, magnitude weight factor = %s, " + "block height = %i, blocks = %u, active_vote_weight_tally = %f, " + "active_vote_weight = %f.", __func__, net_weight.getdouble() / (double) COIN, money_supply.getdouble() / (double) COIN, scaled_pool_magnitude.getdouble() / 100.0, scaled_network_magnitude.getdouble() / 100.0, + m_magnitude_weight_factor.ToString(), pindex->nHeight, blocks, active_vote_weight_tally.getdouble() / (double) COIN, @@ -664,13 +714,15 @@ std::optional PollReference::GetActiveVoteWeight(const PollResultOption // Log the last block and break if at pindex_end. if (pindex == pindex_end) { LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: tally_active_vote_weight: net_weight = %f, money_supply = %f, " - "pool_magnitude = %f, network_magnitude = %f, block height = %i, " - "blocks = %u, active_vote_weight_tally = %f, active_vote_weight = %f.", + "pool_magnitude = %f, network_magnitude = %f, magnitude weight factor = %s, " + "block height = %i, blocks = %u, active_vote_weight_tally = %f, " + "active_vote_weight = %f.", __func__, net_weight.getdouble() / (double) COIN, money_supply.getdouble() / (double) COIN, scaled_pool_magnitude.getdouble() / 100.0, scaled_network_magnitude.getdouble() / 100.0, + m_magnitude_weight_factor.ToString(), pindex->nHeight, blocks, active_vote_weight_tally.getdouble() / (double) COIN, @@ -1010,6 +1062,8 @@ void PollRegistry::AddPoll(const ContractContext& ctx) EXCLUSIVE_LOCKS_REQUIRED( auto result_pair = m_polls_by_txid.emplace(ctx.m_tx.GetHash(), &poll_ref); poll_ref.m_txid = result_pair.first->first; + poll_ref.m_magnitude_weight_factor = payload->m_poll.ResolveMagnitudeWeightFactor(poll_ref.GetStartingBlockIndexPtr()); + if (fQtActive && !poll_ref.Expired(GetAdjustedTime())) { uiInterface.NewPollReceived(poll_ref.Time()); } diff --git a/src/gridcoin/voting/registry.h b/src/gridcoin/voting/registry.h index 0be8eedb60..dff16f2d68 100644 --- a/src/gridcoin/voting/registry.h +++ b/src/gridcoin/voting/registry.h @@ -10,6 +10,7 @@ #include "gridcoin/voting/fwd.h" #include "sync.h" #include "uint256.h" +#include "util.h" #include #include @@ -152,6 +153,13 @@ class PollReference //! std::optional GetEndingHeight() const; + //! + //! \brief Get the magnitude weight factor at the poll start. + //! + //! \return Fraction representing magnitude weight factor. + //! + Fraction GetMagnitudeWeightFactor() const; + //! //! \brief Computes the Active Vote Weight for the poll, which is used to determine whether the poll is validated. //! \param result: The actual tabulated votes (poll result) @@ -175,14 +183,16 @@ class PollReference void UnlinkVote(const uint256 txid); private: - uint256 m_txid; //!< Hash of the poll transaction. - uint32_t m_payload_version; //!< Version of the poll (payload). - PollType m_type; //!< Type of the poll. - const std::string* m_ptitle; //!< Title of the poll for indexing/mapping purposes. - std::string m_title; //!< Original title of the poll for display purposes. - int64_t m_timestamp; //!< Timestamp of the poll transaction. - uint32_t m_duration_days; //!< Number of days the poll remains active. - std::vector m_votes; //!< Hashes of the linked vote transactions. + uint256 m_txid; //!< Hash of the poll transaction. + uint32_t m_payload_version; //!< Version of the poll (payload). + PollType m_type; //!< Type of the poll. + mutable PollWeightType m_weight_type; //!< Weight type of the poll. + const std::string* m_ptitle; //!< Title of the poll for indexing/mapping purposes. + std::string m_title; //!< Original title of the poll for display purposes. + mutable int64_t m_timestamp; //!< Timestamp of the poll transaction. + uint32_t m_duration_days; //!< Number of days the poll remains active. + std::vector m_votes; //!< Hashes of the linked vote transactions. + mutable Fraction m_magnitude_weight_factor; //!< Magnitude weight factor for the poll (defined at poll start). }; // PollReference //! diff --git a/src/gridcoin/voting/result.cpp b/src/gridcoin/voting/result.cpp index 962cf21303..0ec3fe2ae8 100644 --- a/src/gridcoin/voting/result.cpp +++ b/src/gridcoin/voting/result.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or https://opensource.org/licenses/mit-license.php. +#include "gridcoin/protocol.h" #include "main.h" #include "gridcoin/beacon.h" #include "gridcoin/quorum.h" @@ -796,7 +797,22 @@ class VoteCounter } // Use integer arithmetic to avoid floating-point discrepancies: - m_magnitude_factor = supply / total_mag * 100 / 567; + + // Overflow analysis: + // + // Current supply as of block 3404576 = 493424753. + // network magnitude = 115000 + // max value for uint64_t = 2^64 - 1 + // + // 2^64 - 1 >= (493424753 / 115000) * mwf numerator + // + // This means mwf numerator <= (2^64 - 1) * (115000 / 493424753) + // ~<= 4.3E15 + // Even if money supply is approximately doubled we can safely handle 2E15, which means 15 decimal + // places in the fraction numerator + m_magnitude_factor = supply / total_mag + * (uint64_t) m_poll.m_magnitude_weight_factor.GetNumerator() + / (uint64_t) m_poll.m_magnitude_weight_factor.GetDenominator(); m_resolver.SetSuperblock(std::move(superblock)); } @@ -1133,6 +1149,7 @@ CAmount ResolveMoneySupplyForPoll(const Poll& poll) return pindex->nMoneySupply; } + } // Anonymous namespace // ----------------------------------------------------------------------------- @@ -1194,6 +1211,11 @@ PollResultOption PollResult::BuildFor(const PollReference& poll_ref) counter.CountVotes(result, poll_ref.Votes()); + LogPrint(BCLog::LogFlags::VOTE, "INFO: %s: poll_ref.Time() = %" PRId64 " poll.GetMagnitudeWeightFactor() = %s", + __func__, + poll_ref.Time(), + poll_ref.GetMagnitudeWeightFactor().ToString()); + if (auto active_vote_weight = poll_ref.GetActiveVoteWeight(result)) { result.m_active_vote_weight = active_vote_weight; diff --git a/src/main.h b/src/main.h index e297464d9f..051fa53b6e 100644 --- a/src/main.h +++ b/src/main.h @@ -47,8 +47,6 @@ class MRC; typedef std::optional ClaimOption; } -static const int64_t DEFAULT_CBR = 10 * COIN; - /** Threshold for nLockTime: below this value it is interpreted as block number, otherwise as UNIX timestamp. */ static const unsigned int LOCKTIME_THRESHOLD = 500000000; // Tue Nov 5 00:53:20 1985 UTC diff --git a/src/qt/forms/overviewpage.ui b/src/qt/forms/overviewpage.ui index 6e925557b6..310e37d5c3 100644 --- a/src/qt/forms/overviewpage.ui +++ b/src/qt/forms/overviewpage.ui @@ -878,7 +878,7 @@ - You are approaching the accrual limit of 16384 GRC. If you have a relatively low balance, you should request payment via MRC so that you do not lose earned rewards. + You are approaching the accrual limit. If you have a relatively low balance, you should request payment via MRC so that you do not lose earned rewards. diff --git a/src/qt/voting/polltab.cpp b/src/qt/voting/polltab.cpp index 46f09e9708..77a8176438 100644 --- a/src/qt/voting/polltab.cpp +++ b/src/qt/voting/polltab.cpp @@ -25,14 +25,14 @@ QString RefreshMessage() return QCoreApplication::translate("PollTab", "Press \"Refresh\" to update the list."); } -QString WaitMessage() +QString PollWaitMessage() { return QCoreApplication::translate("PollTab", "This may take several minutes."); } QString FullRefreshMessage() { - return QStringLiteral("%1 %2").arg(RefreshMessage()).arg(WaitMessage()); + return QStringLiteral("%1 %2").arg(RefreshMessage()).arg(PollWaitMessage()); } } // Anonymous namespace @@ -188,7 +188,7 @@ void PollTab::refresh() { if (m_polltable_model->empty()) { m_no_result->showDefaultLoadingTitle(); - m_no_result->contentWidgetAs()->setText(WaitMessage()); + m_no_result->contentWidgetAs()->setText(PollWaitMessage()); } m_loading->start(); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 69d73a2a39..3b9e743e18 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -57,7 +57,7 @@ UniValue MRCToJson(const GRC::MRC& mrc) { return json; } -UniValue ClaimToJson(const GRC::Claim& claim, const CBlockIndex* const pindex) +UniValue ClaimToJson(const GRC::Claim& claim, CBlockIndex* pindex) { UniValue json(UniValue::VOBJ); @@ -155,7 +155,7 @@ UniValue SuperblockToJson(const GRC::SuperblockPtr& superblock) return SuperblockToJson(*superblock); } -UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool fPrintTransactionDetail) +UniValue blockToJSON(const CBlock& block, CBlockIndex* blockindex, bool fPrintTransactionDetail) { UniValue result(UniValue::VOBJ); @@ -2432,14 +2432,32 @@ UniValue addkey(const UniValue& params, bool fHelp) break; } case GRC::ContractType::PROTOCOL: - // There will be no legacy payload contracts past version 2. This will need to be changed before the - // block v13 mandatory (which also means contract v3). - contract = GRC::MakeLegacyContract( - type.Value(), - action, - params[2].get_str(), // key - params[3].get_str()); // value + { + if (block_v13_enabled) { + GRC::ProtocolEntryStatus status = GRC::ProtocolEntryStatus::UNKNOWN; + + if (action == GRC::ContractAction::ADD) { + status = GRC::ProtocolEntryStatus::ACTIVE; + } else if (action == GRC::ContractAction::REMOVE) { + status = GRC::ProtocolEntryStatus::DELETED; + } + + contract = GRC::MakeContract( + contract_version, + action, + uint32_t {2}, // Contract payload version number + params[2].get_str(), // key + params[3].get_str(), // value + status); + } else { + contract = GRC::MakeLegacyContract( + type.Value(), + action, + params[2].get_str(), // key + params[3].get_str()); // value + } break; + } case GRC::ContractType::SIDESTAKE: { if (block_v13_enabled) { diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 23c318c659..adc094b752 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -375,7 +375,9 @@ UniValue auditsnapshotaccrual(const UniValue& params, bool fHelp) const uint64_t height, const int64_t low_time, const int64_t high_time, - const int64_t claimed) + const int64_t claimed, + const Allocation magnitude_unit, + CAmount max_reward) { const GRC::Magnitude magnitude = superblock->m_cpids.MagnitudeOf(*cpid); @@ -393,7 +395,7 @@ UniValue auditsnapshotaccrual(const UniValue& params, bool fHelp) // correctly in the bignumber calculations. const uint64_t base_accrual = abs_time_interval * magnitude.Scaled() - * MAG_UNIT_NUMERATOR; + * magnitude_unit.GetNumerator(); int64_t period = 0; @@ -402,7 +404,7 @@ UniValue auditsnapshotaccrual(const UniValue& params, bool fHelp) accrual_bn *= COIN; accrual_bn /= 86400; accrual_bn /= Magnitude::SCALE_FACTOR; - accrual_bn /= MAG_UNIT_DENOMINATOR; + accrual_bn /= magnitude_unit.GetDenominator(); period = accrual_bn.GetLow64() * (int64_t) sign; } @@ -412,14 +414,11 @@ UniValue auditsnapshotaccrual(const UniValue& params, bool fHelp) * COIN / 86400 / Magnitude::SCALE_FACTOR - / MAG_UNIT_DENOMINATOR; + / magnitude_unit.GetDenominator(); } accrual += period; - // TODO: Change this to refer to MaxReward() from the snapshot computer. - int64_t max_reward = 16384 * COIN; - if (accrual > max_reward) { int64_t overage = accrual - max_reward; @@ -450,13 +449,21 @@ UniValue auditsnapshotaccrual(const UniValue& params, bool fHelp) }; for (; pindex; pindex = pindex->pnext) { + // We are only going to use the accrual computer here for the magnitude unit and max_reward. + AccrualComputer computer = GRC::Tally::GetSnapshotComputer(*cpid, pindex->GetBlockTime(), pindex); + + int64_t max_reward = computer->MaxReward(); + if (pindex->ResearchSubsidy() > 0 && pindex->GetMiningId() == *cpid) { + tally_accrual_period( "stake", pindex->nHeight, pindex_low->nTime, pindex->nTime, - pindex->ResearchSubsidy()); + pindex->ResearchSubsidy(), + computer->MagnitudeUnit(), + max_reward); accrual = 0; pindex_low = pindex; @@ -466,13 +473,16 @@ UniValue auditsnapshotaccrual(const UniValue& params, bool fHelp) for (const auto& mrc : pindex->m_mrc_researchers) { // mrc payments are on the block previous to the staked block (the head of the chain when the mrc // was submitted. + if (mrc->m_cpid == *cpid) { tally_accrual_period( "mrc payment", pindex->nHeight, pindex_low->nTime, pindex->pprev->nTime, - mrc->m_research_subsidy); + mrc->m_research_subsidy, + computer->MagnitudeUnit(), + max_reward); accrual = 0; pindex_low = pindex->pprev; @@ -487,7 +497,9 @@ UniValue auditsnapshotaccrual(const UniValue& params, bool fHelp) pindex->nHeight, pindex_low->nTime, pindex->nTime, - 0); + 0, + computer->MagnitudeUnit(), + max_reward); pindex_low = pindex; } @@ -497,8 +509,13 @@ UniValue auditsnapshotaccrual(const UniValue& params, bool fHelp) } } + // We are only going to use the accrual computer here for the magnitude unit and max_reward. + AccrualComputer computer = GRC::Tally::GetSnapshotComputer(*cpid, pindex_low->GetBlockTime(), pindex_low); + + int64_t max_reward = computer->MaxReward(); + // The final period is from the last event till "now". - int64_t period = tally_accrual_period("tip", 0, pindex_low->nTime, now, 0); + int64_t period = tally_accrual_period("tip", 0, pindex_low->nTime, now, 0, computer->MagnitudeUnit(), max_reward); result.pushKV("cpid", cpid->ToString()); result.pushKV("accrual_account_exists", accrual_account_exists); diff --git a/src/test/gridcoin_tests.cpp b/src/test/gridcoin_tests.cpp index ff9ae50222..47d6a68e98 100755 --- a/src/test/gridcoin_tests.cpp +++ b/src/test/gridcoin_tests.cpp @@ -18,53 +18,53 @@ extern bool fTestNet; extern leveldb::DB *txdb; namespace { - // Arbitrary random characters generated with Python UUID. - const std::string TEST_CPID("17c65330c0924259b2f93c31d25b03ac"); - - struct GridcoinTestsConfig - { - GridcoinTestsConfig() - { - } - - ~GridcoinTestsConfig() - { - } - }; - - void AddProtocolEntry(const uint32_t& payload_version, const std::string& key, const std::string& value, - const CBlockIndex index, const bool& reset_registry = false) - { - GRC::ProtocolRegistry& registry = GRC::GetProtocolRegistry(); - - // Make sure the registry is reset. - if (reset_registry) registry.Reset(); - - CTransaction dummy_tx; - dummy_tx.nTime = index.nTime; - - GRC::Contract contract; - - if (payload_version < 2) { - contract = GRC::MakeContract( - uint32_t {2}, // Contract version (pre v13) - GRC::ContractAction::ADD, - key, - value); - } else { - contract = GRC::MakeContract( - uint32_t {3}, // Contract version (post v13) - GRC::ContractAction::ADD, - payload_version, // Protocol payload version (post v13) - key, - value, - GRC::ProtocolEntryStatus::ACTIVE); - } - - dummy_tx.vContracts.push_back(contract); - - registry.Add({contract, dummy_tx, &index}); - } +// Arbitrary random characters generated with Python UUID. +const std::string TEST_CPID("17c65330c0924259b2f93c31d25b03ac"); + +struct GridcoinTestsConfig +{ + GridcoinTestsConfig() + { + } + + ~GridcoinTestsConfig() + { + } +}; + +void AddProtocolEntry(const uint32_t& payload_version, const std::string& key, const std::string& value, + const CBlockIndex index, const bool& reset_registry = false) +{ + GRC::ProtocolRegistry& registry = GRC::GetProtocolRegistry(); + + // Make sure the registry is reset. + if (reset_registry) registry.Reset(); + + CTransaction dummy_tx; + dummy_tx.nTime = index.nTime; + + GRC::Contract contract; + + if (payload_version < 2) { + contract = GRC::MakeContract( + uint32_t {2}, // Contract version (pre v13) + GRC::ContractAction::ADD, + key, + value); + } else { + contract = GRC::MakeContract( + uint32_t {3}, // Contract version (post v13) + GRC::ContractAction::ADD, + payload_version, // Protocol payload version (post v13) + key, + value, + GRC::ProtocolEntryStatus::ACTIVE); + } + + dummy_tx.vContracts.push_back(contract); + + registry.Add({contract, dummy_tx, &index}); +} } // anonymous namespace @@ -105,14 +105,30 @@ BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE(gridcoin_cbr_tests) -BOOST_AUTO_TEST_CASE(gridcoin_DefaultCBRShouldBe10) +BOOST_AUTO_TEST_CASE(gridcoin_DefaultCBRShouldBe10PreV13) +{ + CBlockIndex index; + index.nTime = 1538066417; + + auto& registry = GRC::GetProtocolRegistry(); + registry.Reset(); + + BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), 10 * COIN); +} + +BOOST_AUTO_TEST_CASE(gridcoin_DefaultCBRShouldBeDefaultConstantBlockRewardForV13) { CBlockIndex index; index.nTime = 1538066417; + index.nHeight = Params().GetConsensus().BlockV13Height; + + auto& registry = GRC::GetProtocolRegistry(); + registry.Reset(); - BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), DEFAULT_CBR); + BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), Params().GetConsensus().DefaultConstantBlockReward); } + // Note that payload versions 1 and 2 alternate here. This is to test the constructors and // the machinery to add to the registry. This alternating approach is not what would // happen on the real blockchain, but is tolerated by the Registry independent of @@ -154,7 +170,7 @@ BOOST_AUTO_TEST_CASE(gridcoin_ConfigurableCBRShouldOverrideDefault) CBlockIndex index_2; index_2.nVersion = 13; index_2.nTime = time + 1; - index_2.nHeight = 2; + index_2.nHeight = Params().GetConsensus().BlockV13Height; // Protocol payload version 2 @@ -176,16 +192,16 @@ BOOST_AUTO_TEST_CASE(gridcoin_ConfigurableCBRShouldOverrideDefault) BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index_2), cbr); } -BOOST_AUTO_TEST_CASE(gridcoin_NegativeCBRShouldClampTo0) +BOOST_AUTO_TEST_CASE(gridcoin_NegativeCBRShouldClampTo0PreV13) { const int64_t time = 123456; CBlockIndex index; - index.nTime = time + 2; + index.nTime = time; index.nHeight = 3; auto& registry = GRC::GetProtocolRegistry(); - AddProtocolEntry(1, "blockreward1", ToString(-1 * COIN), index, false); + AddProtocolEntry(1, "blockreward1", ToString(-1 * COIN), index, true); if (GRC::ProtocolEntryOption entry = registry.TryActive("blockreward1")) { LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Protocol entry: m_key %s, m_value %s, m_hash %s, m_previous_hash %s, " @@ -203,16 +219,70 @@ BOOST_AUTO_TEST_CASE(gridcoin_NegativeCBRShouldClampTo0) BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), 0); } -BOOST_AUTO_TEST_CASE(gridcoin_ConfigurableCBRShouldClampTo2xDefault) +BOOST_AUTO_TEST_CASE(gridcoin_NegativeCBRShouldClampToConstantBlockRewardFloorForV13) { const int64_t time = 123456; CBlockIndex index; - index.nTime = time + 3; + index.nTime = time; + index.nHeight = Params().GetConsensus().BlockV13Height; + + auto& registry = GRC::GetProtocolRegistry(); + + AddProtocolEntry(2, "blockreward1", ToString(-1 * COIN), index, true); + + if (GRC::ProtocolEntryOption entry = registry.TryActive("blockreward1")) { + LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Protocol entry: m_key %s, m_value %s, m_hash %s, m_previous_hash %s, " + "m_timestamp %" PRId64 ", m_status string %s", + __func__, + entry->m_key, + entry->m_value, + entry->m_hash.ToString(), + entry->m_previous_hash.ToString(), + entry->m_timestamp, + entry->StatusToString() + ); + } + + BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), Params().GetConsensus().ConstantBlockRewardFloor); +} + +BOOST_AUTO_TEST_CASE(gridcoin_ConfigurableCBRShouldClampTo2xDefaultPreV13) +{ + const int64_t time = 123456; + CBlockIndex index; + index.nTime = time; index.nHeight = 4; auto& registry = GRC::GetProtocolRegistry(); - AddProtocolEntry(2, "blockreward1", ToString(DEFAULT_CBR * 3), index, false); + AddProtocolEntry(2, "blockreward1", ToString(Params().GetConsensus().DefaultConstantBlockReward * 3), index, true); + + if (GRC::ProtocolEntryOption entry = registry.TryActive("blockreward1")) { + LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Protocol entry: m_key %s, m_value %s, m_hash %s, m_previous_hash %s, " + "m_timestamp %" PRId64 ", m_status string %s", + __func__, + entry->m_key, + entry->m_value, + entry->m_hash.ToString(), + entry->m_previous_hash.ToString(), + entry->m_timestamp, + entry->StatusToString() + ); + } + + BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), Params().GetConsensus().DefaultConstantBlockReward * 2); +} + +BOOST_AUTO_TEST_CASE(gridcoin_ConfigurableCBRShouldClampToConstantBlockRewardCeilingForV13) +{ + const int64_t time = 123456; + CBlockIndex index; + index.nTime = time; + index.nHeight = Params().GetConsensus().BlockV13Height; + + auto& registry = GRC::GetProtocolRegistry(); + + AddProtocolEntry(2, "blockreward1", ToString(Params().GetConsensus().ConstantBlockRewardCeiling * 2), index, true); if (GRC::ProtocolEntryOption entry = registry.TryActive("blockreward1")) { LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Protocol entry: m_key %s, m_value %s, m_hash %s, m_previous_hash %s, " @@ -227,12 +297,10 @@ BOOST_AUTO_TEST_CASE(gridcoin_ConfigurableCBRShouldClampTo2xDefault) ); } - BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), DEFAULT_CBR * 2); + BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index), Params().GetConsensus().ConstantBlockRewardCeiling); } -// TODO: when the 180 day lookback is removed, this should be removed as a test as it will -// be irrelevant and invalid. -BOOST_AUTO_TEST_CASE(gridcoin_ObsoleteConfigurableCBRShouldResortToDefault) +BOOST_AUTO_TEST_CASE(gridcoin_ObsoleteConfigurableCBRShouldResortToDefaultPreV13) { CBlockIndex index_check; index_check.nTime = 1538066417; @@ -245,7 +313,7 @@ BOOST_AUTO_TEST_CASE(gridcoin_ObsoleteConfigurableCBRShouldResortToDefault) auto& registry = GRC::GetProtocolRegistry(); - AddProtocolEntry(2, "blockreward1", ToString(3 * COIN), index_add, false); + AddProtocolEntry(1, "blockreward1", ToString(3 * COIN), index_add, true); if (GRC::ProtocolEntryOption entry = registry.TryActive("blockreward1")) { LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Protocol entry: m_key %s, m_value %s, m_hash %s, m_previous_hash %s, " @@ -260,7 +328,39 @@ BOOST_AUTO_TEST_CASE(gridcoin_ObsoleteConfigurableCBRShouldResortToDefault) ); } - BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index_check), DEFAULT_CBR); + BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index_check), Params().GetConsensus().DefaultConstantBlockReward); } +BOOST_AUTO_TEST_CASE(gridcoin_PriorObsoleteConfigurableCBRShouldNotResortToDefaultforV13) +{ + CBlockIndex index_check; + index_check.nTime = 1538066417; + index_check.nHeight = Params().GetConsensus().BlockV13Height; + const int64_t max_message_age = 60 * 60 * 24 * 180; + + CBlockIndex index_add; + index_add.nTime = index_check.nTime - max_message_age - 1; + index_add.nHeight = 5; + + auto& registry = GRC::GetProtocolRegistry(); + + AddProtocolEntry(2, "blockreward1", ToString(3 * COIN), index_add, true); + + if (GRC::ProtocolEntryOption entry = registry.TryActive("blockreward1")) { + LogPrint(BCLog::LogFlags::CONTRACT, "INFO: %s: Protocol entry: m_key %s, m_value %s, m_hash %s, m_previous_hash %s, " + "m_timestamp %" PRId64 ", m_status string %s", + __func__, + entry->m_key, + entry->m_value, + entry->m_hash.ToString(), + entry->m_previous_hash.ToString(), + entry->m_timestamp, + entry->StatusToString() + ); + } + + BOOST_CHECK_EQUAL(GRC::GetConstantBlockReward(&index_check), 3 * COIN); +} + + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index 1aeccd2785..38c73c4649 100755 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -1326,6 +1326,18 @@ BOOST_AUTO_TEST_CASE(util_Fraction_Initialization_from_int64_t) BOOST_CHECK_EQUAL(fraction.IsNonNegative(), false); } +BOOST_AUTO_TEST_CASE(util_Fraction_Initialization_from_string) +{ + Fraction fraction = Fraction().FromString("100/-567"); + + BOOST_CHECK_EQUAL(fraction.GetNumerator(), -100); + BOOST_CHECK_EQUAL(fraction.GetDenominator(), 567); + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK_EQUAL(fraction.IsZero(), false); + BOOST_CHECK_EQUAL(fraction.IsPositive(), false); + BOOST_CHECK_EQUAL(fraction.IsNonNegative(), false); +} + BOOST_AUTO_TEST_CASE(util_Fraction_Simplify) { Fraction fraction(-4, -6); @@ -1732,7 +1744,65 @@ BOOST_AUTO_TEST_CASE(util_Fraction_ToString) Fraction fraction(123, 10000); BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); - BOOST_CHECK_EQUAL(fraction.ToString(),"123/10000"); + BOOST_CHECK_EQUAL(fraction.ToString(), "123/10000"); +} + +BOOST_AUTO_TEST_CASE(util_Fraction_FromString) +{ + Fraction fraction = Fraction().FromString("100/567"); + + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK(fraction == Fraction(100, 567, true)); + + fraction = Fraction().FromString("-100/567"); + + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK(fraction == Fraction(-100, 567, true)); + + fraction = Fraction().FromString("100/-567"); + + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK(fraction == Fraction(-100, 567, true)); + + fraction = Fraction().FromString("5"); + + BOOST_CHECK_EQUAL(fraction.IsSimplified(), true); + BOOST_CHECK(fraction == Fraction(5, 1, true)); + + std::string err; + std::string valid_err_message {"fraction input string cannot be parsed to fraction"}; + + try { + Fraction fraction = Fraction().FromString("100/567/300"); + } catch (std::out_of_range& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, valid_err_message); + + try { + Fraction fraction = Fraction().FromString("100 / 567"); + } catch (std::out_of_range& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, valid_err_message); + + try { + Fraction fraction = Fraction().FromString("100.1"); + } catch (std::out_of_range& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, valid_err_message); + + try { + Fraction fraction = Fraction().FromString(""); + } catch (std::out_of_range& e) { + err = e.what(); + } + + BOOST_CHECK_EQUAL(err, valid_err_message); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util.h b/src/util.h index b51db0661f..db86e1827d 100644 --- a/src/util.h +++ b/src/util.h @@ -340,6 +340,34 @@ class Fraction { return strprintf("%" PRId64 "/" "%" PRId64, m_numerator, m_denominator); } + Fraction FromString(const std::string& string) const + { + std::vector string_fraction = split(string, "/"); + + if (string_fraction.size() > 2 || string_fraction.size() < 1) { + throw std::out_of_range("fraction input string cannot be parsed to fraction"); + } + + int64_t numerator; + int64_t denominator; + + if (string_fraction.size() == 1) { + if (!ParseInt64(string_fraction[0], &numerator)) { + throw std::out_of_range("fraction input string cannot be parsed to fraction"); + } + + denominator = 1; + } else { + // There must be two elements to the string fraction if we are here. + if (!ParseInt64(string_fraction[0], &numerator) + || !ParseInt64(string_fraction[1], &denominator)) { + throw std::out_of_range("fraction input string cannot be parsed to fraction"); + } + } + + return Fraction(numerator, denominator, true); + } + bool operator!() { return IsZero();