Skip to content

以太坊虛擬機

本文檔將以太坊虛擬機 (EVM) 的規範定義為 Cosmos SDK 模塊。

自2015年引入以太坊以來,通過[智能合約]控制數字資產的能力吸引了大量開發人員在以太坊虛擬機 (EVM) 上構建去中心化應用程序。 該社區不斷創建廣泛的工具並引入標準,這進一步提高了 EVM 兼容技術的採用率。

然而,基於 EVM 的鏈(例如以太坊)的增長揭示了一些可擴展性挑戰,這些挑戰通常被稱為去中心化、安全性和可擴展性的三難困境。 開發人員對高昂的 gas 費用、緩慢的交易速度和吞吐量以及鏈特定的治理感到沮喪,這些治理由於其廣泛部署的應用程序而只能經歷緩慢的變化。 需要一種解決方案來消除在熟悉的 EVM 環境中構建應用程序的開發人員的這些顧慮。

x/evm 模塊在可擴展、高吞吐量的權益證明區塊鏈上提供這種 EVM 熟悉度。 它構建為 Cosmos SDK 模塊,允許部署智能合約、與 EVM 狀態機交互(狀態轉換) ,以及 EVM 工具的使用。 它可用於 Cosmos 特定應用程序區塊鏈,通過 Tendermint Core 的高交易吞吐量、快速的交易終結性和 IBC 的水平可擴展性來緩解上述問題。

內容

  1. 概念
  2. 狀態
  3. 狀態轉換
  4. 交易
  5. ABCI
  6. 鉤子
  7. 事件
  8. 參數
  9. 客戶端

模塊架構

注意:: 如果您不熟悉 SDK 模塊的整體模塊結構,請先閱讀此 文檔 作為先決條件。

evm/
├── client
│   └── cli
│       ├── query.go      # 模塊的 CLI 查詢命令
│       └── tx.go         # 模塊的 CLI 交易命令
├── keeper
│   ├── abci.go           # ABCI 開始出塊 和 結束出塊 邏輯
│   ├── keeper.go         # 處理模塊的業務邏輯。
│   ├── params.go         # 參數獲取器和設置器
│   ├── querier.go        # 狀態查詢函數
│   └── statedb.go        # 來自 types/statedb 並傳入 sdk.Context 的函數
├── types
│   ├── chain_config.go
│   ├── codec.go          # 編碼類型註冊
│   ├── errors.go         # 特定於模塊的錯誤
│   ├── events.go         # 暴露給 Tendermint PubSub/Websocket 的事件
│   ├── genesis.go        # 模塊的創世狀態
│   ├── journal.go        # 以太坊狀態轉換交易
│   ├── keys.go           # 存儲密鑰和實用函數
│   ├── logs.go           # 鏈升級後持久化 Ethereum tx 登錄狀態的類型
│   ├── msg.go            #EVM 模塊交易消息
│   ├── params.go         # 可以使用治理參數更改建議自定義的模塊參數
│   ├── state_object.go   # EVM 狀態對象
│   ├── statedb.go        # StateDb接口的實現
│   ├── storage.go        # 使用數組實現以太坊狀態存儲映射以防止非確定性
│   └── tx_data.go        # 以太坊交易數據類型
├── genesis.go            # ABCI InitGenesis 和 ExportGenesis 功能
├── handler.go            # 消息路由
└── module.go             # 模塊管理器的模塊設置

概念

EVM

以太坊虛擬機 (EVM) 是一種計算引擎,可以將其視為由數千台運行以太坊客戶端的連接計算機(節點)維護的單一實體。 作為虛擬機 (VM),EVM 負責確定性地計算狀態變化,而不管其環境(硬件和操作系統)如何。 這意味著在給定相同的起始狀態和交易 (tx) 的情況下,每個節點都必須獲得完全相同的結果。

EVM 被認為是處理 智能合約 部署和執行的以太坊協議的一部分。

要明確區分:

  • 以太坊協議描述了一個區塊鏈,所有以太坊賬戶和智能合約都存在於其中。 它在鏈中的任何給定塊中只有一個規範狀態(一種數據結構,用於保存所有帳戶)。
  • 然而,EVM 是 狀態機,它定義了從一個塊到另一個塊計算新有效狀態的規則。 它是一個隔離的運行時,這意味著在 EVM 內運行的代碼無法訪問網絡、文件系統或其他進程(不是外部 API)。

x/evm 模塊將 EVM 實現為 Cosmos SDK 模塊。 它允許用戶通過提交以太坊交易並在給定狀態上執行包含的消息來與 EVM 交互,以引發狀態轉換。

狀態

以太坊狀態是一種數據結構,實現為 Merkle Patricia Tree,將所有賬戶保存在鏈上。 EVM 對該數據結構進行更改,從而產生具有不同狀態根的新狀態。 因此,以太坊可以被視為一條狀態鏈,它通過使用 EVM 在區塊中執行交易來從一種狀態轉換到另一種狀態。 一個新的 txs 塊可以通過它的塊頭(父哈希、塊號、時間戳、隨機數、收據……)來描述。

賬號

有兩種類型的帳戶可以存儲在給定地址的狀態中:

  • 外部擁有賬戶 (EOA):具有隨機數(tx 計數器)和余額
  • 智能合約:具有隨機數、餘額、(不可變)代碼哈希、存儲根(另一個 Merkle Patricia Trie)

智能合約就像區塊鏈上的常規賬戶一樣,它還以以太坊特定的二進制格式存儲可執行代碼,稱為 EVM 字節碼。 它們通常是用以太坊高級語言編寫的,例如 Solidity,它被編譯成 EVM 字節碼並通過使用以太坊客戶端提交交易部署在區塊鏈上。

結構

EVM 作為基於堆棧的機器運行。 它的主要架構組件包括:

  • Virtual ROM:合約代碼在處理txs時被拉入這個只讀內存
  • 機器狀態(易失性):隨著 EVM 運行而變化,並在處理每個 tx 後被清除
    • 程序計數器(PC)
    • Gas​​:跟踪使用了多少 gas *堆棧和內存:計算狀態變化
  • 訪問帳戶存儲(持久)

智能合約的狀態轉換

通常,智能合約會公開一個公共 ABI,這是一個用戶可以與合約交互的受支持方式的列表。 要與合約交互並調用狀態轉換,用戶將提交一個攜帶任意數量氣體的交易和根據 ABI 格式化的數據有效負載,指定交互類型和任何其他參數。 當收到 tx 時,EVM 使用 tx 負載執行智能合約的 EVM 字節碼。

執行 EVM 字節碼

合約的 EVM 字節碼由基本操作(加法、乘法、存儲等)組成,稱為操作碼。 每個 Opcode 執行都需要用 tx 支付的 gas。 因此,EVM 被認為是準圖靈完備的,因為它允許任意計算,但合約執行期間的計算量僅限於 tx 中提供的氣體量。

每個操作碼的 gas 成本 反映了在實際計算機硬件上運行這些操作的成本(例如,ADD = 3gasSSTORE = 100gas)。 要計算交易的 gas 消耗量,請將 gas 成本乘以 gas price,這可能會根據當時網絡的需求而變化。 如果網絡負載很重,您可能需要支付更高的 gas 價格才能執行您的交易。 如果達到氣體限制(氣體異常),則不會對以太坊狀態進行任何更改,除非發送者的隨機數增加並且他們的餘額下降以支付浪費 EVM 時間的費用。

