tari-project/tari

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 if target_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 then d1 = 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 to d1
  • delta_d2 is added to d2
  • 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.