-
Notifications
You must be signed in to change notification settings - Fork 183
Automatic stake UTXO splitting to optimize staking efficiency #1244
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
Automatic stake UTXO splitting to optimize staking efficiency #1244
Conversation
aa26c36
to
4e0134c
Compare
Here is the stake output... 08/03/18 22:46:26 CreateCoinStake: Found Kernel; |
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.
split into multiple functions
one that calculates how many outputs to split to
second that does the actual splitting
called from the main miner function
That makes it a lot harder Tomas and what does it get you? Those functions would be captive and never used by anything but the miner. |
It will make it prettier... :). Before I go to the trouble to split off into separate functions, do you buy into my implementation concept? |
Tomas. Looking at this more carefully. I agree with putting the number of UTXO's calculation into a helper funtion. It seems wrong to me to actually create another function to "split" the UTXO's, because they are not really being split. The calculation determines the number to "create" and then push is done to create them. This properly belongs in CreateCoinStake I think... Further, we add the reward to the same UTXO's later in CreateGridcoinReward. |
5517a32
to
36aee75
Compare
Implemented unsigned int GetNumberofStakeOutputs(int64_t nValue). Also changed upper efficiency limit to 98%. 99% is too aggressive and unnecessary. |
This is the reason why i want it split. CreateGridcoinReward is called after CreateCoinStake and would only add reward to the 1nd output. You are duplicating (not fully) the splitting code both in CreateCoinStake and CreateGridcoinReward. If the splitting was done after all reward manipulation, only once. |
It adds rewards to both outputs because I put a loop in there. I agree it duplicates the loop. The problem is what it CreateCoinStake doing anymore if it doesn't actually create the UTXO's. I will discuss with you on Slack/Rocket. |
36aee75
to
19fe491
Compare
Ok @tomasbrod. I created void SplitCoinStakeOutput(CBlock &blocknew). Please take a look again. I think this does it... |
@@ -600,7 +600,6 @@ bool CreateCoinStake( CBlock &blocknew, CKey &key, | |||
|
|||
txnew.vout.push_back(CTxOut(0, CScript())); // First Must be empty | |||
txnew.vout.push_back(CTxOut(nCredit, scriptPubKeyOut)); | |||
//txnew.vout.push_back(CTxOut(0, scriptPubKeyOut)); |
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 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 left that code in there commented out, probably from the original write of V8. I figured it should go away.
void SplitCoinStakeOutput(CBlock &blocknew) | ||
{ | ||
// When this function is called, CreateCoinStake and CreateGridcoinReward have already been called | ||
// and there will be a single coinstake output (besides the empty one) that has the combined stake + research |
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.
great candidate for assert()ion
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.
What are you suggesting to assert? There is no condition in any of that code that is expected to fail. It is a no-op if the number of UTXO's is 1, and GetNumberofStakeOutputs is clamped. If CreateCoinStake and CreateGridcoinReward have succeeded (because we have arrived at the call to SplitCoinStakeOutput), then CBlock StakeBlock already exists and is in the proper form.
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.
assert(blockNew.vtx.size() == 2)
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
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.
@tomasbrod I think you actually meant assert(blockNew.vtx[1].vout.size()==2)...
src/kernel.h
Outdated
@@ -69,4 +69,6 @@ int64_t CalculateStakeWeightV8( | |||
const CTransaction &CoinTx, unsigned CoinTxN, | |||
const MiningCPID &BoincData); | |||
|
|||
unsigned int GetNumberofStakeOutputs(int64_t nValue); |
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.
Definition in miner but declaration in kernel.h? Make it consistent, please.
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 don't even have a CreateCoinStake declaration in a header file @tomasbrod ... And for that matter, I didn't make a declaration for SplitCoinStakeOutput... so I need to fix that. I don't even know why the compiler is not barking about it, or about your original CreateCoinStake function that has no declaration. I will move the GetNumberOfStakeOutputs and also add SplitCoinStakeOutput to miner.h.
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 have declaration, because the function is not used nowhere, but the file it is defined in. Yes, correctly is should have a declaration or be made static. I am commenting on the fact that you put the declaration to kernel.cpp
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 do not need declaration if the function is used below it's definition (definition is a declaration)
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's actually good to leave it out so the user API becomes more clear. It does require to extern declare it in the test suite, so don't make it static. You could argue that you should test through the public API. While true, in this case, I suspect the actual unit test will become too hard to both read and write.
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.
Yep. Really hard to unit test this. I moved the declarations to miner.h. Are you guys ok with that?
src/miner.cpp
Outdated
// Pull Minimum Post Stake UTXO Split Value from config or command line parameter. | ||
// Do not allow it to be specified below 800 GRC. | ||
nMinUTXOPostSplitValue = GetArg("-minimumstakeutxovalueforsplit", 800) * COIN; | ||
nMinUTXOPostSplitValue = (nMinUTXOPostSplitValue < 800 * COIN) ? 800 * COIN : nMinUTXOPostSplitValue; |
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.
use std::max
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.
Everything doesn't have to be done with methods @tomasbrod. I will do it, but the request to change is silly. You are lucky. std::max is defined in C++11. Its use here requires a cast of the 800 to int64_t because std::max does not like to compare with different integer types.
if (dEfficiency > 0.98) | ||
dEfficiency = 0.98; | ||
else if (dEfficiency < 0.75) | ||
dEfficiency = 0.75; |
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.
use std::clamp( val, low, high )
include <algorithm>
if not already declared
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 return fast if nValue < nMinUTXOPostSplitValue
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't use std:;clamp. It is C++17 and is not available in many compilers.
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 with the fast return and implemented 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.
I hate that it took them this long to implement a clamp instead of the ugly std::max(lower, std::min(n, upper));
construct.
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 construct is uglier to me than doing the clamp with straight if statements... I agree with 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 forgot to change line 715 to a std::max. I will do it in the morning.
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.
Done.
src/miner.cpp
Outdated
else if (dEfficiency < 0.75) | ||
dEfficiency = 0.75; | ||
|
||
LogPrintf("GetNumberofStakeOutputs: dEfficiency = %f", dEfficiency); |
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.
Please remove prints like this, or at least condition under fDebug2/fDebug
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.
Then you should remove the original prints from CreateCoinStake, which are similar.
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 we are going to be consistent with the LogPrintf's in CreateCoinStake (at 605 for example), then the ones at 663 and 671 should remain non-conditional. I have conditioned all others on fDebug2.
src/miner.cpp
Outdated
// Set Desired UTXO size post stake based on efficiency and difficulty, but do not allow to go below MinUTXOPostSplitValue. | ||
// Note that we use GetAverageDifficulty over a 4 hour (160 block period) rather than StakeKernelDiff, because the block | ||
// to block difficulty has too much scatter. | ||
nDesiredStakeUTXOValue = 9942.2056 * GetAverageDifficulty(160) * (3.0 / 2.0) * (1 / dEfficiency - 1) * COIN; |
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.
please link to explanation of the constants (file under doc/ or steemit)
move 9942.2056 constant somewhere up
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 a good point. I can do that, since I did all of these calculations... the problem is that the final blue paper is not done yet, only the initial draft. I will put a link to the initial draft paper.
I should have done the requested changes instead of talking about them. Anyway. Let's talk about the config options. |
I don't like combining the tunables. It makes it more cryptic. We have ENOUGH cryptic code in this codebase. Option names should be descriptive of what they do, just like variable names/functions, and they should not be overloaded unless absolutely necessary. I will be happy to shorten them and will take suggestions as long as they are descriptive. |
I should have done the requested changes instead of talking about them. @tomasbrod That is insulting to me. Sorry to have wasted your time. |
19fe491
to
d033f5d
Compare
src/miner.cpp
Outdated
|
||
// Pull Minimum Post Stake UTXO Split Value from config or command line parameter. | ||
// Default to 800 and do not allow it to be specified below 800 GRC. | ||
nMinUTXOPostSplitValue = GetArg("-minimumstakeutxovalueforsplit", (int64_t)800) * COIN; |
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's a small bug here where the lower cap is 800 Halfords, not coins, if min stake value is set to 0. You could also extract the constant to avoid someone changing one but not the other.
namespace
{
const uint64_t MIN_STAKE_SPLIT_VALUE_GRC = 800;
}
// ...
const int64_t = std::max(
GetArg("-minimumstakeutxovalueforsplit", MIN_STAKE_SPLIT_VALUE_GRC),
MIN_STAKE_SPLIT_VALUE_GRC) * COIN;
Edit: MIN_STAKE_SPLIT_VALUE_GRC
was a poor name as it's not in coins. Ignore my std::max construct as it made things less readable :D But the bug remains.
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.
u are right. Good catch! I could fix it equivalently by changing line 692 to nMinUTXOPostSplitValue = max(nMinUTXOPostSplitValue, (int64_t)800 * COIN);
which do you think is 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.
Do the small change and let's assume that the next author is a sensible adult.
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 we should define a system constant for the 800. Might as well do it now. And the 800 is effectively in GRC so I think your constant name is good! Now... where to put 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.
I prefer your approach @denravonska. I am putting the constant in miner.h as
// Note the below constant controls the minimum value allowed for post
// split UTXO size. It is int64_t but in GRC so that it matches the entry in the config file.
// It will be converted to Halfords in GetNumberOfStakeOutputs by multiplying by COIN.
static const int64_t MIN_STAKE_SPLIT_VALUE_GRC = 800;
I have to do it this way to avoid a template error, because the GetArg is int64_t.
I tried to applologise for wasting your time by ordering you around to change this and that. Sorry |
Then I owe you an apology. I mistook your comment. Just know that I am the type person that likes to finish what I started... |
d033f5d
to
c4233b6
Compare
Ok. I think everything is in there. The only thing left is to shorten the config params if you both don't like the longer names... :) |
I would prefer a shorter and more to the point name but I cannot come up with anything better. |
Me either... |
I think it is ready for general testnet testing... I have been running in testnet already in the previous guises, and it appears to work nicely. |
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.
utACK agree for testing if everyone comes to consensus on moving forward
c4233b6
to
4768154
Compare
Ok! @barton2526 gave me some inspiration... I have changed the config option names to... minstakesplitvalue Much better! I think this is ready for testing now! |
This modifies the miner to split stake outputs if necessary to optimize UTXO size.
4768154
to
6e85632
Compare
Closing in favor of #1265 |
Side staking (extends stake splitting PR #1244)
merging this as well (no-op code-wise) would grant James bit of stats. |
I didn't even think about that! :) |
This PR implements three conf file parameters:
minstakesplitvalue
enablestakesplit
stakingefficiency
enablestakesplit=1 will enable the automatic splitting of UTXO's in the coinstake transaction (stake outputs). Zero is the default (disabled).
stakingefficiency=xx is an integer that specifies the desired staking efficiency. This is constrained by the code to be between 75% and 98%, in case someone puts some nonsense value in there.
minstakesplitvalue=xxx is an integer that specifies the minimum UTXO size desired post split to provide a secondary control on UTXO size. If difficulty drops and a high efficiency is specified, the efficiency alone may split UTXO's into amounts smaller than the user desires. This will prevent that from occurring. If a user specifies less than 800 GRC, then the code uses 800 GRC. Note that the stake splitter uses a 160 block averaging interval for calculating the difficulty to smooth out the difficulty swings.
The code is implemented as a modification to the UTXO push at the end of the CreateCoinStake, and the addition of the Gridcoin reward to the UTXO(s) at the end of CreateGridcoinReward. Note that the code can handle an n-way UTXO split in the case of giant UTXO's that need to be split more than 2-way, but there is a current limit placed of 2-way because of the limitation of the number of Coinstake outputs in AcceptBlock. This may be changed in the future.
This addresses issue #1240.
This PR is already staking successfully on testnet with my testnet Windows node.