智能合約也可以調用其他智能合約。 每次調用新合約都會創建一個新的 EVM 實例(包括新的堆棧和內存)。 每次調用都會將沙箱狀態傳遞給下一個 EVM。 如果氣體用完,所有狀態變化都將被丟棄。 否則,它們將被保留。

如需進一步閱讀,請參閱:

Daodst 作為 Geth 實現

Daodst 包含 Golang 中的以太坊協議 (Geth) 作為 Cosmos SDK 模塊的實現。 Geth 包含一個 EVM 的實現來計算狀態轉換。 查看 go-ethereum 源代碼 了解 EVM 操作碼是如何實現的。 正如 Geth 可以作為以太坊節點運行一樣,Daodst 也可以作為節點運行以計算 EVM 的狀態轉換。

JSON-RPC

JSON-RPC 是一種無狀態、輕量級的遠程過程調用 (RPC) 協議。 該規範主要定義了幾種數據結構及其處理規則。 它與傳輸無關,因為這些概念可以在同一進程中、通過套接字、通過 HTTP 或在許多不同的消息傳遞環境中使用。 它使用 JSON (RFC 4627) 作為數據格式。

** JSON-RPC 範例: eth_call **

JSON-RPC 方法允許您根據合約執行消息。 通常,您需要將交易發送到 Geth 節點以將其包含在內存池中,然後節點之間相互傳播,最終交易被包含在一個塊中並被執行。 然而,eth_call 允許您將數據發送到合約,並在不提交交易的情況下查看會發生什麼。

在Geth實現中,調用端點大致經過以下步驟:

  1. eth_call 請求轉換為使用 eth 命名空間調用 func (s *PublicBlockchainAPI) Call() 函數
  2. Call() 被賦予交易參數、要調用的塊和可選參數,用於修改要調用的狀態。 然後它調用 DoCall()
  3. DoCall() 將參數轉換為ethtypes.message,實例化 EVM 並使用core.ApplyMessage應用消息
  4. ApplyMessage() 調用狀態轉換TransitionDb()
  5. TransitionDb() Create() 是一個新合約或者 Call() 是一個合約
  6. evm.Call() 運行解釋器 evm.interpreter.Run() 來執行消息。 如果執行失敗,狀態將恢復為執行前拍攝的快照並消耗 gas。
  7. Run() 執行循環以執行操作碼。

狀態DB

來自 go-ethereumStateDB 接口表示用於完整狀態查詢的 EVM 數據庫。 EVM 狀態轉換由該接口啟用,在 x/evm 模塊中由 Keeper 實現。 該接口的實現使 Daodst EVM 兼容。

共識引擎

使用 x/evm 模塊的應用程序通過應用程序區塊鏈接口 (ABCI) 與 Tendermint 核心共識引擎交互。 該應用程序和 Tendermint Core 共同構成了運行完整區塊鏈並將業務邏輯與去中心化數據存儲相結合的程序。

提交給 x/evm 模塊的以太坊事務在執行和更改應用程序狀態之前參與此共識過程。 我們鼓勵了解 Tendermint 共識引擎 的基礎知識,以便詳細了解狀態轉換.

交易記錄

在每個x/evm交易中,結果包含來自狀態機執行的以太坊日誌,JSON-RPC Web3 服務器使用這些日誌進行過濾器查詢和處理 EVM 掛鉤。

tx 日誌在 tx 執行期間存儲在臨時存儲中,然後在事務處理後通過 cosmos events 發出。 可以通過 gRPC 和 JSON-RPC 查詢它們。

Block Bloom

布隆是可用於過濾器查詢的每個塊的布隆過濾器值(以字節為單位)。 塊 bloom 值存儲在瞬態存儲中,然後在 EndBlock 處理期間通過 cosmos 事件發出。 可以通過 gRPC 和 JSON-RPC 查詢它們。

:::提示

👉 注意: 由於它們不存儲在狀態中,事務日誌和塊綻放在升級後不會持久化。 用戶必須在升級後使用存檔節點才能獲得遺留鏈事件。 :::

狀態

本節概述存儲在 x/evm 模塊狀態中的對象,源自 go-ethereum StateDB 接口的功能,及其通過 Keeper 的實現以及創世時的狀態實現。

狀態對象

x/evm 模塊保持以下狀態的對象:

狀態

說明 Key Value Store
Code 智能合約字節碼 []byte{1} + []byte(address) []byte{code} KV
Storage 智能合約存儲 []byte{2} + [32]byte{key} [32]byte(value) KV
Block Bloom Block bloom filter,用於累加當前塊的bloom filter,發送到end blocker的事件. []byte{1} + []byte(tx.Hash) protobuf([]Log) Transient
Tx Index 當前區塊中當前交易的索引。 []byte{2} BigEndian(uint64) Transient
Log Size 到目前為止在當前塊中發出的日誌數。用於決定後續日誌的日誌索引. []byte{3} BigEndian(uint64) Transient
Gas Used 當前cosmos-sdk tx的ethereum消息使用的gas量,當cosmos-sdk tx包含多條ethereum消息時需要。 []byte{4} BigEndian(uint64) Transient

狀態DB

StateDB 接口由 x/evm/statedb 模塊中的 StateDB 實現,代表一個 EVM 數據庫,用於合約和賬戶的完整狀態查詢。 在以太坊協議中,StateDB 用於存儲 IAVL 樹中的任何內容,並負責緩存和存儲嵌套狀態。

// github.com/ethereum/go-ethereum/core/vm/interface.go
type StateDB interface {
 CreateAccount(common.Address)

 SubBalance(common.Address, *big.Int)
 AddBalance(common.Address, *big.Int)
 GetBalance(common.Address) *big.Int

 GetNonce(common.Address) uint64
 SetNonce(common.Address, uint64)

 GetCodeHash(common.Address) common.Hash
 GetCode(common.Address) []byte
 SetCode(common.Address, []byte)
 GetCodeSize(common.Address) int

 AddRefund(uint64)
 SubRefund(uint64)
 GetRefund() uint64

 GetCommittedState(common.Address, common.Hash) common.Hash
 GetState(common.Address, common.Hash) common.Hash
 SetState(common.Address, common.Hash, common.Hash)

 Suicide(common.Address) bool
 HasSuicided(common.Address) bool

 // Exist reports whether the given account exists in state.
 // Notably this should also return true for suicided accounts.
 Exist(common.Address) bool
 // Empty returns whether the given account is empty. Empty
 // is defined according to EIP161 (balance = nonce = code = 0).
 Empty(common.Address) bool

 PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
 AddressInAccessList(addr common.Address) bool
 SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
 // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
 // even if the feature/fork is not active yet
 AddAddressToAccessList(addr common.Address)
 // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
 // even if the feature/fork is not active yet
 AddSlotToAccessList(addr common.Address, slot common.Hash)

 RevertToSnapshot(int)
 Snapshot() int

 AddLog(*types.Log)
 AddPreimage(common.Hash, []byte)

 ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error
}

