Increase rewards for large space ATX

re-posting from internal conversation from Tal

Here’s one proposal reward scheme:

  • Right now, IIRC, we have
ballot_weight = ATX_weight / num_ballots 
proposal_reward = total_layer_reward * ballot_weight / total_layer_weight
  • The idea is to do this instead:
ballot_weight = ATX_weight / num_ballots 
ballot_gas_cost = ATX_size * storage_gas_cost / num_ballots 
proposal_reward = max(0, 
total_layer_reward * ballot_weight / total_layer_weight - 
ballot_gas_cost * ballot_gas_price)

The open questions are:

  • how to set ballot_gas_price. (If we make it the same as the layer’s gas price, then we might have some weird incentives – but I haven’t thought about it too deeply)
  • whether/how to burn and/or distribute the storage costs for ATXs. One way to do it is to simply redistribute in the same layer:

final_proposal_reward = total_layer_reward * proposal_reward / sum_of_proposal_rewards

re-posting from internal conversation from Iddo:

This should be good, but I have the following comments:

  1. I think that final_proposal_reward shouldn’t be calculated as you said because “proposal_reward=max(0,…)” so when proposal_reaward is zero then final_proposal_reward is also immediatey zero. Instead we can have leftover_reward=total_layer_reward-sum_of_proposal_rewards and final_proposal_reward=proposal_reward+leftover_reward*ballot_weight/total_layer_weight
  2. About ballot_gas_price, what do you mean by the “layer’s gas price” ? Do you mean the median gas price of the effective transactions of the layer, or the median among the gas price threshold that the proposals (perhaps of the previous layer) specified ? We need the latter for the “collectively specified minimal fee”, but I don’t think that it’s implemented yet. Taking the median among the effective transactions should be ok, but we won’t need to compute this median in the future, so if we already implemented gas price threshold field in each tortoise proposal block then it’d be best to take this median.

re-posting from internal conversation from Iddo:

@talm @selfdual-brain the simple-non-market proposal for ballot_gas_price at genesis:

  1. We know for example that there are 500 smesh coins at each layer and 50 minimum-space identities rewarded in each layer, so each minimum-weight-identity receives 10 smesh coins (if the identity has e.g. twice the minimum weight then it will receive 20 coins).
  2. We set ballot_gas_price for the formula so that “ballot_gas_price * ballot_gas_cost” is small relative to 10 smesh coins, for example “ballot_gas_price * ballot_gas_cost == 0.5 smesh coins” ,
  3. So identity pays the storage fee per reward, even though the ATX storage burden on the network is per epoch. If there are few ATXs in the entire epoch then the identity will need to pay the 0.5 coins fee many times, and if there are many ATXs in the epoch then the identity will need to pay the 0.5 coins fee maybe only once. In other words, the storage fee is proportional to the identity’s total reward, even though the storage burden on the network is additive and not proportional.
  4. We hope that the latter case is more likely (i.e. that there many ATXs in the epoch), otherwise it will be that e.g. 5126gb ATX will pay 2.5% proportional fee, whereas 256gb ATX will pay 5% proportional fee.
  5. We would have preferred that it’d be a flat fee, i.e., the 256gb ATX pays a flat fee of x coins in total for the epoch, and the 512gb ATX also pays a fee of x coins for the entire epoch. This can be done via stateful reward function that confiscates the “ballot_gas_cost * ballot_gas_price” amount only from the reward for the first tortoise proposal of the identity, or via stateless reward function that confiscates “ballot_gas_cost * ballot_gas_price / num_of_eligibilities” from each of the rewards that the identity earns.

Re-posting the reply by @iddo from 2023-05-02:
@talm @iddo Please continue the discussion here (i.e. not on Slack).

