spacemeshos / SMIPS

Spacemesh Improvement Proposals
https://spacemesh.io
Creative Commons Zero v1.0 Universal
7 stars 1 forks source link

SVM Gas-Metering #82

Closed YaronWittenstein closed 1 year ago

YaronWittenstein commented 2 years ago

Gas Metering

Overview

This document describes what should be done to support Gas-Metering as a pricing mechanism for running Transactions. Since the Templates released in the 1st Mainnet will be compliant with the Fixed-Gas, everything described under this piece will be relevant only for future versions.

Goals and Motivation

The motivation for the Fixed-Gas Wasm pricing was that Templates that abide by specific rules would result in more precise pricing for their transactions.

In short, it boils down to whether a Template contains potential infinite loops. If we have code that contains no loops, call-cycles, recursions, and dynamic-dispatch on runtime. We can apply static-analysis techniques and compute the gas price for running a piece of code ahead of time.

Since many Templates fall into that category, we can optimize their pricing. So the decision was that we'd make sure that the Templates for Genesis would respect these rules.

However, some Templates require more flexibility. In such cases, we'll have to introduce another pricing mechanism. This SMIP details how to add the Gas Metering into SVM - the Gas Metering is the most commonly used solution today for pricing running code.

The downside is that we can't precisely predict how much Gas would suffice for running a Transaction without hitting Out of Gas (see the Halting Problem). Current solutions usually simulate a run, and then they add some extra for the Gas Limit set into the Transaction.

Another standard handling is looking at historical Transactions and making a sophisticated guess. Most of the time, it'd work fine. Every now and then, the guess won't work, and the Transaction will reach Out of Gas and fail to complete.

High-level design

Instructions Injection

To implement Gas Metering on a Wasm program (i.e., the Template's code), we need to inject new opcodes into the program. The most naive solution would be to inject a host function call (such as inc_gas) after each instruction. However, that would result in a much slower running time.

The solution should probably update the used Gas once every couple of Wasm instructions. For example, each new scope should start a new measurement and end the previous one.

Here is an example:

Given this Wasm program sketch:

(block
  op1
  op2
  op3 

  (loop (result i32)
    op4
    op5
    op6
  )

  (if (result i32) 
    (then
      op7
      op8
    )
    (else
      op9
      op10
    )

   op11
   op12
)

After injecting the inc_gas calls (inc_gas is the name of the host function that tracks the gas usage), the newly transformed code will reflect the changes:

(block
  op1
  op2
  op3 
  i32.const op_price(op1) + op_price(op2) + op_price(op3)
  call inc_gas

  (loop (result i32)
    op4
    op5
    op6
    i32.const op_price(op4) + op_price(op5) + op_price(op6)
    call inc_gas
  )

  (if (result i32) 
    (then
      op7
      op8
      i32.const op_price(op7) + op_price(op8) 
      call inc_gas
    )
    (else
      op9
      op10
      i32.const op_price(op9) + op_price(op10)
      call inc_gas
    )

    op11
    op12
    i32.const op_price(op11) + op_price(op12)
    call inc_gas
)

important:

Wasm Runtime-Agnostic

The critical thing is that the applied solution won't assume anything specific to the underlying Wasm Runtime used.

Today, SVM uses [Wasmer](https://github.com/wasmerio/wasmer) solely, but it could have also used [Wasmtime](https://github.com/bytecodealliance/wasmtime). Furthermore, it's advised that SVM will support both in the future and that the used Wasm runtime will be configurable. That would strengthen the system as a whole.

It's good to know that Wasmer has a solution used by some of the Wasm-based Blockchain companies, it's called the Metering Middleware

https://github.com/wasmerio/wasmer/blob/a66226594f5035c6a4816ad338c257f2ce5c5c86/lib/middlewares/src/metering.rs

Due to the described above, it seems that SVM would be better to apply its transformation and not rely on this one. We don't want the injected code having Globals named wasmer_metering_remaining_points or wasmer_metering_points_exhausted. Of course, the inc_gas stated above could be an imported Global Variable instead of an imported host function.

In addition, Wasmer's middleware is part of the compilation process. So I'd recommend assuming the least about the Wasm runtime API and just having the transformation execute before compiling the Wasm module. One existing tool for transforming Wasm is [walrus](https://github.com/rustwasm/walrus), and maybe there're other similar Rust libraries.

Hybrid Mode

Once the Gas Metering mechanism is implemented, we could introduce a new one I'm calling Hybrid Gas Pricing. Given a Template with Fixed-Gas Wasm code, we can set the Gas Limit of the Transaction to the value provided by the Fixed-Gas Pricing.

That value accounts for the most expensive running path the Transaction could end up having. Having the Hybrid mode used will apply Gas Metering and pay for what's been used. Since we've set the Gas Limit to the worst-case scenario - we can know for sure that the Transaction won't fail due to Out of Gas.

Questions/concerns

TBD

Stakeholders and reviewers

@neysofu @avive @moshababo @lrettig @noamnelke

neysofu commented 2 years ago

Not closing this SMIP because it's not strictly outdated per se, but gas metering within the SVM is so far down the road that it doesn't currently make sense to talk about this.

lrettig commented 1 year ago

Closing this now as we're not currently pursuing a Wasm-based VM. Can revisit in the future if we go this route.