The StateDB in the x/evm provides the following functionalities:

以太坊賬戶的 CRUD

您可以從提供的地址創建 EthAccount 實例,並使用 createAccount() 將值設置為存儲在 AccountKeeper 上。 如果具有給定地址的帳戶已經存在,此函數還會重置與該地址關聯的任何預先存在的代碼和存儲。

一個賬戶的貨幣餘額可以通過 BankKeeper 進行管理,可以使用 GetBalance() 讀取並使用 AddBalance()SubBalance() 進行更新。

  • GetBalance() 返回所提供地址的 EVM 貨幣餘額。 貨幣名稱是從模塊參數中獲取的。
  • AddBalance() 通過鑄造新貨幣並將其轉移到地址來將給定金額添加到地址貨幣餘額中。 貨幣名稱是從模塊參數中獲取的。
  • SubBalance() 通過將貨幣轉移到託管賬戶然後銷毀它們來從地址餘額中減去給定的金額。 貨幣名稱是從模塊參數中獲取的。 如果金額為負或用戶沒有足夠的資金進行轉賬,此函數將執行空操作。

隨機數(或交易序列)可以通過身份驗證模塊AccountKeeper從賬戶 Sequence 中獲取。

  • GetNonce() 檢索具有給定地址的帳戶並返回 tx 序列(即隨機數)。 如果找不到該帳戶,該函數將執行空操作。
  • SetNonce() 將給定的隨機數設置為地址帳戶的序列。 如果該帳戶不存在,將從該地址創建一個新帳戶。

包含任意合約邏輯的智能合約字節碼存儲在 EVMKeeper 上,可以使用 GetCodeHash() , GetCode()GetCodeSize() 查詢,並使用 SetCode() 更新。

  • GetCodeHash() 從存儲中獲取帳戶並返回其代碼哈希。 如果賬戶不存在或不是 EthAccount 類型,則返回空代碼哈希值。
  • GetCode() 返回與給定地址關聯的代碼字節數組。 如果來自帳戶的代碼哈希為空,則此函數返回 nil。
  • SetCode() 將代碼字節數組存儲到應用程序 KVStore,並將代碼哈希設置到給定的帳戶。 如果代碼為空,則將從存儲中刪除該代碼。
  • GetCodeSize() 返回與此對象關聯的合約代碼的大小,如果沒有則返回零。

需要跟踪退還的氣體並將其存儲在單獨的變量中,以便在 EVM 執行完成後將其從氣體使用值中減去/添加到氣體使用值中。退款值在每筆交易和每個區塊結束時被清除。

  • AddRefund() 將給定的氣體量添加到內存中的退款值。
  • SubRefund() 從內存中的退款值中減去給定的氣體量。 如果 gas 量大於當前退款,此函數將 panic。
  • GetRefund() 返回 tx 執行完成後可用於返回的氣體量。 該值在每次交易時重置為 0。

狀態存儲在EVMKeeper上。 可以使用 GetCommittedState()GetState() 查詢並使用 SetState() 更新。

  • GetCommittedState() 返回存儲中為給定鍵哈希設置的值。 如果密鑰未註冊,則此函數返回空散列。
  • GetState() 返回給定鍵哈希的內存中臟狀態, 如果不存在,則從 KVStore 加載提交的值。
  • SetState() 將給定的哈希值(鍵、值)設置為狀態。 如果值散列為空,則此函數從狀態中刪除鍵, 新值先保持臟狀態,最後提交給KVStore。

帳戶也可以設置為自殺狀態。 當合約自殺時,賬戶被標記為自殺,當提交代碼時,存儲和賬戶被刪除(從下一個塊開始)。

  • Suicide() 將給定帳戶標記為已自殺並清除 EVM 代幣的帳戶餘額。
  • HasSuicided() 查詢內存中的標誌以檢查帳戶是否已在當前交易中被標記為自殺。 被自殺的賬戶將在查詢期間返回為非零,並在提交塊後“清除”。

要檢查帳戶是否存在,請使用 Exist()Empty()

  • 如果給定帳戶存在於存儲中或已被標記為已自殺,則 Exist() 返回 true。
  • 如果地址滿足以下條件,Empty() 返回 true:
    • nonce為 0
    • evm 貨幣 的餘額為 0
    • 帳戶code hash為空

EIP2930 functionality

支持包含訪問列表、交易計劃訪問的地址和存儲密鑰列表的交易類型。 訪問列表狀態保存在內存中,並在事務提交後丟棄。

  • PrepareAccessList() 處理執行關於 EIP-2929 和 EIP-2930 的狀態轉換的準備步驟。 僅當 Yolov3/Berlin/2929+2930 適用於當前編號時才應調用此方法。
    • 將發件人添加到訪問列表 (EIP-2929)
    • 將目的地添加到訪問列表 (EIP-2929)
    • 將預編譯添加到訪問列表 (EIP-2929)
    • 添加可選的 tx 訪問列表的內容 (EIP-2930)
  • 如果地址已註冊,AddressInAccessList() 返回 true。
  • SlotInAccessList() 檢查地址和插槽是否已註冊。
  • AddAddressToAccessList() 將給定地址添加到訪問列表。 如果該地址已經在訪問列表中,則此函數執行空操作。
  • AddSlotToAccessList() 將給定的(地址、槽)添加到訪問列表中。 如果地址和插槽已經在訪問列表中,則此函數執行空操作。

快照狀態和恢復功能

EVM 使用狀態恢復異常來處理錯誤。 這樣的異常將撤消對當前調用(及其所有子調用)中的狀態所做的所有更改,並且調用者可以處理錯誤並且不會傳播。 您可以使用 Snapshot() 通過修訂識別當前狀態,並使用 RevertToSnapshot() 將狀態恢復到給定修訂以支持此功能。 - Snapshot() 創建一個新快照並返回標識符。 - RevertToSnapshot(rev) 撤消所有修改,直至標識為 rev 的快照。

Daodst 改編了 go-ethereum journal implementation 來支持這一點,它使用日誌列表為了記錄到目前為止完成的所有狀態修改操作,快照由日誌列表中的唯一 ID 和索引組成,要恢復為快照,它只是以相反的順序在快照索引之後撤消日誌日誌。

以太坊交易日誌

使用 AddLog(),您可以將給定的以太坊 Log 附加到與保存在當前狀態的交易哈希關聯的日誌列表中。 該函數還在設置要存儲的日誌之前填寫 tx hash、block hash、tx index 和 log index 字段。

Keeper

EVM 模塊Keeper授予對 EVM 模塊狀態的訪問權限,並實現 statedb.Keeper 接口以支持 StateDB 實現。 Keeper 包含一個存儲密鑰,允許 DB 寫入只能由 EVM 模塊訪問的多存儲的具體子樹。 Daodst 沒有使用 trie 和數據庫進行查詢和持久化( StateDB 實現),而是使用 Cosmos 的 KVStore(鍵值存儲)和 Cosmos SDK 的Keeper來促進狀態轉換。

