gnolang / gno

Gno: An interpreted, stack-based Go virtual machine to build succinct and composable apps + gno.land: a blockchain for timeless code and fair open-source.
https://gno.land/
Other
901 stars 378 forks source link

Gno.land Fee Parameters #1106

Open MichaelFrazzy opened 1 year ago

MichaelFrazzy commented 1 year ago

Gno.land Fee Parameters

A large amount of work has been completed on the new Gno.land fee equation, though a number of options must be discussed before progress can continue. Principally the parameters used must still be confirmed, followed by the method we’ll use to estimate fees for users (such as t+1 equations I have started on, or dedicated calculations/test transactions run on the network, etc). Basing fees on CPU cycles required and bytes stored results in overestimations of the required Gno.land transaction fee simply expediting a transaction, while underestimating would charge the user nearly 0 fees due to the minimal storage and CPU usage of a failed transaction. Both scenarios preferable to under or overestimating fees required on many similar chains.

A more simple version similar to how ETH predicts gas costs is also being considered, but it is outside the scope of this specific issue. Simulations, variants, and fee prediction implementations will be covered once the base fee equation is approved. A simulation framework will be created to test the resulting equation during all conditions and weighted based on existing smart contract platform data. This is the next step after finalizing the base equation, with the scripts to pull this data already submitted as a Hackerspace PR here.

The main aspects currently under consideration are how the equation accounts for CPU cycles required, bytes stored as a result of the transaction, and overall network congestion as independent variables. An alternative to reduce the total number of calculations needed could be having the storage and CPU cycle equations as the key parameters within the Congestion Level Equation, opposed to having Congestion Level as a separate calculation based on other network metrics such as block size.

For example, the max number of CPU cycles and bytes that the network can handle at any given time could act as the network being 100% congested, updated on a regular schedule to serve as the denominator of the Congestion Level Equation below. If theoretically there were 100 maximum CPU cycles Gno.land could handle at any given time, and 10 were active across the entire network, then the resulting percentage would be 10%. This would be repeated with storage, and the resulting average between the two would be what is plugged into the GNOT Fee Equation (possibly weighted more heavily towards storage or CPU cycle congestion).

Using CPU cycles and bytes stored equations simply as parameters within the Congestion Level Equation would result in less separate calculations needed during fee estimation and final on chain calculation. The downside versus the current, fully independent system is a comparative lack of customization and modularity that an additional congestion metric could introduce. My main concern is implementation issues that either method may pose, calculating on-chain transactions and fee estimation for users. I'm sure we'll also clean up the variable notation a bit, since strict math notation is not always the clearest to read (such as e and E or 0f variables).

GNOT Fee Equation:

Fee Equation 1

F = Total transaction fee

E = Expedite fee (10% extra for validators to prioritize the transaction would = 1.1)

M = Number of CPU cycles needed for the transaction

Δd = Change in persisted data (amount of bytes stored to the blockchain)

f(x) = CPU Cycles Function:

CPU Equation 1

g(x) = Storage Cost Function:

Storage Equation 1

Lf = Max cost per CPU cycle

Lg = Max cost per bytes stored

kg = Steepness parameter for storage cost (how quickly does it ramp up after congestion threshold % is hit)

kf = Steepness parameter for compute cost (how quickly does it ramp up after congestion threshold % is hit)

x0g = Storage exponential threshold

x0f = Compute exponential threshold

e = Euler's number (~2.71828)

x = Congestion Level Equation: = currentResourceUsage/maximumResourceUsage

This WIP GNOT Fee Equation was set up as a custom combination of a logistic growth and exponential equation, with parameters added to simplify any future updates and maximize usability. One such set of parameters are the storage and compute exponential thresholds, paired with steepness parameters for each. While I may move these parameters from their current equations into the Congestion Level Equation, for now, the general purpose is the same. The threshold is the % of overall network congestion at which the fee begins increasing exponentially opposed to semi-linearly. This serves both as a strong deterrent to spam attacks and as a powerful tool for keeping the network from becoming overly congested or expensive beyond the capabilities of the network (during normal use). If the threshold for both storage and CPU thresholds are set to 70%, once 70% usage/congestion is hit in either case the fee would ramp up exponentially. More drastically as the steepness parameter values increase and if both thresholds are hit, opposed to one or the other. This allows not only fine tuning of the starting values through pre-launch simulations, but the ability to quickly adjust these relationships anytime post-launch with minimal complexity.

Considerations

3 Potential Fees Variants Planned:

Package Deployment (Package calls + deployments likely costing a fixed number of uGNOT)

Package Call

Storage Rent, based on realm's Banker Module (post-launch model, potentially subscription-based)

Related Issues

Main Issue - #649

GnoMobile #9

#1067

#1070

MichaelFrazzy commented 1 year ago

@giunatale

giunatale commented 1 year ago

For pricing congestion, I would suggest taking a look at EIP-1559: https://ethereum.github.io/abm1559/notebooks/eip1559.html#subsec-2-3 (https://eips.ethereum.org/EIPS/eip-1559)

In a nutshell, congestion is a metric derived by previous block sizes (max block size is variable with EIP-1559 for the record). If blocks are getting filled to their entirety and block sizes are sufficiently big it likely means the network is under heavy load, so it makes sense to me to use this as an indicator for network congestion. This is simple enough but still quite clever IMHO.

Another thing I wanted to ask - and is maybe simply because I am unable to read lol - but where is the final fee equation? I can only see the different variables defined but no final equation that uses them.

