5月31日,Curio(@0xcurio)開源了Keystone,一個內置了遊戲Tick和ECS全鏈遊戲引擎的L2鏈,該鏈基於OP Stack製作。相比通過智能合約編寫ECS 狀態,這種設計允許所有ECS 操作(如查詢和狀態設置)具有更快的性能。通過自定義預編譯,智能合約可以訪問底層的ECS 鏈狀態。遊戲邏輯可以用Go 語言編寫,而不是Solidity,這可以大規模並行化。本文將對Curio項目本身,以及它的原理做深度剖析,並探討它是如何實現上述目的的。
Curio 由工程師和遊戲玩家Kevin Zhang (@kzdagoof)和Yijia Chen (@0x1plus)於2022 年創立,致力於製作完全由智能合約驅動的全鏈遊戲。這使得一種新的多人計算方式成為可能,允許所有參與者為「共享宇宙」做出貢獻,創始人表示這讓遊戲可以幾乎完全由玩家創造。該公司的第一款遊戲Treaty 是一款鏈上策略遊戲,用戶可以在其中編寫和部署智能合約。 2023年2月21日,Curio宣布完成290 萬美元種子輪融資,本輪融資由Bain Capital Crypto 領投,TCG Crypto、Formless Capital、Smrti Lab、Robot Ventures、Zonff Partners 和多家天使投資人參投。
全鏈遊戲目前有多種敘事方式,常見的有去中心化遊戲(DeGame),自治世界(Autonomous Worlds),Curio提出了自己的想法:用戶生成邏輯(User Generated Logic),簡稱UGL。我估摸著,Curio在製作自己的第一款全鏈遊戲Treaty的過程中,遇到了不少的困難,就萌生了製作自己的第一個鏈並且把遊戲引擎內置進去,這個想法和Argus不謀而合,區別在於Argus採用了分片機制,而Curio走了捷徑,直接採用OP Stack。
MUD 的ECS 遊戲框架
我們先來看MUD的ECS框架是如何工作的?如果您看過我寫的科普文章《深度解析全鏈遊戲引擎MUD》( https://captainz.xlog.app/shen-du-jie-xi-quan-lian-you-xi-yin-qing-MUD ),那應該了解到ECS 通過將邏輯、數據和實體分離,提高了遊戲開發的靈活性和可維護性。編程語言採用Solidity,遊戲對象的屬性狀態儲存在智能合約中。以ERC-20 合約為例:ERC-20 合約將每個地址的代幣餘額存儲在一個映射中(從address 到uint256 餘額)。我們可以將每個ERC-20 合約視為一個具有兩列的表:"地址" 和"餘額"。這對應於具有單個模式值("餘額")的組件。表中的每行都將一個實體("地址")與一個組件值("餘額")關聯起來。一個地址可以在許多獨立的ERC-20 合約中持有餘額,這對應於一個實體與許多獨立的組件值關聯。在當前的ERC-20 參考實現中,狀態和邏輯是耦合在同一個合約中的。在ECS 中,將有一個通用的"轉賬系統" 來處理從一個地址向另一個地址轉賬代幣的邏輯,通過修改代幣組件中存儲的狀態。
因為MUD裡面的遊戲對象屬性狀態儲存在智能合約,所以ECS的狀態更改(客戶端的狀態同步到區塊鏈節點)每次都會通過智能合約來同步,而這個同步的過程無法並行進行且需要頻繁調用合約。所以Curio希望通過引入預編譯合約來解決。
預編譯合約
以太坊虛擬機(EVM)中的預編譯合約是一種特殊類型的智能合約,其代碼直接硬編碼在以太坊節點的代碼中,而不是在Solidity 或其他EVM 兼容語言中編寫的。這種合約通常用於執行複雜的計算任務,因為硬編碼的實現通常比在EVM 中解釋執行的代碼更高效。預編譯合約通常用於優化性能和降低gas 成本。
實現預編譯函數涉及以下步驟:
1. 選擇地址:預編譯函數需要一個地址。以太坊選擇了`1` 到`ff`(包括`ff`)這些地址來存儲預編譯合約。
2. 實現功能:預編譯函數需要實現某種功能。這通常涉及到一些複雜的計算,例如橢圓曲線操作或大整數運算。這個函數通常使用Golang 或C++ 編寫,然後直接集成到以太坊節點的代碼中。
3. 計算gas 成本:預編譯函數需要一個函數來計算其運行所需的gas。這個函數應該根據輸入數據的大小和操作的複雜性來確定gas 成本。
4. 集成到以太坊節點:預編譯函數需要集成到以太坊節點的代碼中。這通常涉及修改以太坊節點的代碼以添加新的預編譯合約,並重新編譯和部署節點。
然後,智能合約可以通過調用預編譯合約的地址來使用這個預編譯函數。 EVM 將檢查該地址是否存在預編譯合約,如果存在,EVM 將直接調用節點代碼中的硬編碼函數,而不是在EVM 中解釋執行合約代碼。
需要注意的是,添加新的預編譯函數需要對以太坊的協議進行修改,這通常需要通過社區的共識。此外,由於預編譯函數是硬編碼在以太坊節點的代碼中的,因此每個運行這個新版本的以太坊節點都需要包含這個新的預編譯函數的代碼。這意味著在實踐中,添加新的預編譯函數是一個複雜且需要深思熟慮的過程。
Curio 的解決方案
我們在上面預編譯合約的討論中,可以發現,雖然使用預編譯合約可以極大的提高性能,但是需要修改鏈的節點代碼以及重新編譯和部署節點。以太坊主鏈根本做不到這點。於是Curio選擇了OP Stack,在這個定制化的Layer2裡面,他們修改了節點代碼以添加ECS的預編譯合約。正是通過這個自定義的預編譯合約,智能合約可以直接訪問底層的ECS 鏈狀態。因此,這種設計允許所有ECS 操作(如查詢和狀態設置)具有更快的性能。又因為OP Stack 節點使用的是“Go-Ethereum”客戶端,這就允許Keystone 遊戲邏輯可以用Go 語言編寫,而不是Solidity,這可以大規模並行化。
Keystone中的ECS主要是通過'engine'和'game'這兩個目錄的代碼實現的。
在'engine'目錄中,我們看到了定義了ECS的主要數據結構,例如World
和Component
。 World
是一個ECS基礎世界結構,它包括實體(Entities)和組件(Components)兩個主要部分。每個Component
包含一個數據類型(DataType),以及用於存儲實體到值(EntitiesToValue)和值到實體(ValueToEntities)的映射。
在'game'目錄中,我們看到了一些特定的遊戲組件,如位置組件(PositionComp)、目標位置組件(TargetPositionComp)、標籤組件(TagComp)等。這些組件都有各自的數據類型和是否需要存儲值到實體的標誌(ShouldStoreValueToEntities)。
集成了遊戲引擎的鏈的優點
通過上面的討論,我們可以看到,將ECS(Entity-Component-System)狀態直接構建到定制化的區塊鏈中,可以比通過智能合約編寫ECS狀態實現更快的性能。這種性能提升來自以下幾個方面:
1. 數據結構優化:在智能合約中編寫ECS狀態通常需要使用一些非優化的數據結構,而在Keystone中,它們可以使用高效的數據結構(如稀疏集合)來存儲和操作ECS數據,從而提高查詢和設置狀態的速度。
2. 避免智能合約執行開銷:EVM(以太坊虛擬機)需要解釋執行智能合約的代碼,這會帶來一定的開銷。然而,將ECS狀態直接構建到區塊鏈中可以避免這種開銷,因為ECS操作是在區塊鏈的核心代碼中直接執行的,而不是通過解釋執行智能合約。
3. 並行化:Keystone允許在Go中編寫遊戲邏輯,這意味著可以利用Go的並行和並發特性來提高ECS操作的速度。這在智能合約中是無法實現的,因為EVM是單線程的。
4. 預編譯合約:通過使用預編譯合約來訪問ECS狀態,可以提高ECS操作的速度。預編譯合約是在區塊鏈節點代碼中直接硬編碼的函數,執行速度比在EVM中解釋執行代碼要快。
5. 狀態更新優化:Keystone採用了一種方法,允許在子世界中進行狀態更新,然後將這些更新應用到父世界。這種方法可以減少不必要的狀態更新,從而提高狀態設置的速度。
遊戲節拍器在哪裡
GameTick的概念通常在遊戲開發中用於管理遊戲內的時間進程。每一個tick代表了遊戲主循環的一個週期,各種遊戲事件可以根據這些ticks進行調度。這也是我們說傳統遊戲是“loop-based”的原因。
而區塊鏈的狀態本身並不包含我們通常理解的"當前時間"的概念。區塊鍊是基於區塊的概念運作的,這些區塊按照線性順序被添加到鏈中。雖然這些區塊通常包含一個時間戳,但這並不像在傳統計算環境中那樣被用作"當前時間"的度量。
Curio宣稱集成了GameTick在區塊鏈中(GameTick built into the chain),但是我查遍了整個代碼庫,也沒找到關於GameTick的代碼片段,所以對Keystone 如何實現在區塊鏈中的遊戲節拍感到十分好奇,希望Curio有機會可以對這個功能做更多的細節說明。不過我對此的一個猜測是這樣的,在Keystone的GameTick環境中,next_tick字段可能用於確定遊戲循環的下一個週期應該在何時發生,這基於區塊鏈節點服務器的內部時鐘,它被用來管理遊戲時間在遊戲自身的內部邏輯內的進程,與區塊鏈內區塊的進程是分開的。