Change coin split to 50-50
Closed this issue · 6 comments
Change the code split between Sha3x and Randomx to be 50-50,
@moneromoo pointed out something on the Tari IRC recently that suggested that one algo in hybrid mining may be favoured if the contribution to the overall chain is not equally divided.
I can confirm this is the case by demonstrating it via this in Minotari via the following:
Proof
Assume a chain in equilibrium. i.e. hashrate stays constant and so the difficulty never adjusts. Also for simplicity, achieved difficulty is always equal to the target difficulty. It doesn't change the conclusion.
Under these conditions, there should be ambivalence over selecting a block produced simultaneously via each algo. We can show that this isn't actually the case.
Total work at some time t
, wt = w1 . w2
(the product of the acc work of each algo. This is the square of geometric mean. The criterion we use for overall work).
Assume d1
is the difficulty of algo 1, produced over n1
blocks.
Similarly d2
and n2
.
then,
wt = d1.n1 . d2.n2
Now, A block is produced at the same time on each algo.
For algo 1, the total work, wa
, is now
wa = d1(n1+1)d2.n2
--- (1)
Similarly, wb is the total work calculated via the other algo
wb = d1.n1.d2(n2+1)
--- (2)
For ambivalence, we want wa = wb
, or wa - wb = 0
:
wa - wb = d1(n1+1)d2.n2 - d1.n1.d2(n2+1)
= d1.n1.d2.n2 + d1.d2.n2 - d1.n1.d2.n2 - d1.n1.d2
= d1.d2.n2 - d1.n1.d2
= d1.d2.(n2 - n1)
This is only 0 if n2 = n1
, i.e. if both algos contribute the same number of blocks to the total work calculation.
Since in our case n2 (RandomX) > n1 (Sha3x), wa - wb
is positive, favouring SHA3x.
Note that It doesn't matter about the magnitude of the difficulties on the different chains. If we made SHA3x 60% of blocks, 1-block reorgs would favour RandomX even though it has a lower difficulty.
To see why, look at (1)
wa = d1(n1+1)d2.n2 = d1.n1.d2.n2 + d1.d2.n2
= wt + this block's contribution.
Similarly,
wb = wt + this_block's contribution
= d1.n1.d2.n2 + d1.d2.n1
The increase in total work is the product of the difficulties (making the calc scale-agnostic) times the number of block on the other chain. So if the other chain contributes more blocks then this one, this chain contributes more to the total work for a single block,
There are two ways to resolve this:
- The simplest solution is to change the split between SHA3x and RandomX to 50:50
- Alternatively, keep 40:60, but count the same number of blocks in the accumulated work calculation. This window needs to be "long enough", at least a year's worth of blocks, or even ALL SHA3x blocks and the equivalent number of RandomX blocks.
Clearly option A is simpler to implement.
As a proof of concept I made a quick PR to see how quick option A is,
Turns out, its very simple and easy : #6188
Now, A block is produced at the same time on each algo.
wt = d1.n1 . d2.n2
If we assume algo 1 and algo 2 to be identical, apart from an identifier, requiring identical hash rates to advance one block if each has identical target times, then:
n1 = n2
d1 = d2
- =>
d1.n1 = d2.n2
- => to advance one block
delta_d1 = delta_d2
If the split is not 50:50 then the target times for each must differ.
If target_time_n1 < target_time_n2
, then:
n1 > n2
d1 < d2
- => to advance one block
delta_d1 < delta_d2
and similarly iftarget_time_n1 > target_time_n2
, then: n1 < n2
d1 > d2
- => to advance one block
delta_d1 > delta_d2
This relation will hold in both cases:
d1.n1 = d2.n2
d1 = d2 . (n2 / n1)
- If
n1 = 3/2 . n2
thend1 = d2 . (
n2 / (3/2 . n2)) = 2/3 . d2` - Similarly
delta_d1 = 2/3 . delta_d2
If target_time_n1 < target_time_n2
and a block is produced simultaneously on each algo, then:
delta_d1
is added tod1
delta_d2
is added tod2
delta_d1 < delta_d2
.
For algo 1, the total work, w1
, is now
w1 = (d1 + delta_d1)(n1+1) . d2.n2
--- (1)
Similarly, wb
is the total work calculated via the other algo
w2 = d1.n1 . (d2 + delta_d2)(n2+1)
--- (2)
w1 = (d1 + delta_d1)(n1+1) . d2.n2
= (d1.d2.n2 + delta_d1.d2.n2)(n1+1)
= d1.n1.d2.n2 + (delta_d1.n1.d2.n2 + d1.d2.n2 + delta_d1.d2.n2)
= wt + this block's contribution
w2 = wt + this_block's contribution
= d1.n1 . (d2 + delta_d2)(n2+1)
= (d1.n1.d2 + d1.n1.delta_d2)(n2+1)
= d1.n1.d2.n2 + (d1.n1.delta_d2.n2 + d1.n1.d2 + d1.n1.delta_d2)
= wt + this block's contribution
Considering "this block's contribution" with n1 = 3/2 . n2
, d1 = 2/3 . d2
and delta_d1 = 2/3 . delta_d2
, for w1
delta_d1.n1.d2.n2 + d1.d2.n2 + delta_d1.d2.n2
= 2/3 . delta_d2 . 3/2 . n2 . d2 .n2 + 2/3 . d2 . d2 . n2 + 2/3 . delta_d2 . d2 . n2
= delta_d2 . n2 .d2 . n2 + 2/3 . d2 . d2 . n2 + 2/3 . delta_d2 . d2 . n2
= delta_d2 . d2 . n2^2 + 2/3 . d2^2 . n2 + 2/3 . delta_d2 . d2 . n2
and for w2
d1.n1.delta_d2.n2 + d1.n1.d2 + d1.n1.delta_d2
= 2/3 . d2 . 3/2 . n2 . delta_d2 . n2 + 2/3 . d2 . 3/2 . n2 . d2 + 2/3 . d2 . 3/2 . n2 . delta_d2
= delta_d2 . d2 . n2^2 + d2^2 . n2 + delta_d2 . d2 . n2
=>
w1 = d1 . n1 . d2 . n2 + delta_d2 . d2 . n2^2 + 2/3 . d2^2 . n2 + 2/3 . delta_d2 . d2 . n2
w2 = d1 . n1 . d2 . n2 + delta_d2 . d2 . n2^2 + d2^2 . n2 + delta_d2 . d2 . n2
w2 - w1 = 1/3 . d2^2 . n2 + 1/3 . delta_d2 . d2 . n2
=>
w2 > w1
favouring algo 2
I think there could still be some problems that we'll still find out about.
I like the 50/50 method because we don't have to scale by a floating number
Simpler is always better, and I see no real benefit in having more blocks on 1 algo than the other one.