Here is the entire fomula (the commented out “//” lines will be useful later with market gas price, so at genesis they can appear but be commented out in the code).

// approx 500/50=10 smesh reward for each pblock in the layer
non_market_gas_price = 0.5 smesh
//ballot_gas_price = non_market_gas_price / (ATX_size * storage_gas_cost)
ballot_weight = ATX_weight / num_ballots
//ballot_gas_cost = ATX_size * storage_gas_cost / num_ballots
//proposal_reward = max(0, total_layer_reward * ballot_weight / total_layer_weight - ballot_gas_cost * ballot_gas_price)
proposal_reward = max(0, total_layer_reward * ballot_weight / total_layer_weight - non_market_gas_price / num_ballots) 
leftover_reward = total_layer_reward - sum_of_all_proposal_rewards
final_proposal_reward = proposal_reward + leftover_reward * ballot_weight / total_layer_weight

The goal stated again

The performance of Spacemesh network decreases with the number of nodes. On one hand we want to encourage decentralization, so we want many miners. On the other hard we want the network to perform smoothly.

The expected but unwanted phenomenon is “miner id splitting”. That is - a single entity (person, organization), given certain storage resources, splits their storage to smaller pieces and so establishes many independent miner identities. This is legal but will cause degradation of performance.

We seek for a simple yet effective solution to discourage splitting behaviour. Our primary hope is to adjust rewards algorithm so that a suitable economical incentive would emerge, counter-acting the splitting.

Simplified model of rewards

For the general discussion of possible solutions I will use a simplification of our current rewards model (which is nevertheless detailed enough for the purpose):

  • for any given epoch e there is a number R(e) (it is not crucial how this number is calculated).
  • for a miner with absolute weight w in epoch e, its expected total reward in this epoch will be R(e) * w

In other words: per-epoch reward is proportional to the absolute weight.

Let’s say Alice has storage resources so to establish a single miner with weight x. Or, she can split her resources so to establish two miners with weights x/2 each. Her expected rewards will be:

  • non-splitting: x * R(e)
  • splitting: 2 * (x/2 * R(e))

So, in this rewards model there is no difference. Splitting Alice gets the same reward as non-splitting Alice.

Fixed ATX cost model

The solution suggested by @iddo is based on a simple idea: we should charge every miner participating in an epoch for the pleasure to become the member of this epoch. In other words, this means charging for every ATX.

In the very simple variant (proposed for Genesis), this value to be charged per-ATX is just hardcoded as a protocol constant C. Then the example with Splitting Alice looks like this (and I am ignoring here the leftover re-distribution, which is a cosmetic detail):

  • non-splitting: x * R(e) - C
  • splitting: 2 * (x/2 * R(e) - C)

So, the reward is decreased in the splitting case. This is what we wanted.

Problem #1: Gas cost

Guessing a “good” value of the constant C is rather problematic. We would rather prefer to express C as D * block_gas_price, where D is some protocol-constant denoting a number of gas units we want to charge for each ATX (this one will never change), and block_gas_price we calculate separately per-block (so it will evolve with the changing market of gas prices). The problem here is that is is not easy to find a safe way to establish the per-block gas price (we already had one long discussion on this, no success yet).

Problem #2: Non-proportional incentive

Generally, economical incentives are efficient only if they are proportional to “wealth”. This is true in real life so probably also true for blockchains (and in our case miner’s absolute weight is a good measure of wealth). By splitting into two halves Alice gets a penalty of C, which may be just negligible relative to her weight (i.e. to her per-epoch rewards). So effectively with this algorithm our non-splitting pressure is much higher towards small miners, but not so much towards whales.

Caution: Which may be good if we WANT this ! But if we rather want a similar pressure to everyone, this solution does not work well. My intuition here is that a non-proportional incentive will not work well over time.

Problem #3: The “real” cost of adding yet another ATX is not linear

One could argue that this “fixed ATX cost” algorithm sounds optimal because it maps nicely to technological reality. However, I claim that adding yet another ATX causes possibly some overflows and capacity issues which I doubt are linear. They might be linear when the network is small, but eventually some limits will be reached.

Proportional incentive via weights scaling

Sketchy idea

Let’s assume we were able to find some nice function f: ℝ -> ℝ that we will use for scaling absolute weights of miners, and this scaling will be ONLY for the purpose of rewards calculation. If we want the Alice example to work well, the property we desire is that after splitting into two halves, Alice will experience a fixed proportional loss of her rewards. When expressed relative to her weight, this loss should be a fixed value. We just want a simple rule of thumb: something like “when you split to two halves, your rewards go down by 1%”.

Let’s do the math

In the non-splitting case she has the weight x, so he earns the reward following this formula: R * f(x)

In the splitting case she has two miners, each having weight x/2, hence she earns two rewards (one for each miner) following this formula: R * f(x/2).

We want to introduce a protocol constant Θ representing the proportional loss. Say, if Θ = 0.01 then splitting into two halves should cause your rewards to be only (1-Θ) fraction of non-splitting rewards. So, with Θ = 0.01 you will lose 1% of your rewards by splitting your miner into two halves.

This rule leads to the following equation:
R(e) * f(x/2) * 2 = (1-Θ) * R(e) * f(x)

In the above equation, unknown is the function f. This equation can be solved and the family of solutions is (A - arbitrary non-zero constant):

f(x) = A * x^p, where p=1-log(1-Θ)/log(2)

Because any value A does the job, we may take A=1.


Weights scaling function:
f(x) = A * x^p, where p=1-log(1-Θ)/log(2)

… forms a proportional anti-splitting incentive, that is independent of gas price. This incentive is possibly a better solution than the “fixed ATX cost” because it avoids all problems of the other solution.

In this solution, the value Θ is a protocol constant and should be sealed into the blockchain at Mainnet launch. I suggest the value Θ = 0.01.

In the table below there are some example values of Θ and the corresponding exponents p:

Θ        | p
0.005    | 1.0072315692310758
0.010    | 1.0144995696951151
0.020    | 1.0291463456595165
0.030    | 1.043943347587597 

@selfdual-brain Yes, we are walking on thin ice, because on the one hand we want to make it significantly more profitable to consolidate storage (so miners won’t burden the system by splitting their storage into small ATXs in particular for the purpose of reducing their variance during proof generation), but on the other hand we want encourage small miners to participate (so the small miner shouldn’t get a bad deal relative to the large miner).

So if the small miner is getting a fair deal and the large miner is getting a fair deal, such that the small miner is getting a worse deal than the large miner, then hopefully we are ok.

Specifically, the fair deal is that the miner pays a fee (that’s deducted from her reward) for the burden that the entire system endures for verifying and storing her ATX. This fee is the same for the smaller miner and the large miner, because this burden is a function of the ATX size, and the ATX size is practically the same for small miners and large miners.

So every miners pays this fee (reward deduction) that’s flat relative to her reward, so it’s a worse deal for the small miner because the profit of a miner should be approximately proportional to the miner’s number of space units (for example if the miners stores 512gb then her profit should be approximately twice relative to a miner that stores 256gb, so in this example the small miner earns "profit - flat_fee" and the large miner earns "2*profit - flat_fee").
BTW it’s reasonable to assume that a significant enough flat_fee is minor in absolute terms, because the profit margin should be low in a permissionless system (because the barrier of entry into the mining market is low).

So, if this flat_fee is significant enough, then in commonplace situations the rational miner should prefer to consolidate storage. However, the operation of a very large miner (a.k.a. whale) will still probably involve some ATX splitting, for example if the whale has say 64 terabytes then she might prefer to use 2 separate computers with 32tb each (and also reduce her variance w.r.t. proof generation in every epoch), but in this example the burden on the system is minimal because it’s just 2 ATXs instead of 1 ATX.
Note that if this whale would split her 64tb into 256 ATXs of 256gb each, so that she pays the flat_fee for each of her ATXs, then the deal that she gets is exactly the same as the deal that the small miner (who can afford only a single 256gb ATX) gets, because her total fee (reward deduction) is proportionally the same (for each 256gb segment she pays the flat_fee, and the small miner pays the flat_fee for his single 256gb segment).

So we can say that various large miners have sweet spots at which it’s beneficial for them to split their ATXs, but this reward formula still reduces the overall burden of Spacemesh because they’d split less than they would without this formula.

We should be wary of giving the small miners a worse deal than what’s fair (by making the reward even more lucrative to large miners via x^p with p>1 as you suggest), because if Spacemesh is in the hands of small miners then this implies healthier decentralization (better in terms of security and censorship resistance etc.)

About the gas price, after we add the mechanism for the collectively-specified-minimal-fee (which we need anyway), it will allow us to use a market based gas price here.

About “cost of adding yet another ATX is not linear”, I don’t see why. Let’s say that it takes 1 second to verify the validity of an ATX, and that the ATX size is 200 bytes. So if the miner splits her ATX into 2 ATXs, then it will take 2 seconds to verify and 400 bytes to transmit and store.