ブロックチェーンイーサリアム仮想マシンEVMの詳細な説明

1つの仮想マシン

仮想マシンが使用されます

  • イーサリアムトランザクション実行
  • イーサリアム状態を変更します。

トランザクションには2つのタイプがあります。

  • 通常の取引
  • スマートコントラクトトランザクション。

取引を行う際にはガス代を支払う必要があります。

スマートコントラクト間で呼び出すには4つの方法があります。

 

次に、Ethereum仮想マシン

イーサリアム仮想マシン(略してEVM)は、イーサリアムでトランザクションを実行するために使用されます。

ビジネスプロセスは次のとおりです。
ビジネスプロセス

トランザクションを入力すると、内部メッセージオブジェクトに変換され、実行のためにEVMに渡されます。

合計が通常の振替取引である場合StateDB 、対応する口座残高を変更するため直接アクセス が可能です。

それがある場合の作成や呼び出しスマート契約バイトコードがロードされ、実行され通訳EVMで、かつStateDBは、実行中に照会または変更することができます。

 

3.固有ガス

固定ガス料金の金額に関係なく、すべての取引について、計算方法は次のとおりです。
取引オイル料金の計算

通常の転送など、トランザクションに追加データ(ペイロード)が含まれていない場合は、21,000ガスを請求する必要があります。

トランザクションに追加のデータが含まれている場合は、データのこの部分も課金する必要があります。具体的には、バイト単位で課金されます。

  • バイトが0の4ブロック、
  • バイトが0でない場合、68ブロックを受信します

したがって、多くの契約の最適化が見られます。目的は、データ内のゼロ以外のバイト数を減らし、それによってガス 消費量を減らすこと です。

 

4、契約オブジェクトを生成します

トランザクションはメッセージオブジェクトに変換されてEVMに送信され、EVMはその後の実行のためにメッセージ基づいてコントラクトオブジェクトを生成します

トランザクション生成オブジェクト

見てわかるように、コントラクトは、アドレスStateDB ロードからのコントラクトの下で 、対応するコードをインタープリターにフィードバックして実行することができます。

さらに、コントラクト実行するために消費できる燃料コストには上限があります。これは、ノード構成の各ブロックが対応できるもの GasLimitです。

 

5、実行のために通訳に送られる

コードと入力が利用可能になると、実行のためにインタープリターに送信できます。EVMはスタックベースの仮想マシンです。インタプリタで4つのコンポーネントを操作する必要があります。

  • PC:CPUのPCレジスタに似ており、現在実行中の命令を指します
  • スタック:実行スタック、ビット幅は256ビット、最大深度は1024
  • メモリー:メモリースペース
  • ガス:ガスプール、郵便料金が使い果たされると、トランザクションの実行は失敗します

 

通訳の4つのコンポーネント

実行プロセスの詳細な説明を次の図に示します。

インタプリタ実行フロー

EVMの各命令はOpCode呼ばれ、 1バイトを占めるため、命令セットは最大で256を超えません 。具体的な説明については、https://ethervm.ioを参照してください

たとえば、次の図は例です(PUSH1 = 0x60、MSTORE = 0x52)。

OpCodeコマンド

  • まず、PCは契約コードからOpCodeを読み取ります。
  • 次に、それに関連付けられた関数のセットであるJumpTableから対応する操作を取得します
  • 次に、この操作のガスコストが計算されます。ガスがなくなると、実行は失敗し、ErrOutOfGasエラーが返されます。
  • 燃料費が十分な場合は、execute()を呼び出して命令を実行します。命令の種類に応じて、Stack、Memory、StateDBの読み取り操作と書き込み操作がそれぞれ実行されます。

 

6、コントラクト関数を呼び出します

EVMの説明と実行のメインフローを分析した後、一部の学生は次のように質問する場合があります。それでは、EVMは、トランザクションが呼び出す必要のあるコントラクトの関数をどのように知るのでしょうか。心配しないでください。前述のように、契約コードとともにインタプリタに送信される入力もあり、この入力データはトランザクションによって提供されます。

入力データ