為了支持接口功能,它導入了 4 個模塊 Keepers:

  • auth:CRUD 帳戶
  • bank:會計(供應)和余額的 CRUD
  • staking:查詢歷史標頭
  • fee market:處理DynamicFeeTx的EIP1559基本費用 在ChainConfig參數上激活London硬分叉之後
type Keeper struct {
 // Protobuf codec
 cdc codec.BinaryCodec
 // Store key required for the EVM Prefix KVStore. It is required by:
 // - storing account's Storage State
 // - storing account's Code
 // - storing Bloom filters by block height. Needed for the Web3 API.
 // For the full list, check the module specification
 storeKey sdk.StoreKey

 // key to access the transient store, which is reset on every block during Commit
 transientKey sdk.StoreKey

 // module specific parameter space that can be configured through governance
 paramSpace paramtypes.Subspace
 // access to account state
 accountKeeper types.AccountKeeper
 // update balance and accounting operations with coins
 bankKeeper types.BankKeeper
 // access historical headers for EVM state transition execution
 stakingKeeper types.StakingKeeper
 // fetch EIP1559 base fee and parameters
 feeMarketKeeper types.FeeMarketKeeper

 // chain ID number obtained from the context's chain id
 eip155ChainID *big.Int

 // Tracer used to collect execution traces from the EVM transaction execution
 tracer string
 // trace EVM state transition execution. This value is obtained from the `--trace` flag.
 // For more info check https://geth.ethereum.org/docs/dapp/tracing
 debug bool

 // EVM Hooks for tx post-processing
 hooks types.EvmHooks
}

創世狀態

x/evm 模塊 GenesisState 定義了從先前導出的高度初始化鏈所需的狀態。 它包含 GenesisAccounts 和模塊參數

type GenesisState struct {
  // accounts is an array containing the ethereum genesis accounts.
  Accounts []GenesisAccount `protobuf:"bytes,1,rep,name=accounts,proto3" json:"accounts"`
  // params defines all the parameters of the module.
  Params Params `protobuf:"bytes,2,opt,name=params,proto3" json:"params"`
}

創世賬號

GenesisAccount 類型對應於以太坊GenesisAccount 類型的改編。 它定義了一個在創世狀態下被初始化的賬戶。

它的主要區別在於 Daodst 上的那個使用自定義的Storage類型,該類型使用切片而不是 evmState的映射(由於非確定性),並且它不包含私鑰字段。

同樣重要的是要注意,由於 Cosmos SDK 上的auth模塊管理帳戶狀態,Address字段必須對應於存儲在auth模塊Keeper中的現有EthAccount(即AccountKeeper)。

地址使用 EIP55 十六進制 [格式]genesis.json 上。

type GenesisAccount struct {
  // address defines an ethereum hex formated address of an account
  Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
  // code defines the hex bytes of the account code.
  Code string `protobuf:"bytes,2,opt,name=code,proto3" json:"code,omitempty"`
  // storage defines the set of state key values for the account.
  Storage Storage `protobuf:"bytes,3,rep,name=storage,proto3,castrepeated=Storage" json:"storage"`
}

狀態轉換

x/evm 模塊允許用戶提交以太坊交易 (Tx) 並執行它們包含的消息以在給定狀態上引發狀態轉換。

用戶在客戶端提交交易以將其廣播到網絡。 當交易在共識過程中被包含在一個塊中時,它將在服務器端執行。 我們強烈建議您了解 Tendermint 共識引擎 的基礎知識,以詳細了解狀態轉換.

客戶端

:::提示 👉 這是基於 eth_sendTransaction JSON-RPC :::

  1. 用戶使用以太坊兼容的客戶端或錢包(例如 Metamask、WalletConnect、Ledger 等)通過可用的 JSON-RPC 端點之一提交交易:

    • eth(公共)命名空間: eth_sendTransactioneth_sendRawTransaction
    • 個人(私有)命名空間: personal_sendTransaction
  2. 在使用 SetTxDefaults 填充 RPC 事務後創建一個 MsgEthereumTx 實例,以使用默認值填充缺失的 tx 參數

  3. 使用 ValidateBasic() 驗證 Tx 字段(無狀態)
  4. Tx 使用與發件人地址關聯的密鑰和來自 ChainConfig 的最新以太坊硬分叉(LondonBerlin 等)簽名
  5. Tx 是使用 Cosmos Config 構建器從 msg 字段構建
  6. Tx 在同步模式中廣播 確保等待 CheckTx 執行響應。 在添加到共識引擎的內存池之前,應用程序使用CheckTx()驗證交易。
  7. JSON-RPC 用戶收到帶有交易字段的 RLP 哈希的響應。 此哈希不同於 SDK 交易使用的默認哈希,後者計算交易字節的sha256哈希。

服務端

一旦在共識期間提交了一個塊(包含“Tx”),它就會在服務器端的一系列 ABCI 消息中應用於應用程序。

每個 Tx 都由應用程序通過調用 RunTx 來處理。 在對 Tx 中的每個 sdk.Msg 進行無狀態驗證後,AnteHandler 確認 Tx 是以太坊交易還是 SDK 交易。 作為以太坊交易,它包含消息,然後由 x/evm 模塊處理以更新應用程序的狀態。

前置處理程序

anteHandler 為每筆交易運行。 它檢查 Tx 是否是以太坊交易並將其路由到內部 ante 處理程序。 在這裡,Tx 使用 EthereumTx 擴展選項進行處理,以不同於正常的 Cosmos SDK 交易來處理它們。 antehandler 運行一系列選項及其針對每個 TxAnteHandle 函數:

  • EthSetUpContextDecorator() 改編自 cosmos-sdk 的 SetUpContextDecorator,它通過將燃氣表設置為無限來忽略燃氣消耗
  • EthValidateBasicDecorator(evmKeeper) 驗證以太坊類型 Cosmos Tx 消息的字段
  • EthSigVerificationDecorator(evmKeeper) 驗證註冊的鏈 ID 是否與消息上的相同,並且簽名者地址是否與消息上定義的相匹配。 RecheckTx 不會跳過它,因為它設置了From地址,這對於其他 ante 處理程序的工作至關重要。 RecheckTx 失敗將阻止 tx 被包含到塊中,特別是當 CheckTx 成功時,在這種情況下用戶將不會看到錯誤消息。 -EthAccountVerificationDecorator(ak, bankKeeper, evmKeeper) 將驗證發送方餘額是否大於總交易成本。 如果該帳戶不存在,即無法在存儲中找到,則該帳戶將被設置到存儲中。 如果出現以下情況,此 AnteHandler 裝飾器將失敗:
    • 任何消息都不是 MsgEthereumTx
    • 發件人地址為空
    • 賬戶餘額低於交易成本
  • EthNonceVerificationDecorator(ak) 驗證交易隨機數是否有效並等同於發送方賬戶的當前隨機數。
  • EthGasConsumeDecorator(evmKeeper) 驗證以太坊 tx 消息是否足以支付固有氣體(僅在 CheckTx 期間)並且發送方有足夠的餘額來支付氣體成本。 交易的固有氣體是交易在執行之前使用的氣體量。 氣體是一個常數值加上交易提供的額外數據字節所產生的任何成本。 如果出現以下情況,此 AnteHandler 裝飾器將失敗:
    • 交易包含多個消息
    • 消息不是 MsgEthereumTx
    • 找不到發件人帳戶
    • 交易的 gas limit 低於固有 gas
    • 用戶沒有足夠的餘額來扣除交易費用(gas_limit * gas_price)
    • 交易或區塊煤氣表用完煤氣
  • CanTransferDecorator(evmKeeper, feeMarketKeeper) 從消息中創建一個 EVM 並調用 BlockContext CanTransfer 函數來查看該地址是否可以執行交易。
  • EthIncrementSenderSequenceDecorator(ak) 處理遞增簽名者(即發送者)的序列。 如果交易是合約創建,則隨機數將在交易執行期間遞增,而不是在此 AnteHandler 裝飾器內。