I am also assuming - as also discussed with a few others in person - that CPU cycles are determined using a LUT so that there is a fixed number of CPU cycles per operation. This is required otherwise actual CPU cycles would be machine-dependent and hence non-deterministic, which we definitely don't want.

Are there any issues we foresee estimating Gno.land fees using equations, should we look into emulating ETH's simpler but less accurate methods of estimation? Ultimately needs to be lightweight enough for Gno Mobile as mentioned in GnoMobile issue 9 below.

The only issue I see might be the performance of estimation/computation of fees. If it is non-negligible overhead it might be prohibitive to do. I guess we kind of want to do some simulation or profiling to really see if this is a problem though.

For the cost per CPU cycle and byte stored, would we like to denote this as a fixed uGNOT cost or perform a separate calculation so it is based on USD/USDC? Then from there, the desired uGNOT cost per CPU cycle or byte stored would be calculated based on the USD value to remove GNOT volatility from the equation? If so, this would likely require a trustworthy oracle.

It's definitely easier to not use USD-based pricing, and this is what I would be leaning towards simply for the sake of simplicity. In any case, if we ever consider introducing an oracle for any reason whatsoever the design I like the most and the one I suggest we should consider is the one where validators themselves are also collectively the oracle, similar to what Terra was doing before blowing up: https://github.com/terra-money/classic-core/tree/main/x/oracle/spec It would require some adjusting of course since gno.land is not gonna be PoS, but I really like the fact that validators are also the oracle since it would mean that malicious/erratic behavior could be slashed and therefore somewhat "easily" disincentivized.

MichaelFrazzy commented 1 year ago

@giunatale thank you! Glad you mentioned EIP-1559, I think after this trip it's been tentatively decided that we're going to go with something similar to that in terms of congestion. As long as it can be dynamically updated when needed, which as you said is the case!

Final equation is the top one, then the bottom two equations are to calculate storage and CPU variables for it. As long as we encapsulate any cost in one or the other (storage or CPU), we could pretty easily drop either sub-equation and just use storage instead of both for example.

Definitely agree that it'd be ideal to not base things on USD, does anything come to mind to ensure that there are no scenarios in which someone is paying less than any transaction could cost the network? I'll go through the rest of your comment here in a little more detail soon and maybe we can start some modeling.

I'm thinking the "optional parameters" like threshold and steepness can be left out and added as need be too, just thinking in terms of leaving in a spam circuit breaker and extra "knobs" for anyone building on top. If CPU doesn't need calculated we could potentially cut that whole part out!

giunatale commented 1 year ago

Ok I couldn't see the equations simply because I am using GH with a black background on my laptop, that's why I was confused!

I can see them if I switch to the light theme

giunatale commented 1 year ago

a couple of additional notes/suggestions

on a related note: have gno.land thought about MEV and how it wants to deal with it? I mean we either implement anti-MEV mechanisms in the protocol, or if the chain picks up with usage, MEV will be there, it's kind of inevitable. But it might not necessarily be a bad thing. MEV can be accounted for when designing the tokenimcs as it can be a huge economic driver. If you have time I also suggest looking at the research made by Tarun Chitra on the matter. He is truly a giga brain imho and worth following for better understanding crypto-economics in general

jefft0 commented 1 year ago

The description for the current fee parameters says:

// Fee includes the amount of coins paid in fees and the maximum
// gas to be used by the transaction. The ratio yields an effective "gasprice",
// which must be above some miminum to be accepted into the mempool.
type Fee struct {
    GasWanted int64 `json:"gas_wanted" yaml:"gas_wanted"`
    GasFee    Coin  `json:"gas_fee" yaml:"gas_fee"`
}

I find the parameter name "gas wanted" really confusing. The comment says it is "the maximum gas to be used". This makes total sense. What is "gas wanted"? Is it "gas wanted to be spent" or "gas wanted by the validator"? It would help my brain a lot to call this parameter "MaxGas" or something closer in meaning to "the maximum gas to be used".

The other parameter, GasFee is also confusing to me. The comment says it is "the amount of coins paid in fees", but is this the amount of coins that will be paid no matter what, or is this another maximum? In the end, if the whole point is a situation where "the ratio yields an effective gas price" that the user is willing to pay, then maybe "FeeForMaxGas". (Is that right?) There may be good historical reasons (from other blockchains) for why it looks like this, but now is the time to improve it if it's possible to for it to make sense to someone like me who isn't steeped in years of history.

MichaelFrazzy commented 12 months ago

@jefft0 I'm sorry for the confusion, I had nothing to do with this code so it looks like it's just a placeholder from somewhere else. The base of the system should now be similar to EIP 1559.

We'd set a target block size, a block threshold, and a max amount that gas can be increased each block. For ETH the threshold is 200% and the max increase is 12.5%. So if an ETH block is 200%+ the target block size, gas will be increased by the max 12.5% that block. Less than 200% the target size and that 12.5% scales down until it's at a 0% increase if the block is at 100% the target block size.

So that's the EIP 1559 but we'd still like to combine it with storage and/or CPU usage per transaction when it comes to final fee charged to the user. After a certain % over the target block size or perhaps a certain % of resource allocation I'd still like it to kick into a logistic (ideally) or exponential curve to combat to spam. At this point the blocker is a couple dev meetings we can determine the best way to implement things on their end before I put the final touches on the gas system and fee equation.

Definitely open to any input here based on the above!! From there we can finalize the systems/equations and benchmark all of the opcodes.