Frontier: Support for EVM Weight V2

What about instead of adding proof metering to SputnikVM, we instead make SputnikVM generic over gas type?

Context

Current evm gas meter looks rougly like this:

struct Gasometer {
  gas_limit: u64,
  used_gas: u64,
}

Solution proposed by @crystalin:

struct Gasometer {
  gas_limit: u64,
  used_gas: u64,
  // Added fields
  proof_limit: u64,
  used_proof: u64,
}

I propose going

struct Gasometer<Gas> {
  gas_limit: Gas,
  used_gas: Gas,
}

instead.

This way, we not only can make no substrate-specific changes in EVM implementation but also implement a metering system, which is more suitable for substrate-based chains, and also allow said generic gas implementation to support future Ethereum own multi-dimensional weight system, which was already proposed several times.

Problem

The problem with the current system is that we have one Gas2Weight mapping, which can’t be both correct and fair.
In Ethereum, write costs 20000 gas, and the read of cold storage is 2100 gas, ≈ 10 times difference.
But in substrate, write costs 100μs, and read costs 25μs in case of RocksDb, 4 times difference.
And 50μs/8μs in the case of ParityDb, 6 times difference.

Let’s estimate what substrate is capable of, based on RocksDb weights:

  • ReadsPerSecond = 1s / 25μs = 40000
  • WritesPerSecond = 1s / 100μs = 10000

If we base our Gas2Weight mapping on write prices (Which is bad, because changing non-zero storage value to non-zero is much cheaper than cold storage write)

  • GasPerSecond = WritesPerSecond * 20000Gas = 200MGas

We can perform

  • EthereumReadsPerSecond = GasPerSecond / 2100Gas ≈ 95000

And 95000 > ReadsPerSecond, so we’ve got a vulnerability. Not correct.

And if we base our Gas2Weight mapping on read prices

  • GasPerSecond = ReadsPerSecond * 2100 = 84MGas

We can perform

  • EthereumWritesPerSecond = GasPerSecond / 20000 Gas = 4200

And this is ok, as 4200 < WritesPerSecond… Until we account for warm storage (= specified in the EIP-1559 transaction), to which access is cheaper. Not fair.

In both cases, we have a considerable difference between consumed gas and ref_time, which should be consumed.

The situation is even worse in the case of

  • Optimized accesses, for which EVM provides subsided costs, i.e., this is not possible to optimize access_list from EIP-1559 (G_warmaccess, G_accesslistaddress, G_accessliststorage), thus in substrate access_list should not affect the weight calculation.
  • Changing a storage key from non-zero to non-zero (G_sreset) should not be cheaper too, as in substrate this will result in pov consumed for read and write, and this should not be cheaper than cold storage write
  • Bidirectional conversions: in the case of substrate user calling substrate, we have a conversion from gas to weight for price calculation, and in the case of precompile benchmarking, we have a conversion from weight to gas, in the end resulting in inadequate conversions with no means to track proof size reliably

Solution

With that said, I propose the following Gas implementation for the generic gas type proposed, which also allows measuring ref_time part of a 2-dimensional weight system and makes it possible to use benchmarking with precompiles correctly.

struct Gas {
  traditional_gas: u64,
  ref_time: u64,
  proof_size: u64,
}
  • traditional_gas - is a legacy gas value used for backward compatibility (until no solution is proposed on the Ethereum implementation side).
  • ref_time - consumed time, used to return correct weight in post_info.
  • proof_size - for post_info.

How it will work

Each instruction should have its benchmark, according to substrate specifics, and not based on Ethereum specs (Because they are written for Ethereum, not for substrate).

For compatibility with existing Ethereum RPCs, specified gas should also provide some ref_time/proof_size, I.e

let gas_limit = Gas {
  traditional_gas: tx.gas_limit,
  ref_time: tx.gas_limit * RefTimePerGas,
  proof_size: tx.gas_limit * ProofSizePerGas,
}

This way, existing contracts will work the same, subcall gas limit will only limit traditional_gas consumption; as there is currently no way to specify other limits provided by EVM spec, we will be able to limit contract proof_size consumption, and even more, it allows to return correct PostInfo, which may enable increasing Ethereum transaction throughput on substrate chains.

Links:

1 Like