選項 authante.NewMempoolFeeDecorator()authante.NewTxTimeoutHeightDecorator()authante.NewValidateMemoDecorator(ak) 與 Cosmos Tx 相同。 單擊此處了解有關 anteHandler 的更多信息。

EVM 模塊

通過 antehandler 驗證後,Tx 中的每個 sdk.Msg(在本例中為 MsgEthereumTx) 傳遞給 x/evm 模塊中的 Msg Handler,並通過以下步驟運行:

  1. Msg 轉換為以太坊的 Tx 類型
  2. TxEVMConfig 一起應用並嘗試執行狀態轉換,只有在交易未失敗的情況下才會持久化(提交)到底層 KVStore: 1.確認創建了EVMConfig
    1. 使用來自“EVMConfig”的鏈配置值創建以太坊簽名者
    2. 將以太坊交易哈希設置為(臨時)臨時存儲,以便它也可用於 StateDB 函數 4.生成新的EVM實例
    3. 確認用於合約創建 (EnableCreate) 和合約執行 (EnableCall) 的 EVM 參數已啟用 6.應用消息。如果To地址為nil,則使用代碼作為部署代碼創建新合約。 Else 以給定輸入作為參數在給定地址調用合約 7.計算evm操作使用的gas
  3. 如果Tx申請成功
    1. 執行 EVM Tx 後處理掛鉤。如果鉤子返回錯誤,則還原整個“Tx” 2.按照以太坊gas記賬規則返還gas
    2. 使用從 tx 生成的日誌更新塊布隆過濾器值
    3. 為交易字段和 tx 日誌發出 SDK 事件

交易

本部分定義了導致上一節中定義的狀態轉換的 sdk.Msg 具體類型。

MsgEthereumTx

EVM 狀態轉換可以通過使用“MsgEthereumTx”來實現。 此消息將以太坊交易數據 (TxData) 封裝為 sdk.Msg。 它包含必要的交易數據字段。 請注意,MsgEthereumTx 實現了 sdk.Msg sdk.Tx 接口。 通常,SDK 消息只實現前者,而後者是一組消息捆綁在一起。

本部分定義了導致上一節中定義的狀態轉換的 sdk.Msg 具體類型。

MsgEthereumTx

EVM 狀態轉換可以通過使用 MsgEthereumTx來實現。 此消息將以太坊交易數據 (TxData) 封裝為 sdk.Msg。 它包含必要的交易數據字段。 請注意,MsgEthereumTx 實現了 sdk.Msg sdk.Tx 接口。

通常,SDK 消息只實現前者,而後者是一組消息捆綁在一起。

type MsgEthereumTx struct {
 // inner transaction data
 Data *types.Any `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"`
 // DEPRECATED: encoded storage size of the transaction
 Size_ float64 `protobuf:"fixed64,2,opt,name=size,proto3" json:"-"`
 // transaction hash in hex format
 Hash string `protobuf:"bytes,3,opt,name=hash,proto3" json:"hash,omitempty" rlp:"-"`
 // ethereum signer address in hex format. This address value is checked
 // against the address derived from the signature (V, R, S) using the
 // secp256k1 elliptic curve
 From string `protobuf:"bytes,4,opt,name=from,proto3" json:"from,omitempty"`
}

如果出現以下情況,則此消息字段驗證預計會失敗:

  • From 字段已定義且地址無效
  • TxData 無狀態驗證失敗

如果出現以下情況,交易執行預計會失敗:

  • 任何自定義的AnteHandler以太坊裝飾器檢查都失敗了:
    • 交易的最低gas量要求
    • Tx 發件人帳戶不存在或沒有足夠的費用餘額
    • 賬戶序列與交易Data.AccountNonce不匹配
    • 消息簽名驗證失敗
  • EVM 合約創建(即 evm.Create)失敗,或 evm.Call 失敗

轉換

MsgEthreumTx 可以轉換為 go-ethereum TransactionMessage 類型 為了創建和調用 evm 合約。

// AsTransaction creates an Ethereum Transaction type from the msg fields
func (msg MsgEthereumTx) AsTransaction() *ethtypes.Transaction {
 txData, err := UnpackTxData(msg.Data)
 if err != nil {
  return nil
 }

 return ethtypes.NewTx(txData.AsEthereumData())
}

// AsMessage returns the transaction as a core.Message.
func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
 msg := Message{
  nonce:      tx.Nonce(),
  gasLimit:   tx.Gas(),
  gasPrice:   new(big.Int).Set(tx.GasPrice()),
  gasFeeCap:  new(big.Int).Set(tx.GasFeeCap()),
  gasTipCap:  new(big.Int).Set(tx.GasTipCap()),
  to:         tx.To(),
  amount:     tx.Value(),
  data:       tx.Data(),
  accessList: tx.AccessList(),
  isFake:     false,
 }
 // If baseFee provided, set gasPrice to effectiveGasPrice.
 if baseFee != nil {
  msg.gasPrice = math.BigMin(msg.gasPrice.Add(msg.gasTipCap, baseFee), msg.gasFeeCap)
 }
 var err error
 msg.from, err = Sender(s, tx)
 return msg, err
}

簽名

為了使簽名驗證有效,TxData 必須包含來自 Signerv | r | s 值。 Sign 計算 secp256k1 ECDSA 簽名並簽署交易。 根據 EIP155 標準,它需要一個密鑰環簽名者和 chainID 來簽署以太坊交易。 此方法在填充交易簽名的 V、R、S 字段時改變交易。 如果未為消息定義發件人地址或發件人未在密鑰環上註冊,則該函數將失敗。

// Sign calculates a secp256k1 ECDSA signature and signs the transaction. It
// takes a keyring signer and the chainID to sign an Ethereum transaction according to
// EIP155 standard.
// This method mutates the transaction as it populates the V, R, S
// fields of the Transaction's Signature.
// The function will fail if the sender address is not defined for the msg or if
// the sender is not registered on the keyring
func (msg *MsgEthereumTx) Sign(ethSigner ethtypes.Signer, keyringSigner keyring.Signer) error {
 from := msg.GetFrom()
 if from.Empty() {
  return fmt.Errorf("sender address not defined for message")
 }

 tx := msg.AsTransaction()
 txHash := ethSigner.Hash(tx)

 sig, _, err := keyringSigner.SignByAddress(from, txHash.Bytes())
 if err != nil {
  return err
 }

 tx, err = tx.WithSignature(ethSigner, sig)
 if err != nil {
  return err
 }

 msg.FromEthereumTx(tx)
 return nil
}