入力データは通常、次の2つの部分に分けられます。

  • 最初の4バイトは「4バイト署名と呼ばれ関数の一意の識別子として、特定の関数署名のKeccakハッシュ値の最初の4バイトです(このWebサイトで現在のすべての関数シグネチャ確認できます

  • 以下は、関数を呼び出すために提供する必要のあるパラメーターあり長さは変数です

例:Aコントラクトをデプロイした後、add(1)の呼び出しに対応する入力データは次のようになります。

0x87db03b70000000000000000000000000000000000000000000000000000000000000001

スマートコントラクトをコンパイルすると、コンパイラーは生成されたバイトコードの前に関数選択ロジックの一部を自動的に追加します

  • まずCALLDATALOAD 、命令を介して「4バイトの署名」をスタックにプッシュし ます。
  • 次に、コントラクトに含まれている関数と順番に比較され、一致する場合は、JUMPI命令が呼び出されてコードにジャンプし、実行を続行します。

これは少し抽象的なかもしれません。上の図のコントラクトに対応する逆アセンブリコードを見て、一目で確認できます。

関数シグネチャ

分解コード

CALLDATALOADちなみに、ここ ではデータの読み込みに関連する手順について説明します。4つのタイプがあります。

  • CALLDATALOAD:入力データをスタックにロードします
  • CALLDATACOPY:入力データをメモリにロードします
  • コードコピー:現在の契約コードをメモリにコピーします
  • EXTCODECOPY:外部契約コードをメモリにコピーします

最後のEXTCODECOPYはあまり一般的に使用されていません。これは通常、サードパーティ契約のバイトコードが仕様を満たし、より多くのガスを消費するかどうかを監査するために使用されます。

これらの命令に対応する操作を次の図に示します。

命令に対応する操作

 

 

セブン、契約は契約を呼び出します

コントラクト内で別のコントラクトを呼び出すには、次の4つの方法があります。

  • コール
  • コールコード
  • DELEGATECALL
  • 静的

後で、それらの類似点と相違点を比較するための記事を作成します。最も単純なCALLを例として取り上げましょう。呼び出しプロセスは、次の図に示されています。

CALL呼び出しプロセス

呼び出し元が呼び出しパラメータをメモリに格納してから、CALL命令を実行していることがわかります。

CALL命令が実行されると、新しいコントラクトオブジェクトが作成され、メモリ内の呼び出しパラメータがその入力として使用されます。

通訳者はStackMemory、元の契約の実行環境を混乱させないように、新しい契約の実行のために新しい 合計 を作成します 

新しいコントラクトの実行が完了した後、実行結果はRETURN命令を介して以前に指定されたメモリアドレスに書き込まれ、その後、元のコントラクトは逆方向に実行され続けます。

 

8.契約を作成します

上記のすべての契約呼び出し、契約を作成するプロセスはどうですか?

トランザクションの宛先アドレスがnilの場合、そのトランザクションがスマートコントラクトの作成に使用されていることを示します。

まず、次の計算を使用して、契約アドレスを作成する必要がありますKeccak(RLP(call_addr, nonce))[:12]

つまり、トランザクションイニシエーターのアドレスとナンスでRLPエンコードを実行し、Keccakハッシュ値を計算し、最後の20バイトをコントラクトのアドレスとして使用します。

次のステップは、である対応を作成 契約アドレスに応じてstateObject、その後のトランザクションに含まれる契約コードを格納します

コントラクトのすべての状態変化は1つ storage trie Key-Value 保存され、最終的 にはの形式でStateDBに保存されます。

コードが保存されると、コードを変更することはできませんstorage trie 。また、SSTOREコマンドなどを使用してコントラクトを呼び出すことにより、の内容を変更 できます。

契約アドレスを生成する

 

9、石油料金の計算

最後に、燃料費の計算については長い間、公式は基本的にイーサネットスクエアイエローブックの定義に基づいています
イーサリアムイエローペーパーガス

もちろん、クソコードを直接読むこともできます。コードはcore / vm /gas.goとcore / vm /gas_table.goにあります。

 

X.契約の4つの呼び出し方法

中規模から大規模のプロジェクトでは、すべての機能を1つのスマートコントラクトで実装することは不可能であり、これは分業と協力につながりません。

通常の状況では、コードを関数ごとに異なるライブラリまたはコントラクトに分割し、相互に呼び出すためのインターフェイスを提供します。

では Solidity 、コードの再利用のみを目的とする場合は、コードを共通にコード化し、 Cライブラリ、使用されているJavaライブラリを呼び出すことができるように背面のライブラリにデプロイします。

ただし、ライブラリにストレージタイプ変数を定義することは許可されていません。つまり、ライブラリはコントラクトの状態を変更できません。

コントラクトの状態を変更する必要がある場合は、コントラクトを呼び出すコントラクトを含む新しいコントラクトをデプロイする必要があります。

コントラクトを呼び出すには、次の4つの方法があります。

  • コール
  • コールコード
  • DELEGATECALL
  • STATICCALL

1.CALLとCALLCODE

CALLとCALLCODEの違いは、コード実行のコンテキストが異なることです。

具体的には、変更されたCALLは呼び出し先ストレージであり、変更されたCALLCODEはの呼び出し元ストレージです。

ストレージ

私たちの理解を確認するために契約書を書いてみましょう:

pragma solidity ^0.4.25;

contract A {
  int public x;

  function inc_call(address _contractAddress) public {
      _contractAddress.call(bytes4(keccak256("inc()")));
  }
  function inc_callcode(address _contractAddress) public {
      _contractAddress.callcode(bytes4(keccak256("inc()")));
  }
}

contract B {
  int public x;

  function inc() public {
      x++;
  }
}

最初inc_call()それを呼び出してから、コントラクトAとBのxの値の変化を確認しましょう 

xの値

コントラクトBのxが変更されており、コントラクトAのxはまだ0に等しいことがわかります。

もう一度呼び出して inc_callcode() 試してみましょう。

xも0に等しい

この変更はコントラクトAのxであり、コントラクトBのxは変更されないままであることがわかります。

 

2.CALLCODEとDELEGATECALL

実際、DELEGATECALLはCALLCODEのバグ修正バージョンであると見なすことができ、CALLCODEは推奨されなくなりました。

CALLCODEとDELEGATECALLの違いは次のとおりですmsg.sender 。

具体的には、DELEGATECALLは常に元の呼び出し元のアドレスを使用しますが、CALLCODEは使用しません。

CALLCODEとDELEGATECALLの違い

理解を確認するために、まだコードを記述しています。

pragma solidity ^0.4.25;

contract A {
  int public x;

  function inc_callcode(address _contractAddress) public {
      _contractAddress.callcode(bytes4(keccak256("inc()")));
  }
  function inc_delegatecall(address _contractAddress) public {
      _contractAddress.delegatecall(bytes4(keccak256("inc()")));
  }
}

contract B {
  int public x;

  event senderAddr(address);
  function inc() public {
      x++;
      emit senderAddr(msg.sender);
  }
}

最初にinc_callcode()を呼び出し、ログ出力を観察します。

ログ出力

msg.senderは、トランザクション開始者のアドレスではなく、コントラクトAのアドレスを指していることがわかります。

もう一度inc_delegatecall()を呼び出して、ログ出力を観察してみましょう。

ログ出力

msg.senderがトランザクションの開始者を指していることがわかります。

 

3.STATICCALL

Solidityには現在直接呼び出すことができる低レベルのAPIがないため、ここにSTATICCALLを配置することは汚名のようです。将来的には、ビューと純粋な型を呼び出す関数をコンパイラレベルでSTATICCALL命令にコンパイルする予定です。

ビュータイプの関数は、状態変数を変更できないことを示していますが、純粋タイプの関数はより厳密であり、状態変数を読み取ることさえ許可されていません

これは現在、コンパイルフェーズでチェックされており、規制が満たされていない場合、コンパイルエラーが発生します。将来、STATICCALL命令に変更した場合、実行時にこれを完全に確認でき、トランザクションの実行に失敗する可能性があります。

さらに面倒なことはせずに、STATICCALLの実装コードを見てみましょう。

実装コード

ご覧のとおり、インタープリターはreadOnly属性を追加します。STATICCALLはこの属性をtrueに設定します。状態変数への書き込み操作が発生すると、errWriteProtectionエラーが返されます。

 

 

 

 

 

 

コンテンツはhttps://learnblockchain.cn/2019/04/09/easy-evmから取得さます

おすすめ

転載: blog.csdn.net/u013288190/article/details/112978914