diff --git a/src/main.cpp b/src/main.cpp index 1280ff51e8..f13b64bbcf 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -386,10 +386,10 @@ bool FullSyncWithDPORNodes() const int64_t nLookback = 30 * 6 * 86400; const int64_t iStartTime = (iEndTime - nLookback) - ( (iEndTime - nLookback) % BLOCK_GRANULARITY); std::string cpiddata = GetListOf("beacon", iStartTime, iEndTime); - std::string sWhitelist = GetListOf("project"); + std::string sWhitelist = GetListOf("project"); int64_t superblock_time = ReadCache("superblock", "magnitudes").timestamp; int64_t superblock_age = GetAdjustedTime() - superblock_time; - LogPrintf(" list of cpids %s \n",cpiddata); + LogPrintf(" list of cpids %s \n",cpiddata); double popularity = 0; std::string consensus_hash = GetNeuralNetworkSupermajorityHash(popularity); std::string sAge = ToString(superblock_age); @@ -403,39 +403,318 @@ bool FullSyncWithDPORNodes() return true; } -double GetPoSKernelPS() +double GetPoSKernelPS(int nPoSInterval) { - int nPoSInterval = 72; - double dStakeKernelsTriedAvg = 0; - int nStakesHandled = 0, nStakesTime = 0; + // 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. + double dStakeableWeightSum = 0; + double dDiff = 1.0; + // Initialize nStakesHandled with -1 because the first iteration of the while loop below is degenerate. + // It has to go through once to populate both the previous and current stake in the loop to calculate the staking time. + int nStakesHandled = -1; + int nStakesTime = 0; + double result = 0; - CBlockIndex* pindex = pindexBest;; + CBlockIndex* pindex = pindexBest; CBlockIndex* pindexPrevStake = NULL; while (pindex && nStakesHandled < nPoSInterval) { if (pindex->IsProofOfStake()) { - dStakeKernelsTriedAvg += GetDifficulty(pindex) * 4294967296.0; + /* + * An estimate for the stakeable balance * 80 (weight units) across the entire network is + * netweight = Diff * (MaxHash / StandardDifficultyTarget) * (16 sec) / TimeBetweenStakes + * The first two terms compute the inverse probability of one bernoulli toss (stake test) + * The 16 comes from the quantum time unit imposed by the time mask. The TimeBetweenStakes + * should nominally be 90 seconds, but in fact varies. To avoid near infinities if TimeBetweenStakes + * happens to be very low due to random variation, we will use sum(WeightSum)/sum(StakesTime) + * approach to compute the average. + * + * Don't get confused here. The loop is going backwards in time. Therefore pIndexPrevStake is a later block + * here than pindex. + */ nStakesTime += pindexPrevStake ? (pindexPrevStake->nTime - pindex->nTime) : 0; + if (fDebug10) LogPrintf("nStakesTime = %u", nStakesTime); + dDiff = GetDifficulty(pindex); + dStakeableWeightSum += dDiff * 68720525328.0; + if (fDebug10) LogPrintf("dDiff = %f", dDiff); + if (fDebug10) LogPrintf("dStakeableWeightSum = %f", dStakeableWeightSum); pindexPrevStake = pindex; nStakesHandled++; + if (fDebug10) LogPrintf("nStakesHandled = %u", nStakesHandled); } pindex = pindex->pprev; } - double result = 0; + result = nStakesHandled ? dStakeableWeightSum / (double) nStakesTime : 0; + if (fDebug10) LogPrintf("Average dDiff = %f", result * (nStakesTime / nStakesHandled) / 68720525328.0); + if (fDebug10) LogPrintf("Network Weight = %f", result); + + return result; +} + +double GetAverageDifficulty(int nPoSInterval) +{ + /* + * Diff is an inverse metric. It is essentially the reciprocal of the probability of staking + * (compared to standard target instead of maxhash). The diff is recorded in each block, and + * its inverse is a proxy for the coinweight selected by the staker. Therefore averaging the + * reciprocal of the diffs and then reinverting to determine the average diff is the correct way + * to do this calculation. This is called a harmonic mean in math. + * + * Also... 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. + */ + + double dDiff = 1.0; + double dReciprocalDiffSum = 0.0; + // Initialize nStakesHandled with -1 because the first iteration of the while loop below is degenerate. + // It has to go through once to populate both the previous and current stake in the loop to calculate the staking time. + int nStakesHandled = -1; + int nStakesTime = 0; + double result = 0.0; + + CBlockIndex* pindex = pindexBest; + CBlockIndex* pindexPrevStake = NULL; + + while (pindex && nStakesHandled < nPoSInterval) + { + if (pindex->IsProofOfStake()) + { + /* + * Don't get confused here. The loop is going backwards in time. Therefore pIndexPrevStake is a later block + * here than pindex. + */ + dDiff = GetDifficulty(pindex); + dReciprocalDiffSum += 1 / dDiff; + if (fDebug10) LogPrintf("dDiff = %f", dDiff); + if (fDebug10) LogPrintf("dReciprocalDiffSum = %f", dReciprocalDiffSum); + pindexPrevStake = pindex; + nStakesHandled++; + if (fDebug10) LogPrintf("nStakesHandled = %u", nStakesHandled); + } + + pindex = pindex->pprev; + } + + result = nStakesHandled ? nStakesHandled / dReciprocalDiffSum : 0; + if (fDebug10) LogPrintf("Average dDiff = %f", result); + + return result; +} + +double GetEstimatedTimetoStake(int nPoSInterval, double dConfidence) +{ + /* + * The algorithm below is an attempt to come up with a more accurate way of estimating Time to Stake (ETTS) based on + * the actual situation of the miner and UTXO's. A simple equation will not provide good results, because in mainnet, + * the cooldown period is 16 hours, and depending on how many UTXO's and where they are with respect to getting out of + * cooldown has a lot to do with the expected time to stake. + * + * The way to conceptualize the approach below is to think of the UTXO's as bars on a Gantt Chart. It is a negative Gantt + * chart, meaning that each UTXO bar is cooldown period long, and while the current time is in that bar, the staking probability + * for the UTXO is zero, and UnitStakingProbability elsewhere. A timestamp mask of 16x the normal mask is used to reduce + * the work in the nested loop, so that a 16 hour interval will have a maximum of 225 events, and most likely far less. + * This is important, because the inner loop will be the number of UTXO's. A future improvement to this algorithm would + * also be to quantize (group) the UTXO's themselves (the Gantt bars) so that the work would be further reduced. + * You will see that once the UTXO's are sorted in ascending order based on the time of the end of each of their cooldowns, this + * becomes a manageable, but irritating algorithm to piece the probabilities together. + * + * You will note that I use the compound Poisson (geometric) recursive probability relation, since you cannot simply add + * the probabilities due to consideration of high confidence (CDF) values of 80% or more. + * + * I have also elected to use thin local data structures to hold the UTXO stuff. This minimizes the amount of time + * that locks on the wallet need to be held at the expense of a little memory consumption. + */ + + double result = 0.0; + bool staking = MinerStatus.nLastCoinStakeSearchInterval && MinerStatus.WeightSum; + // Get out early if not staking and set return value of 0. + if (!staking) + { + if (fDebug10) LogPrintf("ETTS debug: Not staking: ETTS = %f", result); + return result; + } + + int64_t nValue = 0; + int64_t nCurrentTime = GetAdjustedTime(); + if (fDebug10) LogPrintf("ETTS debug: nCurrentTime = %i", nCurrentTime); + + CTxDB txdb("r"); + // Here I am defining a time mask 16 times as long as the normal stake time mask. This is to quantize the UTXO's into a maximum of + // 16 hours * 3600 / 256 = 225 time bins for evaluation. Otherwise for a large number of UTXO's, this algorithm could become + // really expensive. + const int ETTS_TIMESTAMP_MASK = 16 * STAKE_TIMESTAMP_MASK; + + int64_t BalanceAvailForStaking = 0; + vector vCoins; + + { + LOCK2(cs_main, pwalletMain->cs_wallet); + + BalanceAvailForStaking = pwalletMain->GetBalance() - nReserveBalance; + + if (fDebug10) LogPrintf("ETTS debug: BalanceAvailForStaking = %u", BalanceAvailForStaking); + + // Get out early if no balance available and set return value of 0. This should already have happened above, because with no + // balance left after reserve, staking should be disabled; however, just to be safe... + if (BalanceAvailForStaking <= 0) + { + if (fDebug10) LogPrintf("ETTS debug: No balance available: ETTS = %f", result); + return result; + } + + //reminder... void AvailableCoins(std::vector& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl=NULL, bool fIncludeStakingCoins=false) const; + pwalletMain->AvailableCoins(vCoins, true, NULL, true); + } + + + // An efficient local structure to store the UTXO's with the bare minimum info we need. + typedef vector< std::pair > vCoinsExt; + vCoinsExt vUTXO; + // A local ordered set to store the unique "bins" corresponding to the UTXO transaction times. We are going to use this + // for the outer loop. + std::set UniqueUTXOTimes; + // We want the first "event" to be the CurrentTime. This does not have to be quantized. + UniqueUTXOTimes.insert(nCurrentTime); + + + // Debug output cooldown... + if (fDebug10) LogPrintf("ETTS debug: nStakeMinAge = %i", nStakeMinAge); + + // Get the average difficulty over the nPosInterval blocks (default 40). + double dDiff = GetAverageDifficulty(nPoSInterval); + if (fDebug10) LogPrintf("ETTS 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); + if (fDebug10) LogPrintf("ETTS debug: dUnitStakeProbability = %e", dUnitStakeProbability); - if (nStakesTime) - result = dStakeKernelsTriedAvg / nStakesTime; + + int64_t nTime = 0; + for (const auto& out : vCoins) + { + CTxIndex txindex; + CBlock CoinBlock; //Block which contains CoinTx + { + LOCK2(cs_main, pwalletMain->cs_wallet); + + if (!txdb.ReadTxIndex(out.tx->GetHash(), txindex)) + continue; //error? + + if (!CoinBlock.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) + continue; + } + + // We are going to store as an event the time that the UTXO matures (is available for staking again.) + nTime = (CoinBlock.GetBlockTime() & ~ETTS_TIMESTAMP_MASK) + nStakeMinAge; + + nValue = out.tx->vout[out.i].nValue; + + // Only consider UTXO's that are actually stakeable - which means that each one must be less than the available balance + // subtracting the reserve. Each UTXO also has to be greater than 1/80 GRC to result in a weight greater than zero in the CreateCoinStake loop, + // so eliminate UTXO's with less than 0.0125 GRC balances right here. + if(BalanceAvailForStaking >= nValue && nValue / 1250000 > 0) + { + vUTXO.push_back(std::pair( nTime, nValue)); + if (fDebug10) LogPrintf("ETTS 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) + // by the nCurrentTime record that was already injected above. + if(nTime > nCurrentTime) UniqueUTXOTimes.insert(nTime); + } + } + + //The below line is only needed for debug. The order is actually only important in the outer loop, which is maintained in the UniqueUTXOTimes set. + //sort(vUTXO.begin(),vUTXO.end()); + + + int64_t nTimePrev = nCurrentTime; + int64_t nDeltaTime = 0; + int64_t nThrows = 0; + int64_t nCoinWeight = 0; + double dProbAccumulator = 0; + double dCumulativeProbability = 0; + // Note: Even though this is a compound Poisson process leading to a compound geometric distribution, and the individual probabilities are + // small, we are mounting to high CDFs. This means to be reasonably accurate, we cannot just add the probabilities, because the intersections + // become significant. The CDF of a compound geometric distribution as you do tosses with different probabilities follows the + // recursion relation... CDF.i = 1 - (1 - CDF.i-1)(1 - p.i). If all probabilities are the same, this reduces to the familiar + // CDF.k = 1 - (1 - p)^k where ^ is exponentiation. + for(const auto& itertime : UniqueUTXOTimes) + { + + nTime = itertime; + dProbAccumulator = 0; + + for( auto& iterUTXO : vUTXO) + { + + if (fDebug10) LogPrintf("ETTS debug: Unique UTXO Time: %u, vector pair <%u, %u>", nTime, iterUTXO.first, iterUTXO.second); + + // If the "negative Gantt chart bar" is ending or has ended for a UTXO, it now accumulates probability. (I.e. the event time being checked + // is greater than or equal to the cooldown expiration of the UTXO.) + // accumulation for that UTXO. + if(nTime >= iterUTXO.first) + { + // The below weight calculation is just like the CalculateStakeWeightV8 in kernel.cpp. + nCoinWeight = iterUTXO.second / 1250000; + + dProbAccumulator = 1 - ((1 - dProbAccumulator) * (1 - (dUnitStakeProbability * nCoinWeight))); + if (fDebug10) LogPrintf("ETTS debug: dProbAccumulator = %e", dProbAccumulator); + } - if (IsProtocolV2(nBestHeight)) - result *= STAKE_TIMESTAMP_MASK + 1; + } + nDeltaTime = nTime - nTimePrev; + nThrows = nDeltaTime / (STAKE_TIMESTAMP_MASK + 1); + if (fDebug10) LogPrintf("ETTS debug: nThrows = %i", nThrows); + dCumulativeProbability = 1 - ((1 - dCumulativeProbability) * pow((1 - dProbAccumulator), nThrows)); + if (fDebug10) LogPrintf("ETTS debug: dCumulativeProbability = %e", dCumulativeProbability); + + if(dCumulativeProbability >= dConfidence) + break; + + nTimePrev = nTime; + } - return result/100; + // If (dConfidence - dCumulativeProbability) > 0, it means we exited the negative Gantt chart area and the desired confidence level + // has not been reached. All of the eligible UTXO's are contributing probability, and this is the final dProbAccumulator value. + // If the loop above is degenerate (i.e. only the current time pass through), then dCumulativeProbability will be zero. + // If it was not degenerate and the positive reqions in the Gantt chart area contributed some probability, then dCumulativeProbability will + // be greater than zero. We must compute the amount of time beyond nTime that is required to bridge the gap between + // dCumulativeProbability and dConfidence. If (dConfidence - dCumulativeProbability) <= 0 then we overshot during the Gantt chart area, + // and we will back off by nThrows amount, which will now be negative. + if (fDebug10) LogPrintf("ETTS debug: dProbAccumulator = %e", dProbAccumulator); + + // Shouldn't happen because if we are down here, we are staking, and there have to be eligible UTXO's, but just in case... + if (dProbAccumulator == 0.0) + { + if (fDebug10) LogPrintf("ETTS debug: ERROR in dProbAccumulator calculations"); + return result; + } + + if (fDebug10) LogPrintf("ETTS debug: dConfidence = %f", dConfidence); + // If nThrows is negative, this just means we overshot in the Gantt chart loop and have to backtrack by nThrows. + nThrows = (int64_t)((log(1 - dConfidence) - log(1 - dCumulativeProbability)) / log(1 - dProbAccumulator)); + if (fDebug10) LogPrintf("ETTS debug: nThrows = %i", nThrows); + + nDeltaTime = nThrows * (STAKE_TIMESTAMP_MASK + 1); + if (fDebug10) LogPrintf("ETTS debug: nDeltaTime = %i", nDeltaTime); + + // Because we are looking at the delta time required past nTime, which is where we exited the Gantt chart loop. + result = nDeltaTime + nTime - nCurrentTime; + if (fDebug10) LogPrintf("ETTS debug: ETTS at %d confidence = %i", dConfidence, result); + + // The old calculation for comparative purposes... + double oldETTS = 0; + + oldETTS = GetTargetSpacing(nBestHeight) * GetPoSKernelPS(nPoSInterval) / MinerStatus.WeightSum; + if (fDebug10) LogPrintf("ETTS debug: oldETTS = %f", oldETTS); + + return result; } + void GetGlobalStatus() { //Populate overview @@ -460,10 +739,12 @@ void GetGlobalStatus() { LOCK(MinerStatus.lock); GlobalStatusStruct.blocks = ToString(nBestHeight); GlobalStatusStruct.difficulty = RoundToString(PORDiff,3); - GlobalStatusStruct.netWeight = RoundToString(GetPoSKernelPS(),2); + GlobalStatusStruct.netWeight = RoundToString(GetPoSKernelPS() / 80.0,2); //todo: use the real weight from miner status (requires scaling) GlobalStatusStruct.coinWeight = sWeight; GlobalStatusStruct.magnitude = RoundToString(boincmagnitude,2); + GlobalStatusStruct.ETTS = RoundToString(GetEstimatedTimetoStake()/86400.0,3); + GlobalStatusStruct.ERRperday = RoundToString(boincmagnitude * GRCMagnitudeUnit(GetAdjustedTime()),2); GlobalStatusStruct.project = msMiningProject; GlobalStatusStruct.cpid = GlobalCPUMiningCPID.cpid; GlobalStatusStruct.poll = msPoll; @@ -3092,11 +3373,11 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck, boo } - if (bb.lastblockhash != pindex->pprev->GetBlockHash().GetHex()) - { - std::string sNarr = "ConnectBlock[ResearchAge] : Historical DPOR Replay attack : lastblockhash != actual last block hash."; - LogPrintf("\n ****** %s ***** \n",sNarr); - } + if (bb.lastblockhash != pindex->pprev->GetBlockHash().GetHex()) + { + std::string sNarr = "ConnectBlock[ResearchAge] : Historical DPOR Replay attack : lastblockhash != actual last block hash."; + LogPrintf("\n ****** %s ***** \n",sNarr); + } if (IsResearchAgeEnabled(pindex->nHeight) && (BlockNeedsChecked(nTime) || nVersion>=9)) @@ -5448,7 +5729,7 @@ bool GetEarliestStakeTime(std::string grcaddress, std::string cpid) } else { - myCPID = pblockindex->GetCPID(); + myCPID = pblockindex->GetCPID(); } if (cpid == myCPID && nCPIDTime==0 && IsResearcher(myCPID)) { @@ -8724,3 +9005,4 @@ bool IsResearcher(const std::string& cpid) { return cpid.length() == 32; } + diff --git a/src/main.h b/src/main.h index ca68abfcb3..da2ee7e52f 100755 --- a/src/main.h +++ b/src/main.h @@ -227,6 +227,8 @@ struct globalStatusType std::string netWeight; std::string coinWeight; std::string magnitude; + std::string ETTS; + std::string ERRperday; std::string project; std::string cpid; std::string status; @@ -283,7 +285,9 @@ std::string UnpackBinarySuperblock(std::string sBlock); bool IsSuperBlock(CBlockIndex* pIndex); bool LoadSuperblock(std::string data, int64_t nTime, int height); -double GetPoSKernelPS(); +double GetPoSKernelPS(int nPoSInterval = 40); +double GetAverageDifficulty(int nPoSInterval = 40); +double GetEstimatedTimetoStake(int nPoSInterval = 40, double dConfidence = 0.8); unsigned int ComputeMinWork(unsigned int nBase, int64_t nTime); unsigned int ComputeMinStake(unsigned int nBase, int64_t nTime, unsigned int nBlockTime); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index da106a50ed..33b0167e50 100755 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -1898,15 +1898,15 @@ void BitcoinGUI::updateStakingIcon() uint64_t nWeight, nLastInterval; std::string ReasonNotStaking; { LOCK(MinerStatus.lock); - // not using real weigh to not break calculation - nWeight = MinerStatus.ValueSum; + // not using real weight to not break calculation - fixed - but retaining GRC units for instead of internal weight units. + nWeight = MinerStatus.WeightSum / 80.0; nLastInterval = MinerStatus.nLastCoinStakeSearchInterval; ReasonNotStaking = MinerStatus.ReasonNotStaking; } - uint64_t nNetworkWeight = GetPoSKernelPS(); + uint64_t nNetworkWeight = GetPoSKernelPS() / 80.0; bool staking = nLastInterval && nWeight; - uint64_t nEstimateTime = staking ? (GetTargetSpacing(nBestHeight) * nNetworkWeight / nWeight) : 0; + uint64_t nEstimateTime = GetEstimatedTimetoStake(); if (staking) { diff --git a/src/qt/forms/overviewpage.ui b/src/qt/forms/overviewpage.ui index 533ba870a2..dd8570d583 100644 --- a/src/qt/forms/overviewpage.ui +++ b/src/qt/forms/overviewpage.ui @@ -336,6 +336,16 @@ + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + @@ -343,6 +353,16 @@ + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + @@ -350,22 +370,25 @@ - - + + - CPID: + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - + + - Status: + CPID: - - + + @@ -374,18 +397,15 @@ - - + + - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + Status: - - + + @@ -394,23 +414,37 @@ - - + + + + Estimated Time to Stake + - + Est. TTS: - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + - - + + + + Estimated Research rewards per day + - + Est. RR/day: - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + diff --git a/src/qt/overviewpage.cpp b/src/qt/overviewpage.cpp index c7a20cf254..e72661d2dc 100644 --- a/src/qt/overviewpage.cpp +++ b/src/qt/overviewpage.cpp @@ -212,6 +212,8 @@ void OverviewPage::UpdateBoincUtilization() ui->labelStatus->setText(QString::fromUtf8(GlobalStatusStruct.status.c_str())); ui->labelPoll->setText(QString::fromUtf8(GlobalStatusStruct.poll.c_str()).replace(QChar('_'),QChar(' '), Qt::CaseSensitive)); ui->labelErrors->setText(QString::fromUtf8(GlobalStatusStruct.errors.c_str())); + ui->labelETTS->setText(QString::fromUtf8(GlobalStatusStruct.ETTS.c_str())); + ui->labelERRperday->setText(QString::fromUtf8(GlobalStatusStruct.ERRperday.c_str())); } void OverviewPage::setModel(WalletModel *model) diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index f12e546c21..13b24865a9 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -36,13 +36,14 @@ Value getmininginfo(const Array& params, bool fHelp) pwalletMain->GetStakeWeight(nWeight); Object obj, diff, weight; double nNetworkWeight = GetPoSKernelPS(); + double nNetworkValue = nNetworkWeight / 80.0; obj.push_back(Pair("blocks", nBestHeight)); diff.push_back(Pair("proof-of-stake", GetDifficulty(GetLastBlockIndex(pindexBest, true)))); { LOCK(MinerStatus.lock); // not using real weigh to not break calculation bool staking = MinerStatus.nLastCoinStakeSearchInterval && MinerStatus.WeightSum; - uint64_t nExpectedTime = staking ? (GetTargetSpacing(nBestHeight) * nNetworkWeight / MinerStatus.ValueSum) : 0; + uint64_t nExpectedTime = GetEstimatedTimetoStake(); diff.push_back(Pair("last-search-interval", MinerStatus.nLastCoinStakeSearchInterval)); weight.push_back(Pair("minimum", MinerStatus.WeightMin)); weight.push_back(Pair("maximum", MinerStatus.WeightMax)); @@ -51,6 +52,7 @@ Value getmininginfo(const Array& params, bool fHelp) weight.push_back(Pair("legacy", nWeight/(double)COIN)); obj.push_back(Pair("stakeweight", weight)); obj.push_back(Pair("netstakeweight", nNetworkWeight)); + obj.push_back(Pair("netstakingGRCvalue", nNetworkValue)); obj.push_back(Pair("staking", staking)); obj.push_back(Pair("mining-error", MinerStatus.ReasonNotStaking)); obj.push_back(Pair("time-to-stake_days", nExpectedTime/86400.0));