TxData

MsgEthereumTx 支持來自 go-ethereum 的 3 種有效的以太坊交易數據類型: LegacyTxAccessListTxDynamicFeeTx。 這些類型被定義為 protobuf 消息,並打包到 MsgEthereumTx 字段中的 proto.Any 接口類型中。

LegacyTx

常规以太坊交易的交易数据。

type LegacyTx struct {
 // nonce corresponds to the account nonce (transaction sequence).
 Nonce uint64 `protobuf:"varint,1,opt,name=nonce,proto3" json:"nonce,omitempty"`
 // gas price defines the value for each gas unit
 GasPrice *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,2,opt,name=gas_price,json=gasPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_price,omitempty"`
 // gas defines the gas limit defined for the transaction.
 GasLimit uint64 `protobuf:"varint,3,opt,name=gas,proto3" json:"gas,omitempty"`
 // hex formatted address of the recipient
 To string `protobuf:"bytes,4,opt,name=to,proto3" json:"to,omitempty"`
 // value defines the unsigned integer value of the transaction amount.
 Amount *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,5,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`
 // input defines the data payload bytes of the transaction.
 Data []byte `protobuf:"bytes,6,opt,name=data,proto3" json:"data,omitempty"`
 // v defines the signature value
 V []byte `protobuf:"bytes,7,opt,name=v,proto3" json:"v,omitempty"`
 // r defines the signature value
 R []byte `protobuf:"bytes,8,opt,name=r,proto3" json:"r,omitempty"`
 // s define the signature value
 S []byte `protobuf:"bytes,9,opt,name=s,proto3" json:"s,omitempty"`
}

如果出現以下情況,則此消息字段驗證預計會失敗:

  • GasPrice 無效(nil,負數或超出 int256 範圍)
  • Fee (gasprice * gaslimit) 無效
  • Amount 無效(負數或超出 int256 範圍)
  • To 地址無效(無效的以太坊十六進制地址)

DynamicFeeTx

EIP-1559動態手續費交易的交易數據。

type DynamicFeeTx struct {
 // destination EVM chain ID
 ChainID *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"chainID"`
 // nonce corresponds to the account nonce (transaction sequence).
 Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
 // gas tip cap defines the max value for the gas tip
 GasTipCap *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=gas_tip_cap,json=gasTipCap,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_tip_cap,omitempty"`
 // gas fee cap defines the max value for the gas fee
 GasFeeCap *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=gas_fee_cap,json=gasFeeCap,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_fee_cap,omitempty"`
 // gas defines the gas limit defined for the transaction.
 GasLimit uint64 `protobuf:"varint,5,opt,name=gas,proto3" json:"gas,omitempty"`
 // hex formatted address of the recipient
 To string `protobuf:"bytes,6,opt,name=to,proto3" json:"to,omitempty"`
 // value defines the the transaction amount.
 Amount *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,7,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`
 // input defines the data payload bytes of the transaction.
 Data     []byte     `protobuf:"bytes,8,opt,name=data,proto3" json:"data,omitempty"`
 Accesses AccessList `protobuf:"bytes,9,rep,name=accesses,proto3,castrepeated=AccessList" json:"accessList"`
 // v defines the signature value
 V []byte `protobuf:"bytes,10,opt,name=v,proto3" json:"v,omitempty"`
 // r defines the signature value
 R []byte `protobuf:"bytes,11,opt,name=r,proto3" json:"r,omitempty"`
 // s define the signature value
 S []byte `protobuf:"bytes,12,opt,name=s,proto3" json:"s,omitempty"`
}

如果出現以下情況,則此消息字段驗證預計會失敗:

  • GasTipCap 無效(nil、負值或溢出 int256)
  • GasFeeCap 無效(nil、負值或溢出 int256)
  • GasFeeCap 小於 GasTipCap
  • Fee (gas price * gas limit) 無效(溢出 int256)
  • Amount 無效(負數或溢出 int256)
  • To 地址無效(無效的以太坊十六進制地址)
  • ChainIDnil

AccessListTx

EIP-2930訪問列表交易的交易數據。

type AccessListTx struct {
 // destination EVM chain ID
 ChainID *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"chainID"`
 // nonce corresponds to the account nonce (transaction sequence).
 Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"`
 // gas price defines the value for each gas unit
 GasPrice *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=gas_price,json=gasPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"gas_price,omitempty"`
 // gas defines the gas limit defined for the transaction.
 GasLimit uint64 `protobuf:"varint,4,opt,name=gas,proto3" json:"gas,omitempty"`
 // hex formatted address of the recipient
 To string `protobuf:"bytes,5,opt,name=to,proto3" json:"to,omitempty"`
 // value defines the unsigned integer value of the transaction amount.
 Amount *github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,6,opt,name=value,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"value,omitempty"`
 // input defines the data payload bytes of the transaction.
 Data     []byte     `protobuf:"bytes,7,opt,name=data,proto3" json:"data,omitempty"`
 Accesses AccessList `protobuf:"bytes,8,rep,name=accesses,proto3,castrepeated=AccessList" json:"accessList"`
 // v defines the signature value
 V []byte `protobuf:"bytes,9,opt,name=v,proto3" json:"v,omitempty"`
 // r defines the signature value
 R []byte `protobuf:"bytes,10,opt,name=r,proto3" json:"r,omitempty"`
 // s define the signature value
 S []byte `protobuf:"bytes,11,opt,name=s,proto3" json:"s,omitempty"`
}

如果出現以下情況,則此消息字段驗證預計會失敗:

  • GasPrice 無效(nil、負數或溢出 int256)
  • Fee (gas price * gas limit) 無效(溢出 int256)
  • Amount 無效(負數或溢出 int256)
  • To 地址無效(無效的以太坊十六進制地址)
  • ChainIDnil

ABCI

應用程序區塊鏈接口 (ABCI) 允許應用程序與 Tendermint 共識引擎進行交互。 該應用程序與 Tendermint 保持多個 ABCI 連接。 與 x/evm 最相關的是 提交時的共識連接。 此連接負責塊執行並調用函數InitChain(包含InitGenesis),BeginBlockDeliverTxEndBlockCommitInitChain 僅在第一次啟動新區塊鏈時調用,並且為塊中的每個交易調用 DeliverTx

初始化創世

InitGenesis 通過將 GenesisState 字段設置為存儲來初始化 EVM 模塊創世狀態。 特別是,它設置參數和創世帳戶(狀態和代碼)。

導出創世

ExportGenesis ABCI 函數導出 EVM 模塊的創世狀態。 特別是,它檢索所有帳戶及其字節碼、餘額和存儲、交易日誌、 以及 EVM 參數和鏈配置。

BeginBlock

EVM 模塊BeginBlock邏輯在處理交易的狀態轉換之前執行。 該功能的主要目標是:

  • 設置當前區塊的上下文,以便區塊頭、存儲、煤氣表等。 一旦在 EVM 狀態轉換期間調用了 StateDB 函數之一,Keeper 就可以使用這些函數。
  • 設置 EIP155 ChainID 編號(從完整的鏈 ID 獲得),以防之前在 InitChain 期間未設置它

