-
Notifications
You must be signed in to change notification settings - Fork 172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new GetEstimatedTimetoStake, GetEstimatedNetworkWeight (replaces GetPoSKernelPS) and GetAverageDifficulty functions #1044
new GetEstimatedTimetoStake, GetEstimatedNetworkWeight (replaces GetPoSKernelPS) and GetAverageDifficulty functions #1044
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a brief description to your commit message? You also refactored some stuff and removed obsolete code, can you add a short list of stuff removed/refactored?
src/qt/forms/overviewpage.ui
Outdated
@@ -7,7 +7,7 @@ | |||
<x>0</x> | |||
<y>0</y> | |||
<width>948</width> | |||
<height>559</height> | |||
<height>942</height> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this height change required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was done by the designer when I added the fields... Not sure if this a problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is because I have a hiDPI (4k) monitor when I was using the designer. If so, that sucks. I wonder if I can change it down manually and see what happens?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be changed manually, yes. And it is certainly caused by your local display.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll just edit it back manually. :)
src/qt/forms/overviewpage.ui
Outdated
<item row="9" column="1"> | ||
<widget class="QLabel" name="labelERRperday"> | ||
<property name="text"> | ||
<string/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are renaming some labels to generic names like "label_4" and give proper names to others. Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did this through the designer. It does that automatically, and some of the generically named fields were there before...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code in *.ui is generated by the qt designer...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be modified outside of designer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also the fields with proper names are the ones where information is plugged in via the reference to GlobalStatusStruct by the label pointers in overviewpage.cpp.
9713289
to
40afb05
Compare
I changed the commit message to be more informative. |
47c1826
to
2468aa5
Compare
Paul gave me some good suggestions via slack message. I have combined the four locking sections into two and made some other tweaks. I also implemented a test for UTXO.value / 1250000 > 0, which is the same as the greater than 0.0125 GRC requirement for a UTXO to stake early in the function before the nested loop. This keeps unstakeable UTXO's out of the nested loop which reduces algorithm workload. I also manually fixed the UI form height back to what it was. The change was done by the qtdesigner and was not necessary. |
@barton2526 made a 1000 UTXO testnet wallet for me with 1 GRC in each UTXO to test near the corner case on UTXO count. Thanks so much @barton2526! The results for getmininginfo, which include GetEstimatedTimetoStake and several other calls.. The getmininginfo results for my normal testnet wallet with 11 UTXO's... I verified via debug logs that the function is looping through all of the UTXO’s several times. Once to fill the local vector, and then several times during the nested loop. (The timings were taken with debug off though, because the output of all of that to the debug log obviously slows things...) So that is 0.065 secs for ~1000 UTXOs. (Because the other calls in getmininginfo are the same in both. An upper bound on what is reasonable for the function without causing issues is probably 0.5 seconds. I think we are good! ;) |
src/main.cpp
Outdated
} | ||
|
||
pindex = pindex->pprev; | ||
} | ||
|
||
double result = 0; | ||
result = nStakesHandled ? dStakeableWeightSum / (double) nStakesTime : 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there are no stakes handled this will be -1
and evaluate to true
, resulting in 0/0
. I suggest starting nStakesHandled
at 0.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right I think. I will subtract one from the counter at the end, because the first trip through doesn’t actually do anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It will still be not zero if there are no stakes. If you make the argument unsigned and the counter start at 0 you'll be safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is what I meant. I will start the counter at zero, but the final value will be one greater than the actual stakes handled...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. @denravonska through Slack, before the comments above, suggested I rename GetPoSKernelPS to something better, since it doesn't affect network protocol. I also still hate the way the function calculates netweight. I tried to adapt the old calculation and I just don't like it. I am going to gut it, rename it GetEstimatedNetworkWeight and simply have it call the new function GetAverageDifficulty and use the conversion factor I already worked out to convert the average difficulty to netweight.
Toggleton made a pull request on my repository to add the tool tips, and I will integrate his changes too... |
Ok. @denravonska through Slack, before the comments above, suggested I rename GetPoSKernelPS to something better, since it doesn't affect network protocol. I also still hated the way the function calculates netweight. I tried to adapt the old calculation and I just don't like it. I gutted it and replaced it with a much simpler function GetEstimatedNetworkWeight that simply calls GetAverageDifficulty and makes a conversion using a constant proportionality factor. (It should be familiar... it is the constant that comes from (MaxHash / StandardDifficultyTarget) * 16 sec / 90 sec. If you divide it by 80 to convert to GRC you get the familiar 9544517.40667.) The last thing I have to do is put in the tool tips for the two new UI fields. --done |
src/main.cpp
Outdated
nStakesHandled++; | ||
dDiff = GetDifficulty(pindex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can slim this down slightly to:
// dDiff should never be zero, but just in case, skip the block and move to the next one.
dDiff = GetDifficulty(pindex);
if (!dDiff)
continue;
dReciprocalDiffSum += 1 / dDiff;
nStakesHandled++;
if (fDebug10) LogPrintf("dDiff = %f", dDiff);
if (fDebug10) LogPrintf("nStakesHandled = %u", nStakesHandled);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ugh. Could create an infinite loop if someone specifies an interval of one, and the first block is bad. It always amazes me how the most simple things can sometimes be the most maddening in programming. I need flip the test around and get rid of the continue. If we select a bad block and exclude from the sum, we still need to do pindex = pindex->pprev to prevent the possible infinite loop.
src/main.cpp
Outdated
CTxIndex txindex; | ||
CBlock CoinBlock; //Block which contains CoinTx | ||
{ | ||
LOCK2(cs_main, pwalletMain->cs_wallet); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need to lock here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I followed what was in miner.cpp on that one. I will try without the lock and see what happens. The less we can lock the better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like it works without the lock.
src/main.cpp
Outdated
// 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
&& nValue >= 1250000
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More efficient but a little more cryptic. I’ll change it.
12d8484
to
5635827
Compare
I just slack messaged with @denravonska and he agreed that I should put in an assertion for the valid range of the input parameters. I am doing that now and reposting the pr. |
38674f4
to
59af9df
Compare
That should do it unless someone sees something else... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome math, good refactoring, few messed up indents. I will merge locally and give it a go.
src/main.cpp
Outdated
// get the familiar 9544517.40667 | ||
result = 763561392.533 * GetAverageDifficulty(nPoSInterval); | ||
if (fDebug10) LogPrintf("Network Weight = %f", result); | ||
if (fDebug10) LogPrintf("Network Weight in GRC = %f", result / 80.0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing log prefix. Or is it now handled by the logging function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. I missed it. I should add it and repost the commit...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are more occurences. Btw you do not have to ammend every time, prs can take multiple commits, it's up to you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I got all of them. I also changed them all to actually specify the exact function name. I was using ETTS...
// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
woah
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah... it is a bit wild, but better than looping every ETTS mask quantum to prevent overshooting. That would require 225+ loops. Most of the time there will only be a few UTXO ends “events” in the outer loop... in many instances only 1 to 3, and this reduces the loop workload both outer and inner considerably...
Also, as you noticed, the same code handles the “tail”, which is very efficient. For lower balance folks, most of the time all of their UTXO’s will be mature, and so the main loop gets iterated once with the current time to add up all of the UTXO probabilities and then this part of the code calculates the nThrows (time).
src/main.cpp
Outdated
|
||
// Derive a smoothed difficulty over desired PoSInterval of blocks by calling GetPoSKernelPS() which is netweight and reverse engineering... | ||
double dDiff = GetAverageDifficulty(nPoSInterval); | ||
if (fDebug10) LogPrintf("ETTS debug: dDiff = %f", dDiff); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allow this difficulty to be overridden by function parameter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... Both GetAverageDifficulty and GetEstimatedTimetoStake have default nPoSInterval arguments of 40. The intent here is to allow someone calling GetEstimatedTimetoStake a different choice of number of blocks back to look to average the Difficulty than the default 40. If I just called GetAverageDifficulty() here without the argument, it would be fixed at the 40, and I would not use the default argument nPoSInterval on GetEstimatedTimetoStake.
So if someone calls GetEstimatedTimetoStake(80, 0.63) this would send 80 on through to line 556 as GetAverageDifficulty(80), which is what we want I think...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, I can see someone would want to see ETTS at arbitrary difficulty, not just the current. Instead of nPoSInterval
and calculatiing the average difficulty internally, the function would take difficulty as argument.
GetEstimatedTimetoStake(GetAverageDifficulty(80), 0.63)
or
GetEstimatedTimetoStake(4, 0.70)
However I am very satisfied with the pr how it is now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get what you are saying now. Do you want me to change it and repush the commit? It is pretty easy... Oh wait... in the header you would need...
double GetEstimatedTimetoStake(double dDifficulty = GetAverageDifficulty(40), double dConfidence = 0.8);
to allow someone to simply say GetEstimatedTimetoStake(); and mean get the average diff for the last 40 blocks and confidence of 80%. Can you specify a default value = to a function in the header?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not think so, but you can do double GetEstimatedTimetoStake(double dDifficulty = -1, double dConfidence = 0.8);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did dDifficulty = 0.0, since normally specifying a dDiff of 0 is nonsensical. Makes the assertion for crazy parameters easier.
src/main.cpp
Outdated
// The old calculation for comparative purposes... | ||
double oldETTS = 0; | ||
|
||
oldETTS = GetTargetSpacing(nBestHeight) * GetEstimatedNetworkWeight(nPoSInterval) / MinerStatus.WeightSum; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this used except for debug? If move it inside the if.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. I should move it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Done!
Ok I made some tweaks based on @tomasbrod's comments and also some other minor cleanups (including removing the commented out lock we didn't need). |
After pondering some more whether the harmonic mean and arithmetic mean is the best for averaging diff, I have decided to change it to arithmetic. I actually have created a spreadsheet which shows the response of the retargeting algorithm to when a whale staker (or several) comes online and "stakes", and then goes away. In reality the situation would be a bit more spread out and more random, but the model shows the overall effects. The arithmetic average responds better on an increasing of Diff (more coins staking than before), while the geometric responds better when Diff decreases. They are pretty similar though, so I have elected to go back to arithmetic because it is more understandable for people. Please see the link to the google sheet if you want to examine more closely... |
The spreadsheet deserves some detailed commentary to help people interpret it. I have added two graphs to provide a visual of what is going on. This is a "simulation" of 1000 blocks and what happens to measured diff and estimated netweight when large stakers (whales) come on and off. The upper portion of the spreadsheet includes parameters that are defined from the current code. I start the simulation with the staking population of GRC = 10000000 and an initial diff of 1.0. (This is slightly out of equilibrium. An equilibrium diff of 1.0 -> GRC of 9544517.) You will notice that the diff adjusts to the equilbrium value of 1.04768. At block 120 I raise the GRC population to 20000000, simulating a 10000000 GRC whale suddenly coming online. At block 251 I have the whale dropping out and the GRC population back to 10000000. Then at block 500 I add in a much bigger whale at 32000000 (raising the GRC population to 42000000) and then at 751 the whale drops off. The graphs vividly show the effects. When the whales come online, the block frequency temporarily increases. The retargeting algorithm acts to raise diff to compensate, bringing the block frequency (spacing) back to the target values. The inverse happens when the whales drop off. The graph shows what happens to the measurable quantities. Interestingly the arithmetic average more quickly converges on the "truth" when the GRC goes up, and the harmonic is quicker to converge with the GRC goes down. You can also see the lag in the response of the measurable quantities to "reality". |
Corrects netweight -- replaces the old GetPosKernelPS() with a new function GetEstimatedNetworkWeight(). (The old function does not have anything to do with network protocol and was misleading.) Implements new EstimatedTimetoStake() with a better algorithm and replaces denormalized code in both GUI and RPC areas. Implements new GetAverageDifficulty(). Changes the staking tooltip to use the correct functions. The overview ui will be dealt with in a separate pr/commit.
f14c1d8
to
50de265
Compare
Upon conferring with @denravonska, he concurs, and prefers, that we move UI work to a separate PR. I have therefore removed the overview.ui changes and only retained the corrections to the staking tooltip functions that were put in. We will open separate PR(s) to deal with the overview page UI overhaul. |
This is intended to be responsive to issue #732.
GetPosKernelPS() has now been corrected to return more realistic netweight values and renamed to GetEstimatedNetworkWeight to more accurately reflect its function. Internally these are now in correct weight units 80 * GRC.
There is a new GetAverageDifficulty() function which returns the correct average diff.
Note both of these functions now take an optional argument of unsigned integer number of blocks to check back from current height, and default to 40 if no argument provided. The original hardcoded value of 72 everyone agreed was too long and seemingly arbitrary.
The new GetEstimatedTimetoStake function replaces the original simple formula (which never properly took into account UTXO's about to be stakeable, but not yet stakeable, among other things), and which was spread out in the code in several areas. This function also takes optional arguments of diff and the confidence level as doubles. These default to 0.0 (which is detected and processed internally as GetAverageDifficulty(40)) and 0.8 (80%) respectively.
I have replaced the denormalized calculations in the code with calls to GetEstimatedTimetoStake in both the GUI and rpc areas.
I have also put two new fields on the main UI status screen, Est. TTS and Est RR/day. The net weight field displays net weight in units of GRC, which is the internal net weight / 80.(See the comments towards the bottom on this. The staking tooltip has been corrected but we are going to do overview UI improvements in another pr.)Please see my comments in the code itself, and the discussion on Slack #development for additional details/observations.
Given the complexity of the algorithm, I have taken pains to make it as efficient, and take locks for as short as time possible. We need to test it on a wallet that has a really large number of UTXO's.
Note that the new ETTS algorithm gives radically different (and correct) ETTS values in the corner case of a big UTXO which is about to come off cooldown, but with only small UTXO's stakeable. The old calculation would only consider the currently stakeable (small UTXO's) sum and give a ridiculously long ETTS, when in fact the big UTXO would be stakeable very shortly and has a large probability of staking (i.e. short ETTS).
It also deals correctly with the middle ground of suboptimal UTXO count... If you have a relatively large balance, but have less than the optimal number of UTXO's, it will provide more realistic (longer in this case) ETTS values.