為什么Compound選擇使用Substrate進行獨立鏈的开發
Mike Tang
Compound(https://compund.finanice)在 2021年3月正式上线了其獨立鏈網絡 Gateway(原名 Compound Chain)。Gateway是一條獨立的鏈,它的目標是成為一個統一的 Defi 樞紐,實現跨鏈的 Defi——你可以質押鏈 A 上的資產,在鏈 B 上借款——從而將所有鏈的資產全部打通,提升全網絡資產利用率和流動性。
Gateway 的出現基於如下的判斷:未來世界一定是一個多鏈的世界,即沒有一條區塊鏈能解決所有問題。未來會有很多條鏈,很多種可能性。於是人們的資產會被分散到各個獨立的平臺。如何提升這些分散的資產的利用率,就是 Gateway 要研究和解決的問題。
Gateway 的系統架構
Gateway 架構圖
如架構圖所示,Gateway通過針對各種鏈(Peer Chain)开發特定的 Starport(Peer Chain上的一組合約或組件),來連接所有的鏈。
本文不會在 Gateway 的 Defi 相關事物上做過多敘述,本文主要就 Gateway 為何選擇 Substrate 進行开發做一個簡要的分析。
為什么要選擇 Substate
Compound 官方的說法:
We chose Substrate so that we could focus on building application code, instead of inventing consensus algorithms; it’s a modern framework built on a modern language, Rust. (https://medium.com/compound-finance/gateway-623f6f48d2b6)
我們選擇 Substrate 以便我們可以聚焦在構建應用代碼上,而不是去發明共識算法;並且它是一個現代框架,構建在現代語言 Rust 之上。
Rust語言是一門面向安全的現代編程語言。關於Rust語言的良好口碑,本文也不再贅述,感興趣的朋友可以閱讀《2020 开發者調查報告:Rust 再次成為最受歡迎的語言》。Rust幾乎已成為當今區塊鏈开發的首選語言,比如:Polkadot, Near, Solana, Dfinity,FileCoin 的底層等等都是Rust 實現的。Rust語言有以下顯著特性:
內存安全:其獨有的所有權和生命期設計,理論上保證不會出現內存錯誤。解決了底層系統缺陷中的70%的問題
並發安全:在並發編程(多线程,多協程)中,能保證並發的安全性
高性能:與C/C++是同一級的性能,目前主流語言中的頂級
零开銷抽象:此特性讓Rust能夠無縫連接 C 語言的既有生態,而沒有性能損失
強大的抽象表達能力:Rust借鑑了很多函數式語言的表達力,讓其表達更幹練
現代的工程化設施:Cargo 和 crates.io 這種現代化的輔助系統,讓 Rust 構建復雜工程時輕松自在
積極活躍的社區:Rust從一开始就是以社區模式开發迭代的,這點其實非常有意思,與go這種集權式开發顯著不同
了解Rust的這些特性後,我們再去理解“Rust語言幾乎已成為當今區塊鏈开發的首選語言“這句話就顯得很自然了。區塊鏈本身以安全性為第一位,這點與Rust的設計理念完全一致。區塊鏈中要做大量的計算,也需要高性能,這點Rust也是當仁不讓。區塊鏈系統一般比較復雜,對工程相關基礎設施的要求很高,而Rust強大的工程性設計,讓其在團隊、开源社區开發中,特別適合大型項目的協作。
以注重安全性聞名的 Paritytech 公司,用 Rust 語言开發了以太坊的 parity 客戶端。然後繼續用Rust 开發了新項目 Polkadot,在开發 Polkadot 的過程中,逐漸形成了一個重大決定:將區塊鏈的所有功能,拆解成抽象的設計,實現到一個开源的、通用的區塊鏈框架中,並以此框架為工具,構建 Polkadot 產品。而這個區塊鏈框架的重大成果,就是本文的主角——Substrate。
Substrate是一個用Rust語言开發的以通用性為目標的區塊鏈开發框架。它的設計元素,比如密碼學算法、存儲結構MPT樹,账戶體系等,大部分借鑑自有史以來最成功的以太坊的基礎設施,(這個可以理解,Paritytech 最早就是做以太坊客戶端起家的,Gavin Wood 也是以太坊的聯合創始人之一)。一個框架,要做到通用,就需要高度抽象。而高度抽象的代價往往會顯得結構復雜,不易於使用。所以 Substrate 也提供了很多 DSL(領域特定語言),方便新手學習使用。簡單歸納一下,Substrate具有如下特點:
1.面向通用。其設計面向通用領域,而不是專為某一條鏈做开發的 SDK。每個團隊都可以使用 Substrate 开發出一條完全獨立的不依賴於任何既有網絡的鏈出來(比如,使用Substrate 开發的區塊鏈可以與 Polkadot 無關,這也是 Paritytech 的設計目標之一)
2.功能全面。能覆蓋區塊鏈幾乎所有的場景,可以說是目前市面上功能最全面的區塊鏈框架
3.Runtime 代碼編譯成 wasm 執行。Wasm是當今區塊鏈業界主流的VM字節碼選擇
4.可定制性超強。Substrate 本身是一堆分散的組件,可以在一套規範約束下,自由替換組件,自由組合
工程設計就是做取舍,當它強調一方面的時候,在另一方面,就會有一定的妥協。Substrate也有自己的不足:
新引入一些概念名詞(比如:extrinsic),需要重新學習理解,上手有一定門檻
強調通用,導致在某些方面過於抽象,代碼視覺比較復雜(比如泛型參數特別多)。而這些抽象將 Rust 的高級語法特性做了充分的展現,代碼噪音較大
整個工程代碼量較大,依賴的包非常多(目前有1000多個),導致編譯時間比較長(普通筆記本幾十分鐘以上),對开發機性能要求較高
如上可以看到,這些不足主要是 Substrate 的設計目標——通用——本身帶來的客觀依賴復雜性造成的。
瑕不掩瑜,作為目前為止最強的區塊鏈开發框架之一,Substrate 受到了越來越多創新團隊的歡迎,Compound 團隊選擇 Substrate 進行 Gateway 的开發也就順理成章了。
Substrate 的功能組件
Substrate 整體框架圖
從架構圖可以看到,Substrate有如下幾大組件:
P2p Networking P2p網絡
Runtime 運行時
Storage 存儲
Consensus 共識
RPC 遠程過程調用
Telemetry 客戶端監測工具
P2p網絡,是區塊鏈系統必備的子系統。多個節點通過 p2p 連接成組成同一個網絡。在節點與節點之間發消息傳遞數據,網絡中的消息傳播有可能通過多個中繼節點傳播後到達。在Substrate 中,使用的是 rust-libp2p(https://github.com/libp2p/rust-libp2p),此項目也主要是由 Paritytech 在負責維護。
Runtime,運行時,是區塊鏈業務邏輯的實現部分。也就是說要用區塊鏈幹什么實際的事,就需要寫在這裏面。Substrate 支持將 Runtime 代碼編譯成 wasm 字節碼或 native code 兩種模式運行(隨着 wasm vm 運行速度的提升,未來 native code 模式可能會被拋棄,這也能降低一些內部復雜性)。在 Runtime 中可以操作 Storage,實現狀態的變更,因此 Runtime 整體也被稱作狀態轉移函數(State Transition Function, STF)。
Storage,存儲子系統,是區塊鏈系統中不可缺少的組成部分。在 Substrate 中,Storage 用於持久化 Runtime 的邏輯對狀態的變更,同時也支持對外的 RPC 狀態讀取接口,Runtime 中的 Event 在發出前,也會在 Storage 中做短暫的存儲。共識系統的目標,也是對 Storage 中的狀態達成一致。Storage 子系統在底層用的是kv數據庫 rocksdb 或 paritydb。
Consensus,共識子系統,用於在網絡的參與方之間就區塊鏈的狀態達成一致,也就是“共識”。由於是分布式系統,所謂區塊鏈的狀態,並沒有一個宏觀的上帝視角能看到一個統一的宏觀狀態。這個狀態,其實就是各個節點的狀態,每個節點有自己的本地狀態視圖,也有一個“局部“的全局視圖。每個節點通過本地視圖與全局視圖的比對,做出決策。共識系統要在各個節點上達成一個一致的狀態,從而推動系統往前運行。
RPC,遠程過程調用,用於向節點外部提供訪問的接口。一個 Substrate node 本身可以作為一個服務而存在,外界可以通過這些 rpc 接口訪問 node 的本地狀態信息或向 node 提交變更請求。Substrate 同時提供 HTTP 和 Websocket 兩種 rpc 通道。
Telemetry,客戶端監測工具,用於搜集 node 的運行信息,發送到遠端的 Prometheus 服務器。
Substrate 的开發模式
Substrate 是一個通用开發框架。它為不同層次的开發者提供了三種开發風格。
一:直接使用 Substrate 自帶的 node 起鏈。對於想快速起鏈,體驗效果的开發者,可以直接使用 Substrate 預置的 node 的實現。只需要修改一個 JSON 配置文件,就可以跑起來,具體可定制以下內容:
創始區塊的狀態信息
账戶 Account
余額 Balance
Staking 比例等
二:Runtime FRAME pallets 的开發。這種开發模式通過寫 Runtime 中的 pallet 代碼,將業務邏輯實現到 pallet 中。然後將自定義的 pallets 和其依賴的 Substrate 自帶的 pallets 一起編譯成 wasm 字節碼運行,這是大部分 Substrate 开發者的選擇。
三:基於 Substrate Core 深度定制。Substrate 已實現成分散的組件,做了充分的抽象和解耦。對於一些高級开發者,在某些特定的場景下,可以完全從底層重新組合這些組件,實現深度的 node 的定制。比如,可以做到:
使用不同的密碼曲线和哈希算法
使用不同的序列化方法
替換不同的共識算法
完全去除 FRAME 層代碼,使用另一種語言編寫業務,只要能保證編譯到 wasm 且遵循 Substrate 的規範即可
等等
下圖展示了三個層次的开發難度和技術靈活性之間的關系。
直接使用 Substrate Node 最簡單,但是最不靈活。基於 Substrate Core 开發最靈活,但是最難。進行 FRAME pallets 开發處於中間位置。也是大部分 Substrate 开發者應該採用的方式。
Substrate 的優秀之處
Substrate 的設計有很多優秀之處,我們來了解一下。
可升級無分叉 Runtime
由於 Substrate 的 Runtime 代碼編譯為 wasm 運行,然後 wasm 字節碼本身作為交易的數據直接提交到鏈上,再藉由鏈本身的 p2p 網絡全網傳播,實現業務邏輯的更新。每個節點在收到更新版本的 wasm 字節碼後,將其更新到代碼段,在某個塊之後就使用新版本的 wasm 來執行邏輯。
有了這種熱更新代碼機制,業務代碼的升級不再會引起分叉(軟分叉或硬分叉)了,也就是說,不會因為是技術客觀的原因,導致網絡的分叉(人為主動分叉還是可以的)。
需要注意的是,這種升級僅限於 wasm 字節碼能覆蓋的部分——也即 Runtime 中的代碼——的升級。如果改動了 node 代碼本身(即 Runtime 外的部分),仍然需要通知所有節點進行手動或devops 替換。
可替換的密碼學庫
Substrate 同時支持幾種密碼學曲线:
ECDSA
Ed25519
SR25519
同時支持幾種 Hash:
Blake2
xxHash
开發者可根據自己的需要選擇使用。如果沒有你想要的,按照它的架構為其添加新的曲线和 Hash 函數也不難。
層次豐富的 Account 系統
Substrate 的 Account Key 分三個層次:
Stash Key
Controller Key
Session Keys
Stash Key 是用來存資金的账戶,其私鑰部分應該盡可能安全地存儲在冷錢包中。Controller Key用來控制 Validator 的參數,也是 Stash Key 的一個中間代理账戶,在更新驗證人集合時非常有用。Session Keys 用來對共識相關的消息進行籤名。Session Keys 可以有多個,每一個都有自己專門的用途,一般可組合在一起使用,如:
上述代碼將4個獨立的 Session Keys: Grandpa session key, Babe session key, ImOnline session key, AuthorityDiscovery session key 組合在一起成為一個大的 SessionKeys。
可以將 Session Keys 理解成 Validator 運行過程中的唯一標識(Identification)。Session Keys 每過一個 Session 最好更換一次,這樣能最大程度保證安全性。
Substrate 通過這種分層的 Account Key 的設計,既保證了安全性,又提供了充分的靈活性,基本能覆蓋所有應用場景的需求。
抽象可切換的共識引擎
Substrate 設計了一套共識框架,這套共識框架非常了得。它將塊的生產(proposal)與敲定(finalize)分離,同時容納了 Nakamoto 類(只有概率性敲定,無確定性敲定)共識和 BFT 類(有確定性敲定)共識。
Substrate 為出塊提供了以下幾種算法:
Aura:slot 模式,在一個已知的 authority set 中,使用 round robin 模式輪流出塊
Babe:slot 模式,在一個已知的 authority set 中,使用可驗證隨機函數 VRF 隨機選擇出塊節點(每個 slot 可能不止一個出塊人)
Pow:工作量證明出塊
這幾種出塊算法,如果沒有 Finalize Gadget 配合,只能做到概率性敲定(finalization),而無法做到確定性敲定。Substrate 提供了 Grandpa 這個 Finalize Gadget,用於確定性敲定。
出塊算法與敲定算法可以自由配合使用。於是有 Aura/Grandpa, Babe/Grandpa, 甚至 Pow/Grandpa 這幾種組合。而在不需要確定性敲定的場景下,當然也可以不使用 Grandpa。Substrate 給了开發者充分的自由。
Substrate 共識框架還提供了其它一些基礎設施:
Fork Choince Rules: Longest Chain Rule 或 GHOST Rule,用於決定在鏈有分叉的情況下,如何選擇一個最好的鏈的算法
Import Queue:導入隊列
Block Import Trait:塊導入接口
Block Import Pipeline:塊導入流水线
Substrate 還提供了在 Runtime 中對共識過程進行協調控制的功能。比如,在 Pow 運行過程中調整難度,在 PoA 中決定下一個是否輪轉到,在 PoS 中動態修改 Stake 的權重等等。
在 Substrate 這一套完備的基礎設施之上,如果他自帶的共識引擎無法滿足开發者的需求,开發者還可以按照他的規範开發自己的共識引擎,引入到框架中使用,並且可在 Runtime 中進行適當控制。
Off-Chain 特性
Off-Chain 特性是 Substrate 中提供的一套相當強大的基礎設施。畢竟對區塊鏈來說,鏈上的邏輯操作空間非常有限,有些事情必須通過鏈下來完成。在沒有 Off-Chain Worker (OCW) 之前,這一類事情,通常是由預言機 Oracle 來完成。預言機是外部服務,通過區塊鏈節點 RPC 接口向區塊鏈提交交易從而把外界的信息傳到鏈上去。這種方式雖然是可行的,但它在安全性、集成性、可擴展性和基礎設施效率問題上面,仍然不夠好。
為了讓鏈下數據的集成更安全和有效,Substrate 提供了 off-chain 相關的特性。其架構圖如下:
Off-chain Worker架構圖
Off-chain 特性包含三大組件:
Off-Chain Worker
Off-Chain Storage
Off-Chain Indexing
Off-Chain Worker 用於實現鏈下邏輯。其代碼與 Runtime 代碼寫在一起,並被編譯到同一個 wasm 字節碼字符串中,在同一個交易中被傳播到全網絡。但是在執行的時候,Off-Chain Worker 的代碼是在獨立的 VM 中執行的,即與 Runtime 邏輯的執行完全隔離开。具體來說,Off-Chain Worker 能夠實現如下功能:
將計算的結果以交易形式提交到鏈上
包含一個全功能的 HTTP 客戶端,能夠訪問外部服務的數據
可以訪問本地 node 的 keystore,這樣便可以驗證和籤名交易
可以訪問本地的 KV 數據庫,且在所有 off-chain worker 中共享這個數據庫
本地的安全的熵源,用來產生隨機數
可以訪問本節點的本地時間
可以 sleep 和 resume 工作
Off-Chain Storage 是鏈下邏輯獨立的存儲空間,與鏈上的 Storage 是完全隔離开的。它具有如下特性:
能被 Off-Chain Worker 讀取和寫入
存儲在 node 本地,不會傳遞到網絡中其它節點去,不會參與網絡共識
被所有同時運行的 Off-Chain Workers 共享訪問(因此需要鎖操作)。因此,可以利用其在不同的 Workers 之間通信
能被 Runtime 代碼寫入,但是不能讀。因此,可基於其實現一定的鏈上鏈下交互功能
可被 wasm 環境外的 node 中的代碼讀取,因此能被 RPC 讀取
Off-Chain Indexing 提供了在 Runtime 環境中,向 Off-Chain Storage 寫入數據的能力。但是不能讀取 Off-Chain Storage 中的數據。這為一些新的編程範式提供了可能性。
其它還有一些,比如,完善的OCW集成測試框架等等。
Substrate 的 Off-chain 特性非常強大,令人印象深刻。
完備靈活的 Gas 費計算機制
有以太坊开發經驗的朋友都知道,Gas 費機制是非常成功的一個設計,對鏈的安全和平穩運行非常重要。幾乎所有後來的區塊鏈都直接借鑑了這種設計。而在 Substrate 中,提供了非常完整詳盡的機制和配置參量來幫助开發者設計他們自己的 Gas 費算法。Substrate 中內置了如下 Gas 計算和配置參量:
Includsion Fee: 包含 length_fee 和 weight_fee
Fee Multiplier
Additional Fees,包含
Bonds
Deposits
Burns
Limits
Default Weight Annotations
Dynamic Weights
Post Dispatch Weight Correction
Custom Fees
Custom Inclusion Fee
我們這裏不再對每個條目做詳細解釋,具體意義請參考:https://substrate.dev。
可以看到,Substrate 對 Gas 費計算的設計非常全面,甚至稍顯復雜。其目的仍然是實現通用區塊鏈框架的目標——該有的都應該有,並且要能开箱即用。
Substrate 非常適合用於啓動獨立的鏈或者是面向 Web3.0 應用的 Appchain,一般這些鏈會在用戶體驗上下足功夫。比如對於應用的普通用戶來講,使用服務的過程中,可能會意識不到 Gas 費的存在,在這種場景下,Substrate 提供的上述豐富的 Gas 費機制,能做到 Gas 費置零或者設置為代付。這類特性便有機會讓 Substrate 成為最適合 Web3.0 App 的开發框架之一。
Runtime API 與 RPC 集成
Substrate 提供了一套 RPC 擴展框架,讓开發者可以(在Substrate 默認提供的接口之外)擴展开發自己的 RPC 接口。由於 RPC 實現代碼是在 node 中,Runtime 之外,所以理論上來說,可以在 Substrate 中开發全功能(做任何事情)的 RPC 服務。這就使得 Substrate 成為了一個強大的 RPC 开發框架。
而往往我們需要與 Runtime 中的狀態進行交互,這時就需要用到 Runtime API 了。Runtime API 是 Runtime 內與外的橋梁,也可以說是鏈上與鏈下的橋梁。
於是 RPC 與 Runtime API 組合起來,就可以將外部請求發起到獲取鏈上狀態的流程全部打通。配合自定義的 Runtime API 功能和 RPC 擴展接口功能,給予了开發者巨大的靈活性和可能性。也為 Substrate 成為一個一體化集成式的 Web3.0 开發框架打下基礎。
Gateway 如何使用 Substrate
為了搞清楚 Gateway 是如何使用 Substrate 的,我們直接拉它的源代碼下來簡要分析一番。
git clone https://github.com/compound-finance/gateway
cd gateway && ls
可以看到,有如下主要目錄(我們加了簡要注釋)。
ethereum/ 以太坊上的 Starport 合約代碼
ethereum-client/ 以太坊相關類型定義和基礎工具函數
gateway-crypto/ Gateway 用到的密碼相關的基礎工具函數
integration/ 集成測試代碼
node/ Substrate 的自定義 node 代碼
our-std/ Gateway 的 std 代碼,是對 sp_std 的簡單封裝,並添加了一點額外的東西
pallets/ Gateway 獨立鏈的主體業務邏輯代碼
runtime/ Substrate 的運行時構建代碼
types-derive/ Gateway 用到的類型相關過程宏
可以看到,目前 Gateway 只實現了到 ethereum 的 startport 連接,其它鏈的對接還在开發中,會逐漸加入。
主體業務代碼在 pallets/ 目錄下,此目錄下有三個子目錄。
cash/ 主體業務邏輯
oracle/ 從喂價機獲取價格的代碼
runtime-interfaces/ 一些運行時接口
我們從 pallets/cash/src/lib.rs 看起。這是一個標准的 pallet 文件,其結構有
trait Config 的定義
decl_storage! 定義鏈上存儲部分
decl_event! 定義事件輸出部分
decl_module! 定義模塊實現部分
在模塊實現部分中,有 fn offchain_worker(block_number: T::BlockNumber) {} 這個函數。其實現主要是這兩個函數調用:
internal::events::track_chain_events::
internal::notices::process_notices::
我們知道,Substrate 的 offchain_worker 入口,是在每個塊導入本地狀態數據庫時調用。也即 fn offchain_worker(block_number: T::BlockNumber) 這個函數會被每個塊驅動執行,每個塊執行一次這個函數。因此,每增長一個塊,上面的代碼會分別處理 events 和 notices 這兩塊內容,而這兩塊內容的代碼在 internal 模塊中定義。
繼續追蹤 pallets/cash/src/internal/events.rs 和 pallets/cash/src/internal/notices.rs 文件後,可以整理出大體的邏輯要點:
Offchain_worker 每個塊調用一次
在 offchain_worker 代碼中,會調用到 ethereum-client/ 提供的以太坊基礎類型和輔助工具,發 http 請求到以太坊的事件服務器,批量獲取以太坊的 events,並分類處理
處理後的結果會以發交易的形式從 offchain_worker 中提交到鏈上
進入 Runtime 業務邏輯進行處理
pallets/oracle/ 裏面的邏輯流程也類似:在 offchain_worker 中,用 http 請求從喂價機地址獲取數據,然後提交給鏈上使用。
理解了上述流程後,也就能比較輕松地理解 Compound 在 Ethereum 上做 Compound Governance Proposal 是如何影響到 Gateway 鏈上的驗證人節點集合更新的了。其仍然是通過以太坊的事件向外拋出信息,事件服務器搜集到事件後,緩存下來。Gateway 這邊的 Offchain worker 每新增一個塊就去事件服務器上批量取一次最近的事件。獲取到最近的事件後,按對應的邏輯迭代處理就行了。而 Substrate 中要更新驗證人集合,需要用到 Controler account,以及 SetKeys 等相關由 Substrate 提供的功能。
限於篇幅,我們這裏的分析點到為止,這裏只簡要的分析了 Gateway Pallet 粗线條的邏輯流程,然後對 Offchain Worker 部分做了重點關注。
可以看到,Gateway 充分利用 Substrate 提供的基礎設施,很方便地實現了業務邏輯以及鏈上代碼與鏈下代碼的集成,以及與其它鏈的交互。整個代碼實現得非常清晰,值得我們學習借鑑。
總結
本篇,我們介紹了 Gateway 的總體架構,Substrate 的功能模塊,Substrate 框架的特色之處,以及 Gateway 是如何充分使用 Substrate 提供的基礎設施進行獨立鏈的开發。
限於篇幅,很多地方只能點到而止,文末附上一些鏈接,可供大家擴展閱讀。
筆者水平有限,歡迎讀者使用以下郵箱與我聯系:[email protected]。
參考資料
1.Introducing Gateway: https://medium.com/compound-finance/gateway-623f6f48d2b6
2.Gateway Document: https://docs.google.com/document/d/1bp4uLYHt0_lUNO3dveFGelXYAC-M7RojqY-5jPDyUlM/edit#heading=h.ymt4jrq294bj
3.Compound’s Gateway: a deep dive into setting up a validator node. https://medium.com/ethereum-on-steroids/compounds-gateway-a-deep-dive-into-setting-up-a-validator-node-399a2817702d
4.https://github.com/compound-finance/gateway
5.Substrate.dev: https://substrate.dev/docs/en/
6.《2020 开發者調查報告:Rust 再次成為最受歡迎的語言》 https://mp.weixin.qq.com/s/xGZqrXfzfthVSIUWIQWENA
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播信息之目的,不構成任何投資建議,如有侵權行為,請第一時間聯絡我們修改或刪除,多謝。
Arthur Hayes 新文聚焦 | 全球貨幣政策的真相,比特幣接下來何去何從?
作為一名宏觀經濟預測者,我試圖基於公开數據和當前事件,作出能夠指導投資組合資產配置的預測。我喜歡“...
Ouroboros DeFi:為什么 Usual Money 被低估了?
前言:Ouroboros DeFi 方法論在Ouroboros DeFi收益基金,我們的投資策略始...
WEEX 唯客交易所贊助臺北區塊鏈周 支持更多全球用戶Onboard Web3
第三屆臺北區塊鏈周(Taipei Blockchain Week, TBW)於 12 月 12-1...