結束出塊

EVM 模塊的EndBlock邏輯發生在執行完交易的所有狀態轉換之後。 該功能的主要目標是:

  • 發出塊綻放事件
    • 這是由於 web3 兼容性,因為以太坊標頭包含此類型作為字段。 JSON-RPC 服務使用此事件查詢從 Tendermint 標頭構造以太坊標頭。
    • 塊布隆過濾器值從瞬態存儲中獲取,然後發出

鉤子

x/evm 模塊實現了一個 EvmHooks 接口,可以在外部擴展和定制 Tx 處理邏輯。

這支持 EVM 合約調用原生 cosmos 模塊

  1. 定義日誌簽名並從智能合約發出特定日誌,
  2. 在本機 tx 處理代碼中識別這些日誌,以及
  3. 將它們轉換為本地模塊調用。

為此,接口包含一個 PostTxProcessing 掛鉤,用於在 EvmKeeper 中註冊自定義的 Tx 掛鉤。 這些Tx掛鉤在 EVM 狀態轉換完成並且不會失敗後進行處理。 請注意,EVM 模塊中沒有實現默認掛鉤。

type EvmHooks interface {
 // Must be called after tx is processed successfully, if return an error, the whole transaction is reverted.
 PostTxProcessing(ctx sdk.Context, msg core.Message, receipt *ethtypes.Receipt) error
}

PostTxProcessing

PostTxProcessing 僅在 EVM 事務​​成功完成後調用,並將調用委託給底層掛鉤。 如果沒有鉤子被註冊,這個函數返回一個nil 錯誤。 .

func (k *Keeper) PostTxProcessing(ctx sdk.Context, msg core.Message, receipt *ethtypes.Receipt) error {
 if k.hooks == nil {
  return nil
 }
 return k.hooks.PostTxProcessing(k.Ctx(), msg, receipt)
}

它與 EVM 事務​​在相同的緩存上下文中執行,如果它返回錯誤,整個 EVM 事務​​將被還原,如果 hook 實現者不想還原 tx,他們總是可以返回 nil

鉤子返回的錯誤被轉換為 VM 錯誤無法處理本機日誌,詳細的錯誤消息存儲在返回值中。 消息異步發送到本機模塊,調用者無法捕獲和恢復錯誤。

** 用例:在 Daodst 上調用原生 ERC20 模塊 **

以下是取自 Daodst erc20 模塊 的示例,展示了EVMHooks如何支持調用原生模塊以將 ERC-20 代幣轉換為 Cosmos 原生代幣的合約。 按照上面的步驟。

您可以像這樣在智能合約中定義並發出一個“Transfer”日誌簽名:

event Transfer(address indexed from, address indexed to, uint256 value);

function _transfer(address sender, address recipient, uint256 amount) internal virtual {
  require(sender != address(0), "ERC20: transfer from the zero address");
  require(recipient != address(0), "ERC20: transfer to the zero address");

  _beforeTokenTransfer(sender, recipient, amount);

  _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
  _balances[recipient] = _balances[recipient].add(amount);
  emit Transfer(sender, recipient, amount);
}

應用程序將向 EvmKeeper 註冊一個 BankSendHook。 它識別以太坊 tx Log 並將其轉換為對銀行模塊的 SendCoinsFromAccountToAccount 方法的調用:


const ERC20EventTransfer = "Transfer"

// PostTxProcessing implements EvmHooks.PostTxProcessing
func (k Keeper) PostTxProcessing(
 ctx sdk.Context,
 msg core.Message,
 receipt *ethtypes.Receipt,
) error {
 params := h.k.GetParams(ctx)
 if !params.EnableErc20 || !params.EnableEVMHook {
  // no error is returned to allow for other post-processing txs
  // to pass
  return nil
 }

 erc20 := contracts.ERC20BurnableContract.ABI

 for i, log := range receipt.Logs {
  if len(log.Topics) < 3 {
   continue
  }

  eventID := log.Topics[0] // event ID

  event, err := erc20.EventByID(eventID)
  if err != nil {
   // invalid event for ERC20
   continue
  }

  if event.Name != types.ERC20EventTransfer {
   h.k.Logger(ctx).Info("emitted event", "name", event.Name, "signature", event.Sig)
   continue
  }

  transferEvent, err := erc20.Unpack(event.Name, log.Data)
  if err != nil {
   h.k.Logger(ctx).Error("failed to unpack transfer event", "error", err.Error())
   continue
  }

  if len(transferEvent) == 0 {
   continue
  }

  tokens, ok := transferEvent[0].(*big.Int)
  // safety check and ignore if amount not positive
  if !ok || tokens == nil || tokens.Sign() != 1 {
   continue
  }

  // check that the contract is a registered token pair
  contractAddr := log.Address

  id := h.k.GetERC20Map(ctx, contractAddr)

  if len(id) == 0 {
   // no token is registered for the caller contract
   continue
  }

  pair, found := h.k.GetTokenPair(ctx, id)
  if !found {
   continue
  }

  // check that conversion for the pair is enabled
  if !pair.Enabled {
   // continue to allow transfers for the ERC20 in case the token pair is disabled
   h.k.Logger(ctx).Debug(
    "ERC20 token -> Cosmos coin conversion is disabled for pair",
    "coin", pair.Denom, "contract", pair.Erc20Address,
   )
   continue
  }

  // ignore as the burning always transfers to the zero address
  to := common.BytesToAddress(log.Topics[2].Bytes())
  if !bytes.Equal(to.Bytes(), types.ModuleAddress.Bytes()) {
   continue
  }

  // check that the event is Burn from the ERC20Burnable interface
  // NOTE: assume that if they are burning the token that has been registered as a pair, they want to mint a Cosmos coin

  // create the corresponding sdk.Coin that is paired with ERC20
  coins := sdk.Coins{{Denom: pair.Denom, Amount: sdk.NewIntFromBigInt(tokens)}}

  // Mint the coin only if ERC20 is external
  switch pair.ContractOwner {
  case types.OWNER_MODULE:
   _, err = h.k.CallEVM(ctx, erc20, types.ModuleAddress, contractAddr, true, "burn", tokens)
  case types.OWNER_EXTERNAL:
   err = h.k.bankKeeper.MintCoins(ctx, types.ModuleName, coins)
  default:
   err = types.ErrUndefinedOwner
  }

  if err != nil {
   h.k.Logger(ctx).Debug(
    "failed to process EVM hook for ER20 -> coin conversion",
    "coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(),
   )
   continue
  }

  // Only need last 20 bytes from log.topics
  from := common.BytesToAddress(log.Topics[1].Bytes())
  recipient := sdk.AccAddress(from.Bytes())

  // transfer the tokens from ModuleAccount to sender address
  if err := h.k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, recipient, coins); err != nil {
   h.k.Logger(ctx).Debug(
    "failed to process EVM hook for ER20 -> coin conversion",
    "tx-hash", receipt.TxHash.Hex(), "log-idx", i,
    "coin", pair.Denom, "contract", pair.Erc20Address, "error", err.Error(),
   )
   continue
  }
 }

 return nil

最後,在 app.go 中註冊鉤子:

