這三十天會相當充實,這篇會那麼晚發其實是因為後面的文章貨屯不夠😂,這挑戰難度和字數應該都挺高的。主要會介紹以太坊並實作核心 EVM 的部分,在最後幾天會教基礎的 zkevm,會一步步教學如何實現核心 VM (以太坊虛擬機),然後理解怎麼運作後將這部分用 zk 證明 (這部分可能比較偏理論,因為程式做不完),這篇其實算是我做原始碼閱讀的筆記,所以有任何錯請多多包涵及留言~
以太坊可以視為一個交易狀態機 (state machine)。當交易狀態發生變化時,它是由打包成區塊的數據來實現的。對於帳本來說,以太坊就是一組交易的堆疊資料。而這些堆疊資料就是靠 Opcodes 計算出來的,而平常我們寫 solidity 在 compile 後就會轉成 bytecode 而這些 bytecode 會有相對應的 opcodes 可以使用。Opcodes 就會透過 EVM 做解析,再將資料寫入堆疊資料中,再透過節點將這個堆疊資料寫入狀態機。
這次挑戰就是要介紹如何將 bytecode 轉換成 Opcodes,細說 Opcodes 是如何運算的,將每個 Opcodes 的大致實現以 Python 撰寫出來,在 github 上有 code 可以拉下來跑跑看,可以測試 EVM 是如何運作的。
我們的目標是實現一個簡單的 Python EVM,這能讓我們更深入理解以太坊的運作方式。通過這個 30 天的教學,你將學會如何建立一個基本的虛擬機,以模擬以太坊的核心功能。
如果要跟著完成 evm 會需要安裝 docker 就好了,我是利用 vscode 的 devcontainer 安裝全部的環境,如果覺得不需要的話,就使用系統上原有的環境即可,主要會安裝以下工具和環境
先以介紹分佈式帳本為主,該帳本會紀錄所有帳戶的餘額,然後限制帳戶無法花費比餘額更多的費用,這限制是比特幣和許多區塊鏈的基礎.
但以太坊不僅支援帳戶餘額,同時支援智能合約,這讓以太坊不僅僅是分佈式帳本,更是一個分佈式狀態機.該狀態是好幾個數所構成的,不僅保存了帳戶餘額,同時保存機器的狀態,該狀態可以根據定義好的規則由前一個區塊做更改,這些規則就是由 EVM 所定義.
而可以把這狀態的轉換想像成一組數學公式,由一個舊的狀態 S 和一組交易 T,透過 EVM 函式匯出新的狀態 S’
如上所述,以太坊就是一個狀態機,所有節點會由創世區塊的狀態開始,開始執行每個區塊內的交易,這些交易就是一組組 bytecode,會根據 EVM 對狀態機的狀態做增刪改.而所有節點執行的交易和順序都相同,由創世區塊計算出來的狀態就會和其他節點的一致,該狀態就是世界狀態.
而這些狀態裡面記錄了什麼呢?他是由一種 Merkle Patricia Tree 的資料結構所記錄的,內部有帳戶的狀態和帳戶用 hash 關聯在一起的資料,而最後就是一個 tree root
如同大家所知的, 以太坊在支援加密貨幣(ETH)的同時,也支援智能合約
而這虛擬機就如同 JVM (bytecode-compiled) 的直譯器有著類似的執行脈絡,可以用於處理智能合約的部署和執行.如果當外部擁有帳戶 (EOA),只有做帳戶的餘額 (value) 更動,就不需要動到 EVM 的運作,這時候的 bytecode 就只會出現 0x
,而大多數以太坊的功能都會動用到該 vm 的運算.
EVM 是個類圖靈完備的狀態機,因為有 gas 和計算的限制,所以不會有停機問題,如果執行了惡意程式或無限迴圈,會因為 gas 總量而被暫停.
而 EVM 是基於堆疊的結構.他堆疊深度為 1024,一個 word 大小為 256 位元,所以為了方便加密 ,使用 Keccak256 hash 或 secp256k1 簽名.在執行期間會有一個暫時記憶體可供利用,該記憶體不會永久存在.
而合約會有一組 merkle patricia trie 儲存合約 bytecode,該存儲與帳戶和全局狀態關聯.
而編譯後的智能合約會以 opcodes 作執行,這就是下一章會介紹的部分.
交易是來自帳戶的簽名,而交易主要分為兩類
創建合約交易會建立一個合約帳戶,內部包含智能合約的 bytecode,當 EOA 傳遞訊息給合約帳戶就會執行該 bytecode
這邊將這章介紹的執行以 Python 撰寫出來,在這邊先設定一個 EVM 的 class ,在 execute 部分後面會實現內部 Opcodes 是如何執行的,而 solidity 編譯後的 bytecode 就會帶入該虛擬機,由程式計數器 PC 來計算 code 跑到哪裡了,而進入 operations 的部分後面會一步步帶大家實現
該 class 目前實現兩個 func
class EVM:
def __init__(self, code) -> None:
self.code = code
self.pc = 0
self.stack = []
# Program Counter Return Execute Opcodes
def next(self):
opCode = self.code[self.pc]
self.pc += 1
return opCode
# Run EVM until code end
def execute(self):
while self.pc < len(self.code):
opCode = self.next()
print(opCode)
# main
if __name__ == '__main__':
code = b"\x01\x01"
evm = EVM(code)
evm.execute()
print(evm.stack)
# output 1
# 1
那麼今天 EVM 就介紹到這邊,明天開始介紹 Operations 的實現
參考資料
https://ethereum.org/zh/developers/docs/evm/
https://takenobu-hs.github.io/downloads/ethereum_evm_illustrated.pdf