Skip to content

Commit

Permalink
Enhance ETTS calculation
Browse files Browse the repository at this point in the history
This commit implements a two step process for computing the
underlying difficulty used for the GetEstimatedTimetoStake function.

This is meant to address the matching of the appropriate span to
use for the difficulty calculation compared to the ETTS (which is
a coupled problem). For low balance holders, a long (960 block)
averaging interval should be used to compute the difficulty
for estimating ETTS, since if someone has an ETTS of 1 month, it
makes no sense to be changing that estimate based on difficulty
changes during the last hour. Conversely, if someone is staking
6 times a day, a much shorter span for the difficulty is appropriate
since changes in the network difficulty will be reflected very
quickly with staking frequency.
  • Loading branch information
jamescowens committed Nov 18, 2020
1 parent 09391a6 commit 2cbcf9d
Showing 1 changed file with 45 additions and 12 deletions.
57 changes: 45 additions & 12 deletions src/gridcoin/staking/difficulty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ double GRC::GetAverageDifficulty(unsigned int nPoSInterval)
unsigned int nStakesHandled = 0;
double result;

LOCK(cs_main);

CBlockIndex* pindex = pindexBest;

while (pindex && nStakesHandled < nPoSInterval)
Expand All @@ -166,15 +168,14 @@ double GRC::GetAverageDifficulty(unsigned int nPoSInterval)
{
dDiffSum += dDiff;
nStakesHandled++;
LogPrint(BCLog::LogFlags::NOISY, "GetAverageDifficulty debug: dDiff = %f", dDiff);
LogPrint(BCLog::LogFlags::NOISY, "GetAverageDifficulty debug: nStakesHandled = %u", nStakesHandled);
}
}

pindex = pindex->pprev;
}

result = nStakesHandled ? dDiffSum / nStakesHandled : 0;
LogPrint(BCLog::LogFlags::NOISY, "GetAverageDifficulty debug: nStakesHandled = %u", nStakesHandled);
LogPrint(BCLog::LogFlags::NOISY, "GetAverageDifficulty debug: Average dDiff = %f", result);

return result;
Expand Down Expand Up @@ -331,17 +332,8 @@ double GRC::GetEstimatedTimetoStake(bool ignore_staking_status, double dDiff, do
// Debug output cooldown...
LogPrint(BCLog::LogFlags::NOISY, "GetEstimatedTimetoStake debug: nStakeMinAge = %i", nStakeMinAge);

// If dDiff = 0 from supplied argument (which is also the default), then derive a smoothed difficulty over the default PoSInterval of 40 blocks by calling
// GetAverageDifficulty(40), otherwise let supplied argument dDiff stand.
if (!dDiff) dDiff = GetAverageDifficulty(40);
LogPrint(BCLog::LogFlags::NOISY, "GetEstimatedTimetoStake debug: dDiff = %f", dDiff);

// The stake probability per "throw" of 1 weight unit = target value at diff of 1.0 / (maxhash * diff). This happens effectively every STAKE_TIMESTAMP_MASK+1 sec.
double dUnitStakeProbability = 1 / (4295032833.0 * dDiff);
LogPrint(BCLog::LogFlags::NOISY, "GetEstimatedTimetoStake debug: dUnitStakeProbability = %e", dUnitStakeProbability);


int64_t nTime = 0;
int64_t nStakeableBalance = 0;
for (const auto& out : vCoins)
{
CTxIndex txindex;
Expand All @@ -362,6 +354,8 @@ double GRC::GetEstimatedTimetoStake(bool ignore_staking_status, double dDiff, do
if (BalanceAvailForStaking >= nValue && nValue >= 1250000)
{
vUTXO.push_back(std::pair<int64_t, CAmount>( nTime, nValue));
nStakeableBalance += nValue;

LogPrint(BCLog::LogFlags::NOISY, "GetEstimatedTimetoStake debug: pair (relative to current time: <%i, %i>", nTime - nCurrentTime, nValue);

// Only record a time below if it is after nCurrentTime, because UTXO's that have matured already are already stakeable and can be grouped (will be found)
Expand All @@ -370,6 +364,45 @@ double GRC::GetEstimatedTimetoStake(bool ignore_staking_status, double dDiff, do
}
}

// If dDiff = 0 from supplied argument (which is also the default), then derive a smoothed difficulty, otherwise
// let the supplied argument dDiff stand. 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 expected to be relatively long, so it is appropriate to use a longer span to
// compute the difficulty. Conversely, if the stakeable balance is high, it is appropriate to use a corresponding
// short span so that the staking estimate reflects appropriate network conditions compared to the expected
// staking interval. Since this is a coupled problem, the approach here, which works reasonably well, uses the
// last hour (40 block) diff to bootstrap a span estimate from using the thumbrule estimate of ETTS with that diff,
// and then re-computes the diff using the block span, with clamp of [40, BLOCKS_PER_DAY], since it is silly to
// allow difficulty to become more sensitive than one hour of change, and is of minimal value to long term
// projections to use more than a day's history of diff. (Difficulty patterns tend to repeat on a daily basis.
// Longer term historical variations of more than a day are due to extraneous variables of which history is of
// little predictive value.)
if (!dDiff)
{
// First estimate the difficulty based on the last 40 blocks.
dDiff = GetAverageDifficulty(40);

// Compute an appropriate block span for the second iteration of dificulty computation based on the
// above diff calc. Clamp to no less than 40 (~1 hour) and no more than 960 (~1 day). Note that those
// familiar with the thumbrule for ETTS, ETTS = 10000 / Balance * Diff should recognize it in the below
// expression. Note that the actual constant is 9942.2056 (from the bluepaper, eq. 12), but it suffices to
// use the rounded thumbrule value here.
unsigned int nEstAppropriateDiffSpan = std::min((unsigned int) 960,
std::max((unsigned int) 40,
(unsigned int) (10000.0 * BLOCKS_PER_DAY * COIN
/ nStakeableBalance * dDiff)));

LogPrint(BCLog::LogFlags::NOISY, "GetEstimatedTimetoStake debug: nStakeableBalance: %u", nStakeableBalance);
LogPrint(BCLog::LogFlags::NOISY, "GetEstimatedTimetoStake debug: nEstAppropriateDiffSpan: %u", nEstAppropriateDiffSpan);

dDiff = GetAverageDifficulty(nEstAppropriateDiffSpan);
}

LogPrint(BCLog::LogFlags::NOISY, "GetEstimatedTimetoStake debug: dDiff = %f", dDiff);

// The stake probability per "throw" of 1 weight unit = target value at diff of 1.0 / (maxhash * diff). This happens effectively every STAKE_TIMESTAMP_MASK+1 sec.
double dUnitStakeProbability = 1 / (4295032833.0 * dDiff);
LogPrint(BCLog::LogFlags::NOISY, "GetEstimatedTimetoStake debug: dUnitStakeProbability = %e", dUnitStakeProbability);

int64_t nTimePrev = nCurrentTime;
int64_t nDeltaTime = 0;
Expand Down

0 comments on commit 2cbcf9d

Please sign in to comment.