app.EvmKeeper = app.EvmKeeper.SetHooks(app.Erc20Keeper)

事件

x/evm 模塊在狀態執行後發出 Cosmos SDK 事件。 EVM 模塊發出相關交易字段的事件,以及交易日誌(以太坊事件)。

MsgEthereumTx

类型 属性 Key 属性 Value
ethereum_tx "amount" {amount}
ethereum_tx "recipient" {hex_address}
ethereum_tx "contract" {hex_address}
ethereum_tx "txHash" {tendermint_hex_hash}
ethereum_tx "ethereumTxHash" {hex_hash}
ethereum_tx "txIndex" {tx_index}
ethereum_tx "txGasUsed" {gas_used}
tx_log "txLog" {tx_log}
message "sender" {eth_address}
message "action" "ethereum"
message "module" "evm"

此外,EVM 模塊在EndBlock期間為過濾器查詢塊 bloom 發出一個事件。

ABCI

類型 屬性 Key 屬性 Value
block_bloom "bloom" string(bloomBytes)

參數

evm 模塊包含以下參數:

模塊參數

Key 數據類型 默認值
EVMDenom string "dst"
EnableCreate bool true
EnableCall bool true
ExtraEIPs []int TBD
ChainConfig ChainConfig See ChainConfig

:::提示 注意:SDK 應用想要導入 EVM 模塊作為依賴 將需要設置自己的 evm_denom(即不是 "dst")。 :::

EnableCreate

EnableCreate 參數切換使用vm.Create函數的狀態轉換。 當禁用該參數時,它將阻止所有合約創建功能。

EnableCall

EnableCall 參數切換使用 vm.Call 函數的狀態轉換。 當該參數被禁用時,它將阻止賬戶之間的轉賬和執行智能合約調用。

Extra EIPs

額外的 EIPs 參數在應用自定義跳轉表的以太坊 VM Config 上定義了一組可激活的以太坊改進建議 (EIPs)。

:::提示 注意: 其中一些 EIP 已經通過鏈配置啟用,具體取決於硬分叉數量。 :::

支持的可激活 EIPS 是:

Chain Config

ChainConfig 是一種 protobuf 包裝器類型,它包含與 go-ethereum ChainConfig 參數相同的字段,但使用*sdk.Int 類型而不是*big.Int

默認情況下,除了ConstantinopleBlock之外的所有塊配置字段都在創世紀(高度 0)啟用。

默认 ChainConfig

名称 默认值
HomesteadBlock 0
DAOForkBlock 0
DAOForkSupport true
EIP150Block 0
EIP150Hash 0x0000000000000000000000000000000000000000000000000000000000000000
EIP155Block 0
EIP158Block 0
ByzantiumBlock 0
ConstantinopleBlock 0
PetersburgBlock 0
IstanbulBlock 0
MuirGlacierBlock 0
BerlinBlock 0
LondonBlock 0
ArrowGlacierBlock 0
GrayGlacierBlock 0
MergeNetsplitBlock 0
ShanghaiBlock 0
CancunBlock. 0

客戶端

用戶可以使用 CLI、JSON-RPC、gRPC 或 REST 查詢 evm 模塊並與之交互。

CLI

在下面找到使用 x/evm 模塊添加的 stcd 命令列表。 您可以使用 stcd -h 命令獲取完整列表。

查詢

query 命令允許用戶查詢 evm 狀態。

code

允許用戶查詢給定地址的智能合約代碼。

stcd query evm code ADDRESS [flags]
# Example
$ stcd query evm code 0x7bf7b17da59880d9bcca24915679668db75f9397

# Output
code: "0xef616c92f3cfc9e92dc270d6acff9cea213cecc7020a76ee4395af09bdceb4837a1ebdb5735e11e7d3adb6104e0c3ac55180b4ddf5e54d022cc5e8837f6a4f971b"

storage

允許用戶查詢具有給定鍵和高度的帳戶的存儲。

stcd query evm storage ADDRESS KEY [flags]
# Example
$ stcd query evm storage 0x0f54f47bf9b8e317b214ccd6a7c3e38b893cd7f0 0 --height 0

# Output
value: "0x0000000000000000000000000000000000000000000000000000000000000000"

交易

tx 命令允許用戶與 evm 模塊進行交互。

raw

允許用戶從原始以太坊交易構建 cosmos 交易。

stcd tx evm raw TX_HEX [flags]
# Example
$ stcd tx evm raw 0xf9ff74c86aefeb5f6019d77280bbb44fb695b4d45cfe97e6eed7acd62905f4a85034d5c68ed25a2e7a8eeb9baf1b84

# Output
value: "0x0000000000000000000000000000000000000000000000000000000000000000"

gRPC

查詢

Verb Method 說明
gRPC ethermint.evm.v1.Query/Account 獲取以太坊賬戶
gRPC ethermint.evm.v1.Query/CosmosAccount 獲取以太坊賬戶的 Cosmos 地址
gRPC ethermint.evm.v1.Query/ValidatorAccount 從驗證者共識地址獲取以太坊賬戶
gRPC ethermint.evm.v1.Query/Balance 獲取單個 EthAccount 的 EVM 面額餘額.
gRPC ethermint.evm.v1.Query/Storage 獲取單個賬戶所有幣的餘額
gRPC ethermint.evm.v1.Query/Code 獲取單個賬戶所有幣的餘額
gRPC ethermint.evm.v1.Query/Params 獲取x/evm模塊的參數
gRPC ethermint.evm.v1.Query/EthCall 實現 eth_call rpc api
gRPC ethermint.evm.v1.Query/EstimateGas 實現 eth_estimateGas rpc api
gRPC ethermint.evm.v1.Query/TraceTx 實現 debug_traceTransaction rpc api
gRPC ethermint.evm.v1.Query/TraceBlock 實現 debug_traceBlockByNumber 和 debug_traceBlockByHash rpc api
GET /ethermint/evm/v1/account/{address} 獲取以太坊賬戶
GET /ethermint/evm/v1/cosmos_account/{address} 獲取以太坊賬戶的 Cosmos 地址
GET /ethermint/evm/v1/validator_account/{cons_address} 從驗證者共識地址獲取以太坊賬戶
GET /ethermint/evm/v1/balances/{address} 獲取單個 EthAccount 的 EVM 面額餘額.
GET /ethermint/evm/v1/storage/{address}/{key} 獲取單個賬戶所有幣的餘額
GET /ethermint/evm/v1/codes/{address} 獲取單個賬戶所有幣的餘額
GET /ethermint/evm/v1/params 獲取x/evm模塊的參數
GET /ethermint/evm/v1/eth_call 實現 eth_call rpc api
GET /ethermint/evm/v1/estimate_gas 實現 eth_estimateGas rpc api
GET /ethermint/evm/v1/trace_tx 實現 debug_traceTransaction rpc api
GET /ethermint/evm/v1/trace_block 實現 debug_traceBlockByNumber 和 debug_traceBlockByHash rpc api

交易

Verb Method 說明
gRPC ethermint.evm.v1.Msg/EthereumTx 提交以太坊交易
POST /ethermint/evm/v1/ethereum_tx 提交以太坊交易