EthermintをforkしてeWASMを追加した


最近、(自分の中で)WebAssemblyがきてます。
自分が普段関わってるBlockchainの分野でもWebAssemblyの導入が多く議論されています。
その一つにEthereum上でwasmを動かすeWASM VMがあります。

開発が止まっているみたいですが、testnetも用意されています。

これには、heraというeWASM VMが使われていますが、こちらもかなり前から開発が止まっているようでした...

さらに調査してみるとSecond StateというプロジェクトでWebAssemblyを使ったVMが開発されていることが分かりました。

Official: https://www.secondstate.io/
Github: https://github.com/second-state/SSVM

ということで、今回はeWASMとそれに関連したSecond Stateのプロジェクトについて調べてみました。
そして、最新のgo-ethereumをimportしてeWASMが使えるようにEthermintを改造してみました。

eWASMについて

Ewasm 1.0

  • wasmのサブセット
  • 各命令の前にuseGASを挿入して、GASのコストを計算する
  • eWASM Contract
  • 不動小数点を使わない
  • Ethereum Environment Interfaceのモジュール のみをimportし、他のモジュール はimportしていない
  • mainとmemoryという2つのシンボルをexportする
    • main: VMが実行する関数
    • memory: EEIのモジュール が書き込むメモリスペース
  • Ethereum Environment Interface(EEI)

    • eWASM ContractがEthereumにアクセスするためのAPI
    • WebAssemblyのModuleとして実装され、eWASMの中でimportして使う
    • 例えば、Ethereumの特定のAccountの残高を取得するgetBalance, Accountにメッセージを送るcallなど 元々、EVM1の命令として実装されていたが、WASMではこのような命令はないので、モジュール としてimportして使う仕様となっている
  • System Contract

    • eWASM VMが利用するContract
    • Contractとして定義されたインターフェース
    • 仮想マシンの外で実装したいロジックをContractの形で定義しておき、仮想マシンがcallして使う仕組み
    • EVM1のPrecompiled Contractに相当
    • Sentinel Contract
      • ContractをdeployするときにVMが呼び出し、
        1. コードがeWASMの仕様にあっている確認
        2. gas計算ロジックを追加
        3. deploy準備OKの印として、preambleをつける
    • EVM Transcompiler
      • EVM1のbytecodeをeWASMにtranscompileする処理を実行
      • EVM1をサポートしていない場合VMを呼び出す

Ewasm 2.0

  • Ewasm 2.0のSmart contractはExecution Environments (EE)と呼ばれる
  • EEは全てWASMによって作成される
  • クロスシャードをサポートするため、各EEはシャードで実行される
  • EEはstate rootのみを取得できる
  • EEはstateless
  • scout: Ethereum 2.0 Phase 2 execution prototyping engine

  • 詳細は今後調査したい。特にstate shardingの環境下でどうやってcontractを実行するのか...など

Second stateのプロジェクトを使ったeWASMの構造

  • eWASMが使えるVMは現在Second stateによって開発が進められている。

ewasmint

調べたことを参考にEthermintをwasmで実行できるように改造してみました。

go-ethereum

  • 最新版のgo-ethereumはewasmのinterpreterに対応していないので、forkしてvm moduleを修正しました。

  • EVMのinstanceを作るときにchainconfigを確認して、wasmのflagが立っていれば、ewasmのinterpreterを使うように分岐しています。

  • &EVMC{...}でewasm用のinstanceを用意します。

evm.go
func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM {
    evm := &EVM{
        Context:      ctx,
        StateDB:      statedb,
        vmConfig:     vmConfig,
        chainConfig:  chainConfig,
        chainRules:   chainConfig.Rules(ctx.BlockNumber),
        interpreters: make([]Interpreter, 0, 1),
    }

    if chainConfig.IsEWASM(ctx.BlockNumber) {
        evm.interpreters = append(evm.interpreters, &EVMC{ewasmModule, evm, evmc.CapabilityEWASM, false})
    }

    if vmConfig.EVMInterpreter != "" {
        evm.interpreters = append(evm.interpreters, &EVMC{evmModule, evm, evmc.CapabilityEVM1, false})
    } else {
        // vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here
        // as we always want to have the built-in EVM as the failover option.
        evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, vmConfig))
    }

    evm.interpreter = evm.interpreters[0]

    return evm
}

Ethermint

  • emintdをstartするときにssvm-evmcをbuildして生成されたbinaryを指定します。
emintd start --vm-wasm <your/build/folder>/tools/ssvm-evmc/libssvm-evmc.dylib
  • このflagを利用してEVMのinstanceを作るときにwasmを選択するようにしています。
  • go-ethereumでもwasmを使うときのflagが用意されています。それを指定するとvm.InitEVMCEwasmが呼ばれて、pathが設定されますが、Ethermintでは外部からvmのinstanceを作るようにしなければならないので、NewEVMの直前でvm.InitEVMCEwasmを読んでいます。
state_transition.go
func (st StateTransition) newEVM(ctx sdk.Context, csdb *CommitStateDB, gasLimit uint64, gasPrice *big.Int) *vm.EVM {
    // Create context for evm
    context := vm.Context{
        CanTransfer: core.CanTransfer,
        Transfer:    core.Transfer,
        Origin:      st.Sender,
        Coinbase:    common.Address{}, // there's no benefitiary since we're not mining
        BlockNumber: big.NewInt(ctx.BlockHeight()),
        Time:        big.NewInt(ctx.BlockHeader().Time.Unix()),
        Difficulty:  big.NewInt(0), // unused. Only required in PoW context
        GasLimit:    gasLimit,
        GasPrice:    gasPrice,
    }

    if emint.VM != "" {
        vm.InitEVMCEwasm(emint.VM)
        return vm.NewEVM(context, csdb, GenerateWASMChainConfig(st.ChainID), vm.Config{})
    }
    return vm.NewEVM(context, csdb, GenerateEVM1ChainConfig(st.ChainID), vm.Config{})

}

あとは、READMEにしたがって進めるとEthermint上でwasm contractが実行できます。
SolidityからwasmへのcompilerはSOLLを使います。

コードは以下にあります。
Github: https://github.com/shiki-tak/ewasmint

まとめ

  • eWASMにも1.0と2.0がある。
  • 1.0はEVMをwasmで実現した感じ?
  • 2.0になるとShard chainに対応したVMになるみたい。
  • Blockchain + WebAssemblyはまじ楽しい!
  • 調べれば調べるほど、色々なプロジェクトが出てくるので今後も継続して調査したい