前言
首先讀者需要具備Go語言基礎(chǔ),至少要通關(guān)菜鳥教程,知道Go語言的基本語法,這些基礎(chǔ)教程網(wǎng)絡(luò)上非常多,請(qǐng)大家自行學(xué)習(xí)。
具備語言基礎(chǔ)了,還需要在開始這一章之前做一些準(zhǔn)備工作:
- 安裝Go SDK,即Go語言的開發(fā)環(huán)境;
- 安裝GoLand,即Go語言的IDE,當(dāng)然也可以選擇VSCode等其他IDE;
- 克隆 Go Ethereum源碼;
- 克隆Understanding-Ethereum-Go-version源碼(可選);
- 以太坊基礎(chǔ)知識(shí),Ethereum 協(xié)議(黃皮書 )
做好這些準(zhǔn)備工作,就可以打開 Go Ethereum源碼了,如下圖所示:
以太坊是以區(qū)塊鏈作為基礎(chǔ)的應(yīng)用,所以我們必須具備區(qū)塊鏈相關(guān)的基礎(chǔ)知識(shí),否則很難讀懂源碼究竟在做什么。
侵刪聲明:如果本文有侵犯到Understanding-Ethereum-Go-version原作者的地方,請(qǐng)告知?jiǎng)h除相關(guān)內(nèi)容,筆者已經(jīng)向Understanding-Ethereum-Go-version作者發(fā)送了添加微信好友的請(qǐng)求,希望可以加到對(duì)方好友!
本文的宗旨還是學(xué)習(xí)和分享,并無商業(yè)目的,希望可以將自己的心得記錄下來,對(duì)于引用的出處都會(huì)提前聲明。
下面開始對(duì)Understanding-Ethereum-Go-version的增刪改查工作,當(dāng)然,主要是跟著作者思路去學(xué)習(xí)!
[Chapter_001] 萬物的起點(diǎn): Geth Start
本章概要:
-
go-ethereum
代碼庫的主要目錄結(jié)構(gòu)。 -
geth
客戶端/節(jié)點(diǎn)是如何啟動(dòng)的。 - 如何修改/添加
geth
對(duì)外的APIs。
什么是 geth?
geth
是以太坊基金會(huì)基于 Go 語言開發(fā)以太坊的官方客戶端,它實(shí)現(xiàn)了 Ethereum 協(xié)議(黃皮書 )中所有需要的實(shí)現(xiàn)的功能模塊。我們可以通過啟動(dòng) geth
來運(yùn)行一個(gè) Ethereum 的節(jié)點(diǎn)。在以太坊 Merge 之后,geth
作為節(jié)點(diǎn)的執(zhí)行層繼續(xù)在以太坊生態(tài)中發(fā)揮重要的作用。 go-ethereum
是包含了 geth
客戶端代碼和以及編譯 geth
所需要的其他代碼在內(nèi)的一個(gè)完整的代碼庫。在本系列中我們會(huì)通過深入 go-ethereum 代碼庫,從High-level 的 API 接口出發(fā),沿著 Ethereum 主 Workflow,逐一的理解 Ethereum 具體實(shí)現(xiàn)的細(xì)節(jié)。
為了方便區(qū)分,在接下來的文章中,我們用 geth
來表示 Geth 客戶端程序,用 go-ethereum (Geth
)來表示 go-ethereum 的代碼庫。
總結(jié)的來說:
- 基于
go-ethereum
代碼庫中的代碼,我們可以編譯出geth
客戶端程序。 - 通過運(yùn)行
geth
客戶端程序我們可以啟動(dòng)一個(gè) Ethereum 的節(jié)點(diǎn)。
go-ethereum Codebase 結(jié)構(gòu)
為了更好的從整體工作流的角度來理解 Ethereum,根據(jù)主要的業(yè)務(wù)功能,我們可以把 go-ethereum
劃分成如下幾個(gè)模塊。
- Geth Client 模塊(客戶端)
- Core 數(shù)據(jù)結(jié)構(gòu)模塊
- State Management 模塊(狀態(tài)管理)
- StateDB 模塊(狀態(tài)數(shù)據(jù)庫)
- Trie 數(shù)據(jù)結(jié)構(gòu)模塊
- State Optimization (Pruning)減枝算法優(yōu)化
- Mining 模塊(挖礦)
- EVM 模塊(以太坊虛擬機(jī)Ethereum Virtual Machine)
- P2P 網(wǎng)絡(luò)模塊
- 節(jié)點(diǎn)數(shù)據(jù)同步
- 交易數(shù)據(jù)
- 區(qū)塊數(shù)據(jù)
- 區(qū)塊鏈數(shù)據(jù)
- 節(jié)點(diǎn)數(shù)據(jù)同步
- Storage 模塊
- 抽象數(shù)據(jù)庫層
- LevelDB 調(diào)用
- …
目前,go-ethereum 代碼庫中的主要目錄結(jié)構(gòu)如下所示:
cmd/ 以太坊基金會(huì)官方開發(fā)的一些 Command-line 程序。該目錄下的每個(gè)子目錄都是一個(gè)單獨(dú)運(yùn)行的 CLI 程序。
|── clef/ 以太坊官方推出的賬戶管理程序.
|── geth/ 以太坊官方的節(jié)點(diǎn)客戶端。
core/ 以太坊核心模塊,包括核心數(shù)據(jù)結(jié)構(gòu),statedb,EVM 等核心數(shù)據(jù)結(jié)構(gòu)以及算法實(shí)現(xiàn)
|── rawdb/ db 相關(guān)函數(shù)的高層封裝(在 ethdb 和更底層的 leveldb 之上的封裝)
├──accessors_state.go 從 Disk Level 讀取/寫入與 State 相關(guān)的數(shù)據(jù)結(jié)構(gòu)。
|── state/
├── statedb.go StateDB 是管理以太坊 World State 最核心的代碼,用于管理鏈上所有的 State 相關(guān)操作。
├── state_object.go state_object 是以太坊賬戶(包括 EOA & Contract)在 StateDB 具體的實(shí)現(xiàn)。
|── txpool Transaction Pool 相關(guān)的代碼。
|── txpool.go Transaction Pool 的具體實(shí)現(xiàn)。
|── types/ 以太坊中最核心的數(shù)據(jù)結(jié)構(gòu)
|── block.go 以太坊 Block 的的數(shù)據(jù)結(jié)構(gòu)定義與相關(guān)函數(shù)實(shí)現(xiàn)
|── bloom9.go 以太坊使用的一個(gè) Bloom Filter 的實(shí)現(xiàn)
|── transaction.go 以太坊 Transaction 的數(shù)據(jù)結(jié)構(gòu)定義與相關(guān)函數(shù)實(shí)現(xiàn)。
|── transaction_signing.go 用于對(duì) Transaction 進(jìn)行簽名的函數(shù)的實(shí)現(xiàn)。
|── receipt.go 以太坊交易收據(jù)的實(shí)現(xiàn),用于記錄以太坊 Transaction 執(zhí)行的結(jié)果
|── vm/ 以太坊的核心中核心 EVM 相關(guān)的一些的數(shù)據(jù)結(jié)構(gòu)的定義。
|── evm.go EVM 數(shù)據(jù)結(jié)構(gòu)和方法的定義
|── instructions.go EVM 指令的具體的定義,核心中的核心中的核心文件。
|── logger.go 用于追蹤 EVM 執(zhí)行交易過程的日志接口的定義。具體的實(shí)現(xiàn)在eth/tracers/logger/logger.go 文件中。
|── opcode.go EVM 指令和數(shù)值的對(duì)應(yīng)關(guān)系。
|── genesis.go 創(chuàng)世區(qū)塊相關(guān)的函數(shù)。每個(gè) geth 客戶端/以太坊節(jié)點(diǎn)初始化的都需要調(diào)用這個(gè)模塊。
|── state_processor.go EVM 執(zhí)行交易的核心代碼模塊。
console/
|── bridge.go
|── console.go Geth Web3 控制臺(tái)的入口
eth/ Ethereum 節(jié)點(diǎn)/后端/客戶端具體功能定義和實(shí)現(xiàn)。例如節(jié)點(diǎn)的啟動(dòng)關(guān)閉,P2P 網(wǎng)絡(luò)中交易和區(qū)塊的同步。
ethdb/ Ethereum 本地存儲(chǔ)的相關(guān)實(shí)現(xiàn), 包括 leveldb 的調(diào)用
|── leveldb/ Go-Ethereum使用的與 Bitcoin Core version一樣的Leveldb作為本機(jī)存儲(chǔ)用的數(shù)據(jù)庫
internal/ 一些內(nèi)部使用的工具庫的集合,比如在測試用例中模擬 cmd 的工具。在構(gòu)建 Ethereum 生態(tài)相關(guān)的工具時(shí)值得注意這個(gè)文件夾。
miner/
|── miner.go 礦工模塊的實(shí)現(xiàn)。
|── worker.go Block generation 的實(shí)現(xiàn),包括打包 transaction,計(jì)算合法的 Block
p2p/ Ethereum 的P2P模塊
|── params Ethereum 的一些參數(shù)的配置,例如: bootnode 的 enode 地址
|── bootnodes.go bootnode 的 enode 地址 like: aws 的一些節(jié)點(diǎn),azure 的一些節(jié)點(diǎn),Ethereum Foundation 的節(jié)點(diǎn)和 Rinkeby 測試網(wǎng)的節(jié)點(diǎn)
rlp/ RLP的 Encode與 Decode的相關(guān),RLP(Recursive Length Prefix)是以太坊中序列化數(shù)據(jù)的編碼方式。
rpc/ Ethereum RPC客戶端的實(shí)現(xiàn),遠(yuǎn)程過程調(diào)用。
les/ Ethereum light client 輕節(jié)點(diǎn)的實(shí)現(xiàn)
trie/ Ethereum 中至關(guān)重要的數(shù)據(jù)結(jié)構(gòu) Merkle Patrica Trie(MPT) 的實(shí)現(xiàn)
|── committer.go Trie 向 Memory Database 提交數(shù)據(jù)的工具函數(shù)。
|── database.go Memory Database,是 Trie 數(shù)據(jù)和 Disk Database 提交的中間層。同時(shí)還實(shí)現(xiàn)了 Trie 剪枝的功能。**非常重要**
|── node.go MPT中的節(jié)點(diǎn)的定義以及相關(guān)的函數(shù)。
|── secure_trie.go 基于 Trie 的封裝的結(jié)構(gòu)。與 trie 中的函數(shù)功能相同,不過secure_trie中的 key 是經(jīng)過hashKey()函數(shù)hash過的,無法通過路徑獲得原始的 key值
|── stack_trie.go Block 中使用的 Transaction/Receipt Trie 的實(shí)現(xiàn)
|── trie.go MPT 具體功能的函數(shù)實(shí)現(xiàn)。
Geth Start
前奏: Geth Console
當(dāng)我們想要部署一個(gè) Ethereum 節(jié)點(diǎn)的時(shí)候,最直接的方式就是下載官方提供的發(fā)行版的 geth 客戶端程序。geth
是一個(gè)基于 CLI (命令行)的應(yīng)用,啟動(dòng)geth
和調(diào)用 geth
的功能性 API 需要使用對(duì)應(yīng)的指令來操作。geth
提供了一個(gè)相對(duì)友好的 console 來方便用戶調(diào)用各種指令。當(dāng)我第一次閱讀 Ethereum 的文檔的時(shí)候,我曾經(jīng)有過這樣的疑問,為什么geth
是由 Go 語言編寫的,但是在官方文檔中的 Web3 的API卻是基于 Javascript 的調(diào)用?
這是因?yàn)?geth
內(nèi)置了一個(gè) Javascript 的解釋器: Goja (interpreter),來作為用戶與 geth
交互的 CLI Console。我們可以在console/console.go
中找到它的定義。
< !-- /Goja is an implementation of ECMAScript 5.1 in Pure GO/ -->
//控制臺(tái)是一個(gè)JavaScript解釋的運(yùn)行時(shí)環(huán)境。它是一個(gè)完全成熟的JavaScript控制臺(tái),通過外部或進(jìn)程內(nèi)RPC客戶端連接到正在運(yùn)行的節(jié)點(diǎn)。
type Console struct {
client *rpc.Client // 通過RPC客戶端執(zhí)行以太坊請(qǐng)求
jsre *jsre.JSRE // 運(yùn)行解釋器的JavaScript運(yùn)行時(shí)環(huán)境
prompt string // 輸入提示前綴字符串
prompter prompt.UserPrompter // 輸入提示器,通過它來允許交互式用戶反饋
histPath string // 控制臺(tái)回滾歷史記錄的絕對(duì)路徑
history []string // 由控制臺(tái)維護(hù)的滾動(dòng)歷史記錄字符串?dāng)?shù)組
printer io.Writer // 輸出寫入器,通過它來序列化任何顯示字符串
interactiveStopped chan struct{}
stopInteractiveCh chan struct{}
signalReceived chan struct{}
stopped chan struct{}
wg sync.WaitGroup
stopOnce sync.Once
}
筆者對(duì)引用的源代碼做了更新,并且對(duì)注解做了中文翻譯,大家閱讀時(shí)可以參照源代碼。
geth 節(jié)點(diǎn)是如何啟動(dòng)的
了解 Ethereum,我們首先要了解 Ethereum 客戶端 Geth 是怎么運(yùn)行的。 geth 程序的啟動(dòng)點(diǎn)位于 cmd/geth/main.go/main()
函數(shù)處,如下所示。
func main() {
//筆者注:運(yùn)行app,如果有錯(cuò)誤就打印出來,然后退出
if err := app.Run(os.Args); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
筆者這里是補(bǔ)充一下main.go中對(duì)app的定義
var app = flags.NewApp("the go-ethereum command line interface")
func init() {
// Initialize the CLI app and start Geth
app.Action = geth
app.HideVersion = true // we have a command to print the version
app.Copyright = "Copyright 2013-2022 The go-ethereum Authors"
app.Commands = []*cli.Command{
// See chaincmd.go:
initCommand,
importCommand,
exportCommand,
importPreimagesCommand,
exportPreimagesCommand,
removedbCommand,
dumpCommand,
dumpGenesisCommand,
// See accountcmd.go:
accountCommand,
walletCommand,
// See consolecmd.go:
consoleCommand,
attachCommand,
javascriptCommand,
// See misccmd.go:
makecacheCommand,
makedagCommand,
versionCommand,
versionCheckCommand,
licenseCommand,
// See config.go
dumpConfigCommand,
// see dbcmd.go
dbCommand,
// See cmd/utils/flags_legacy.go
utils.ShowDeprecated,
// See snapshot.go
snapshotCommand,
// See verkle.go
verkleCommand,
}
sort.Sort(cli.CommandsByName(app.Commands))
app.Flags = flags.Merge(
nodeFlags,
rpcFlags,
consoleFlags,
debug.Flags,
metricsFlags,
)
app.Before = func(ctx *cli.Context) error {
flags.MigrateGlobalFlags(ctx)
return debug.Setup(ctx)
}
app.After = func(ctx *cli.Context) error {
debug.Exit()
prompt.Stdin.Close() // Resets terminal mode.
return nil
}
}
大家從app的定義中知道了app就是go-ethereum命令行接口,那么才會(huì)進(jìn)一步有下面的邏輯,所以大家在閱讀的時(shí)候一定要參照源代碼,否則很難跟上作者的節(jié)奏。
我們可以看到 main()
函數(shù)非常的簡短,其主要功能就是啟動(dòng)一個(gè)解析 command line命令的工具: gopkg.in/urfave/cli.v1
。繼續(xù)深入,我們會(huì)發(fā)現(xiàn)在 cli app 初始化的時(shí)候會(huì)調(diào)用 app.Action = geth
,來調(diào)用 geth()
函數(shù)。而 geth()
函數(shù)就是用于啟動(dòng) Ethereum 節(jié)點(diǎn)的頂層函數(shù),其代碼如下所示:
// 如果沒有運(yùn)行特殊的子命令,Geth是進(jìn)入系統(tǒng)的主要入口點(diǎn)。
// 它根據(jù)命令行參數(shù)創(chuàng)建一個(gè)默認(rèn)節(jié)點(diǎn),并以阻塞模式運(yùn)行它,直到它關(guān)閉才解除阻塞。
func geth(ctx *cli.Context) error {
if args := ctx.Args().Slice(); len(args) > 0 {
return fmt.Errorf("invalid command: %q", args[0])
}
prepare(ctx)
stack, backend := makeFullNode(ctx)
defer stack.Close()
startNode(ctx, stack, backend, false)
stack.Wait()
return nil
}
在 geth()
函數(shù)中,有三個(gè)比較重要的函數(shù)調(diào)用,分別是:prepare()
,makeFullNode()
,以及 startNode()
。
prepare()
函數(shù)的實(shí)現(xiàn)就在當(dāng)前的 main.go
文件中。它主要用于設(shè)置一些節(jié)點(diǎn)初始化需要的配置。比如,我們?cè)诠?jié)點(diǎn)啟動(dòng)時(shí)看到的這句話: Starting Geth on Ethereum mainnet… 就是在 prepare()
函數(shù)中被打印出來的。
// prepare函數(shù)操作內(nèi)存緩存空間分配并設(shè)置矩陣系統(tǒng)。
// 這個(gè)函數(shù)應(yīng)該在啟動(dòng)devp2p棧之前被調(diào)用。(devp2p,dev就是開發(fā)的意思,p2p就是點(diǎn)到點(diǎn))
func prepare(ctx *cli.Context) {
// 如果我們正在運(yùn)行一個(gè)已知的預(yù)設(shè),為了方便起見記錄它。
switch {
case ctx.IsSet(utils.RopstenFlag.Name):
log.Info("Starting Geth on Ropsten testnet...")
case ctx.IsSet(utils.RinkebyFlag.Name):
log.Info("Starting Geth on Rinkeby testnet...")
case ctx.IsSet(utils.GoerliFlag.Name):
log.Info("Starting Geth on G?rli testnet...")
case ctx.IsSet(utils.SepoliaFlag.Name):
log.Info("Starting Geth on Sepolia testnet...")
case ctx.IsSet(utils.KilnFlag.Name):
log.Info("Starting Geth on Kiln testnet...")
case ctx.IsSet(utils.DeveloperFlag.Name):
log.Info("Starting Geth in ephemeral dev mode...")
log.Warn(`You are running Geth in --dev mode. Please note the following:
1. 此模式僅用于快速的迭代開發(fā),沒有安全性或持久性的考慮。
2. 除非另有說明,否則數(shù)據(jù)庫將在內(nèi)存中創(chuàng)建。因此,關(guān)閉計(jì)算機(jī)或斷電將擦除開發(fā)環(huán)境中的整個(gè)區(qū)塊數(shù)據(jù)和鏈狀態(tài)。
3. 一個(gè)隨機(jī)的、預(yù)先分配的開發(fā)者賬戶將可用并解鎖為eth.Coinbase,可用于測試。隨機(jī)的dev帳戶是臨時(shí)的,存儲(chǔ)在一個(gè)ramdisk硬盤上,如果你的機(jī)器重新啟動(dòng),這個(gè)帳戶就會(huì)丟失。
4. 默認(rèn)開啟挖掘。但是,只有在mempool(內(nèi)存池)中有待處理的事務(wù)時(shí),客戶端才會(huì)密封塊。該礦工接受的最低汽油價(jià)格是1。
5. 禁用網(wǎng)絡(luò);沒有l(wèi)isten-address(監(jiān)聽地址),最大對(duì)等體數(shù)設(shè)置為0,發(fā)現(xiàn)功能未開啟。
`)
case !ctx.IsSet(utils.NetworkIdFlag.Name):
log.Info("Starting Geth on Ethereum mainnet...")
}
// 如果我們是主網(wǎng)上沒有指定緩存的完整節(jié)點(diǎn),則取消默認(rèn)緩存配額
if ctx.String(utils.SyncModeFlag.Name) != "light" && !ctx.IsSet(utils.CacheFlag.Name) && !ctx.IsSet(utils.NetworkIdFlag.Name) {
// 確保我們也不在任何受支持的預(yù)配置testnet測試網(wǎng)絡(luò)上
if !ctx.IsSet(utils.RopstenFlag.Name) &&
!ctx.IsSet(utils.SepoliaFlag.Name) &&
!ctx.IsSet(utils.RinkebyFlag.Name) &&
!ctx.IsSet(utils.GoerliFlag.Name) &&
!ctx.IsSet(utils.KilnFlag.Name) &&
!ctx.IsSet(utils.DeveloperFlag.Name) {
// 不,我們真的在主網(wǎng)上。提升緩存!
log.Info("Bumping default cache on mainnet", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 4096)
ctx.Set(utils.CacheFlag.Name, strconv.Itoa(4096))
}
}
// 如果我們?cè)谌魏尉W(wǎng)絡(luò)上運(yùn)行輕量客戶端,請(qǐng)將緩存降低到某個(gè)有意義的值
if ctx.String(utils.SyncModeFlag.Name) == "light" && !ctx.IsSet(utils.CacheFlag.Name) {
log.Info("Dropping default light client cache", "provided", ctx.Int(utils.CacheFlag.Name), "updated", 128)
ctx.Set(utils.CacheFlag.Name, strconv.Itoa(128))
}
// 如果有啟用則啟動(dòng)矩陣導(dǎo)出
utils.SetupMetrics(ctx)
// 啟動(dòng)系統(tǒng)運(yùn)行時(shí)矩陣收集
go metrics.CollectProcessMetrics(3 * time.Second)
}
prepare函數(shù)源碼在原文中沒有給出,因?yàn)樽髡哳A(yù)設(shè)大家會(huì)看源代碼,所以比較精簡,筆者在這里給出源代碼是為了方便大家流暢閱讀,后續(xù)的源碼中如果有“筆者附加源碼”的字樣,代表原文沒有引用的源碼,但是筆者為了方便大家閱讀而附加上去的,類似這樣的提示后文就不再說明了。
makeFullNode()
函數(shù)的實(shí)現(xiàn)位于 cmd/geth/config.go
文件中。它會(huì)將 Geth 啟動(dòng)時(shí)的命令的上下文加載到配置中,并生成 stack
和backend
這兩個(gè)實(shí)例。其中 stack
是一個(gè) Node 類型的實(shí)例,它是通過 makeFullNode()
函數(shù)調(diào)用 makeConfigNode()
函數(shù)來初始化的。Node
是 geth 生命周期中最頂級(jí)的實(shí)例,它負(fù)責(zé)管理節(jié)點(diǎn)中的 P2P Server, Http Server, Database 等業(yè)務(wù)非直接相關(guān)的高級(jí)抽象。關(guān)于 Node 類型的定義位于node/node.go
文件中。
// 筆者附加源碼
// makeFullNode加載geth配置并創(chuàng)建以太坊后端。
func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
stack, cfg := makeConfigNode(ctx)//這里的stack就是返回的Node節(jié)點(diǎn),cfg是config配置
//讀取配置
if ctx.IsSet(utils.OverrideTerminalTotalDifficulty.Name) {
cfg.Eth.OverrideTerminalTotalDifficulty = flags.GlobalBig(ctx, utils.OverrideTerminalTotalDifficulty.Name)
}
if ctx.IsSet(utils.OverrideTerminalTotalDifficultyPassed.Name) {
override := ctx.Bool(utils.OverrideTerminalTotalDifficultyPassed.Name)
cfg.Eth.OverrideTerminalTotalDifficultyPassed = &override
}
//根據(jù)節(jié)點(diǎn)和配置注冊(cè)以太坊服務(wù)
backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
// 警告用戶遷移,如果他們有一個(gè)遺留的冷凍格式。
if eth != nil && !ctx.IsSet(utils.IgnoreLegacyReceiptsFlag.Name) {
firstIdx := uint64(0)
// 侵入以加快對(duì)主網(wǎng)的檢查,因?yàn)槲覀冎赖谝粋€(gè)非空塊,創(chuàng)世區(qū)塊46147,這個(gè)編號(hào)說明以太坊為自己至少提前挖掘了46147個(gè)區(qū)塊。
ghash := rawdb.ReadCanonicalHash(eth.ChainDb(), 0)
if cfg.Eth.NetworkId == 1 && ghash == params.MainnetGenesisHash {
firstIdx = 46147
}
isLegacy, firstLegacy, err := dbHasLegacyReceipts(eth.ChainDb(), firstIdx)
if err != nil {
log.Error("Failed to check db for legacy receipts", "err", err)
} else if isLegacy {
stack.Close()
log.Error("Database has receipts with a legacy format", "firstLegacy", firstLegacy)
utils.Fatalf("Aborting. Please run `geth db freezer-migrate`.")
}
}
// 配置日志過濾器RPC API。
filterSystem := utils.RegisterFilterAPI(stack, backend, &cfg.Eth)
// 如果需要,配置GraphQL。
if ctx.IsSet(utils.GraphQLEnabledFlag.Name) {
utils.RegisterGraphQLService(stack, backend, filterSystem, &cfg.Node)
}
// 如果需要,添加以太坊統(tǒng)計(jì)守護(hù)進(jìn)程。
if cfg.Ethstats.URL != "" {
utils.RegisterEthStatsService(stack, backend, cfg.Ethstats.URL)
}
return stack, backend
}
這里的 backend
是一個(gè) ethapi.Backend
類型的接口,提供了獲取以太坊執(zhí)行層運(yùn)行時(shí),所需要的基本函數(shù)功能。它的定義位于 internal/ethapi/backend.go
中。 由于這個(gè)接口中函數(shù)較多,我們選取了其中的部分關(guān)鍵函數(shù)方便大家理解這個(gè)接口所提供的基本功能,如下所示。
// 筆者更新源碼且附注中文注解
// 后端接口提供公共API服務(wù)(由完全客戶端和輕量級(jí)客戶端提供)以訪問必要的功能。
type Backend interface {
// 以太坊通用API
SyncProgress() ethereum.SyncProgress//同步進(jìn)度
SuggestGasTipCap(ctx context.Context) (*big.Int, error)
FeeHistory(ctx context.Context, blockCount int, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error)
ChainDb() ethdb.Database
AccountManager() *accounts.Manager
ExtRPCEnabled() bool
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
RPCTxFeeCap() float64 // global tx fee cap for all transaction related APIs
UnprotectedAllowed() bool // allows only for EIP155 transactions.
// 區(qū)塊鏈API
SetHead(number uint64)
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error)
CurrentHeader() *types.Header
CurrentBlock() *types.Block
BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error)
BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error)
BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error)
StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error)
StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error)
PendingBlockAndReceipts() (*types.Block, types.Receipts)
GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error)
GetTd(ctx context.Context, hash common.Hash) *big.Int
GetEVM(ctx context.Context, msg core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config) (*vm.EVM, func() error, error)
SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
// 交易池API
SendTx(ctx context.Context, signedTx *types.Transaction) error
GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error)
GetPoolTransactions() (types.Transactions, error)
GetPoolTransaction(txHash common.Hash) *types.Transaction
GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error)
Stats() (pending int, queued int)
TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions)
TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions)
SubscribeNewTxsEvent(chan<- core.NewTxsEvent) event.Subscription
ChainConfig() *params.ChainConfig
Engine() consensus.Engine
// 這里是從filters.Backend復(fù)制的
// eth/filters 需要從這個(gè)后端類型初始化,所以它所需的方法也必須包含在這里。
GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error)
SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription
SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription
SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription
BloomStatus() (uint64, uint64)
ServiceFilter(ctx context.Context, session *bloombits.MatcherSession)
}
我們可以發(fā)現(xiàn) ethapi.Backend
接口主要對(duì)外提供了:
- General Ethereum APIs, 這些 General APIs 對(duì)外提供了查詢區(qū)塊鏈節(jié)點(diǎn)管理對(duì)象的接口,例如
ChainDb()
返回當(dāng)前節(jié)點(diǎn)的 DB 實(shí)例,AccountManager()
返回賬戶管理對(duì)象; - Blockchain 相關(guān)的 APIs, 例如鏈上數(shù)據(jù)的查詢(Block & Transaction),
CurrentHeader(), BlockByNumber(), GetTransaction()
; - Transaction Pool (交易緩存池)相關(guān)的APIs, 例如發(fā)送交易到本節(jié)點(diǎn)的 Transaction Pool, 以及查詢交易池中的 Transactions,
GetPoolTransaction
獲取交易池。
目前 Geth 代碼庫中,有兩個(gè) ethapi.Backend
接口的實(shí)現(xiàn),分別是:
- 位于
eth\api_backend
中的EthAPIBackend
(全節(jié)點(diǎn)) - 位于
les\api_backend
的LesApiBackend
(輕節(jié)點(diǎn))
顧名思義,EthAPIBackend
提供了針對(duì)全節(jié)點(diǎn)的 Backend API 服務(wù), 而 LesApiBackend
提供了輕節(jié)點(diǎn)的 Backend API 服務(wù)??偨Y(jié)的來說,如果讀者想定制一些新的 RPC API(遠(yuǎn)程過程調(diào)用接口),可以在 ethapi.Backend
接口中定義函數(shù),并給 EthAPIBackend
添加具體的實(shí)現(xiàn)。
讀者可能會(huì)發(fā)現(xiàn),ethapi.Backend
接口所提供的函數(shù)功能,主要讀寫本地的維護(hù)的數(shù)據(jù)結(jié)構(gòu)(i.e. Transaction Pool, Blockchain)的為主。那么作為一個(gè)有網(wǎng)絡(luò)連接的 Backend, 以太坊的 Backend 或者說 Node 是怎么管理以太坊執(zhí)行層節(jié)點(diǎn)的網(wǎng)絡(luò)連接,共識(shí)等功能模塊的呢?
我們深入 makeFullNode()
函數(shù)可以發(fā)現(xiàn),生成ethapi.Backend
接口的語句 backend, eth := utils.RegisterEthService(stack, &cfg.Eth)
, 還返回了另一個(gè) Ethereum
類型的實(shí)例 eth
。 這個(gè) Ethereum
類型才是以太坊節(jié)點(diǎn)數(shù)結(jié)構(gòu)中核心中的核心,它實(shí)現(xiàn)了以太坊全節(jié)點(diǎn)所需要的所有的 Service。它負(fù)責(zé)提供更為具體的以太坊的功能性 Service, 負(fù)責(zé)與以太坊業(yè)務(wù)直接相關(guān)的抽象,比如維護(hù) Blockchain 的更新,共識(shí)算法,從 P2P 網(wǎng)絡(luò)中同步區(qū)塊,同步P2P節(jié)點(diǎn)遠(yuǎn)端的交易并放到交易池中,等業(yè)務(wù)功能。我們會(huì)在后續(xù)詳細(xì)講解 Ethereum
類型具體提供的服務(wù)。
// 筆者附加源碼
// RegisterEthService將以太坊客戶端添加到棧中。
// 第二個(gè)返回值是完整的node實(shí)例,如果節(jié)點(diǎn)作為輕量客戶端運(yùn)行,這個(gè)值可能是nil。
func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend, *eth.Ethereum) {
//輕節(jié)點(diǎn)同步
if cfg.SyncMode == downloader.LightSync {
backend, err := les.New(stack, cfg)
if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err)
}
stack.RegisterAPIs(tracers.APIs(backend.ApiBackend))
if err := lescatalyst.Register(stack, backend); err != nil {
Fatalf("Failed to register the Engine API service: %v", err)
}
return backend.ApiBackend, nil
}
backend, err := eth.New(stack, cfg)
if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err)
}
if cfg.LightServ > 0 {
_, err := les.NewLesServer(stack, backend, cfg)
if err != nil {
Fatalf("Failed to create the LES server: %v", err)
}
}
if err := ethcatalyst.Register(stack, backend); err != nil {
Fatalf("Failed to register the Engine API service: %v", err)
}
stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
// 在同步目標(biāo)配置完成的情況下,注冊(cè)輔助的全同步測試服務(wù)。
if cfg.SyncTarget != nil && cfg.SyncMode == downloader.FullSync {
ethcatalyst.RegisterFullSyncTester(stack, backend, cfg.SyncTarget)
log.Info("Registered full-sync tester", "number", cfg.SyncTarget.NumberU64(), "hash", cfg.SyncTarget.Hash())
}
return backend.APIBackend, backend
}
Ethereum
實(shí)例根據(jù)上下文的配置信息在調(diào)用 utils.RegisterEthService()
函數(shù)生成。在utils.RegisterEthService()
函數(shù)中,首先會(huì)根據(jù)當(dāng)前的config來判斷需要生成的Ethereum backend 的類型,是 light node backend 還是 full node backend。我們可以在 eth/backend/new()
函數(shù)和 les/client.go/new()
中找到這兩種 Ethereum backend 的實(shí)例是如何初始化的。Ethereum backend 的實(shí)例定義了一些更底層的配置,比如chainid,鏈?zhǔn)褂玫墓沧R(shí)算法的類型等。這兩種后端服務(wù)的一個(gè)典型的區(qū)別是 light node backend 不能啟動(dòng) Mining 服務(wù)。在 utils.RegisterEthService()
函數(shù)的最后,調(diào)用了 Nodes.RegisterAPIs()
函數(shù),將剛剛生成的 backend 實(shí)例注冊(cè)到 stack
實(shí)例中。
總結(jié)的說,api_backend
主要是用于對(duì)外提供查詢,或者與后端功能性生命周期無關(guān)的函數(shù),Ethereum
這類的節(jié)點(diǎn)層的后端,主要用于管理/控制節(jié)點(diǎn)后端的生命周期。
最后一個(gè)關(guān)鍵函數(shù),startNode()
的作用是正式的啟動(dòng)一個(gè)以太坊執(zhí)行層的節(jié)點(diǎn)。它通過調(diào)用 utils.StartNode()
函數(shù)來觸發(fā) Node.Start()
函數(shù)來啟動(dòng)Stack
實(shí)例(Node)。在 Node.Start()
函數(shù)中,會(huì)遍歷 Node.lifecycles
中注冊(cè)的后端實(shí)例,并啟動(dòng)它們。此外,在 startNode()
函數(shù)中,還是調(diào)用了unlockAccounts()
函數(shù),并將解鎖的錢包注冊(cè)到 stack
中,以及通過 stack.Attach()
函數(shù)創(chuàng)建了與 local Geth 交互的 RPClient 模塊。
// 筆者附加源碼
// startNode啟動(dòng)系統(tǒng)節(jié)點(diǎn)和所有注冊(cè)的協(xié)議,之后它解鎖任何請(qǐng)求的帳戶,并啟動(dòng)RPC/IPC接口和礦工。
func startNode(ctx *cli.Context, stack *node.Node, backend ethapi.Backend, isConsole bool) {
debug.Memsize.Add("node", stack)
// 啟動(dòng)節(jié)點(diǎn)本身
utils.StartNode(ctx, stack, isConsole)
// 解鎖任何特定要求的賬戶
unlockAccounts(ctx, stack)
// 注冊(cè)錢包事件處理程序來打開和自動(dòng)導(dǎo)出錢包
events := make(chan accounts.WalletEvent, 16)
stack.AccountManager().Subscribe(events)
// 創(chuàng)建一個(gè)客戶端與本地geth節(jié)點(diǎn)交互。
rpcClient, err := stack.Attach()
if err != nil {
utils.Fatalf("Failed to attach to self: %v", err)
}
ethClient := ethclient.NewClient(rpcClient)
go func() {
// 打開任何已經(jīng)連接的錢包
for _, wallet := range stack.AccountManager().Wallets() {
if err := wallet.Open(""); err != nil {
log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)
}
}
// 監(jiān)聽錢包事件直到終止
for event := range events {
switch event.Kind {
case accounts.WalletArrived:
if err := event.Wallet.Open(""); err != nil {
log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)
}
case accounts.WalletOpened:
status, _ := event.Wallet.Status()
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
var derivationPaths []accounts.DerivationPath
if event.Wallet.URL().Scheme == "ledger" {
derivationPaths = append(derivationPaths, accounts.LegacyLedgerBaseDerivationPath)
}
derivationPaths = append(derivationPaths, accounts.DefaultBaseDerivationPath)
event.Wallet.SelfDerive(derivationPaths, ethClient)
case accounts.WalletDropped:
log.Info("Old wallet dropped", "url", event.Wallet.URL())
event.Wallet.Close()
}
}
}()
// 生成一個(gè)獨(dú)立的goroutine用于狀態(tài)同步監(jiān)控,如果用戶需要,同步完成后關(guān)閉節(jié)點(diǎn)。
if ctx.Bool(utils.ExitWhenSyncedFlag.Name) {
go func() {
sub := stack.EventMux().Subscribe(downloader.DoneEvent{})
defer sub.Unsubscribe()
for {
event := <-sub.Chan()
if event == nil {
continue
}
done, ok := event.Data.(downloader.DoneEvent)
if !ok {
continue
}
if timestamp := time.Unix(int64(done.Latest.Time), 0); time.Since(timestamp) < 10*time.Minute {
log.Info("Synchronisation completed", "latestnum", done.Latest.Number, "latesthash", done.Latest.Hash(),
"age", common.PrettyAge(timestamp))
stack.Close()
}
}
}()
}
// 啟動(dòng)輔助服務(wù)(如果啟用)
if ctx.Bool(utils.MiningEnabledFlag.Name) || ctx.Bool(utils.DeveloperFlag.Name) {
// 只有在完整的以太坊節(jié)點(diǎn)運(yùn)行時(shí),挖礦才有意義
if ctx.String(utils.SyncModeFlag.Name) == "light" {
utils.Fatalf("Light clients do not support mining")
}
ethBackend, ok := backend.(*eth.EthAPIBackend)
if !ok {
utils.Fatalf("Ethereum service not running")
}
// 通過CLI將汽油價(jià)格設(shè)置為限制,然后開始挖礦
gasprice := flags.GlobalBig(ctx, utils.MinerGasPriceFlag.Name)
ethBackend.TxPool().SetGasPrice(gasprice)
// 開始挖礦
threads := ctx.Int(utils.MinerThreadsFlag.Name)
if err := ethBackend.StartMining(threads); err != nil {
utils.Fatalf("Failed to start mining: %v", err)
}
}
}
在 geth()
函數(shù)的最后,函數(shù)通過執(zhí)行 stack.Wait()
,使得主線程進(jìn)入了阻塞狀態(tài),其他的功能模塊的服務(wù)被分散到其他的子協(xié)程中進(jìn)行維護(hù)。
Node
正如我們前面提到的,Node 類型在 geth 的生命周期性中屬于頂級(jí)實(shí)例,它負(fù)責(zé)作為與外部通信的高級(jí)抽象模塊的管理員,比如管理 rpc server,http server,Web Socket,以及P2P Server外部接口。同時(shí),Node中維護(hù)了節(jié)點(diǎn)運(yùn)行所需要的后端的實(shí)例和服務(wù) (lifecycles []Lifecycle
),例如我們上面提到的負(fù)責(zé)具體 Service 的Ethereum
類型。
// Node節(jié)點(diǎn)是一個(gè)可以注冊(cè)服務(wù)的容器。
type Node struct {
eventmux *event.TypeMux
config *Config
accman *accounts.Manager
log log.Logger
keyDir string // 密鑰存儲(chǔ)目錄
keyDirTemp bool // 如果為true,將通過Stop函數(shù)刪除密鑰目錄,因?yàn)橹皇桥R時(shí)目錄
dirLock fileutil.Releaser // 阻止并發(fā)使用實(shí)例目錄
stop chan struct{} // 等待終止通知的通道
server *p2p.Server // 目前運(yùn)行的P2P網(wǎng)絡(luò)層
startStopLock sync.Mutex // Start/Stop函數(shù)由一個(gè)額外的互斥鎖保護(hù)
state int // 跟蹤節(jié)點(diǎn)的生命周期狀態(tài)
lock sync.Mutex
lifecycles []Lifecycle // 所有具有生命周期的注冊(cè)后端、服務(wù)和輔助服務(wù)
rpcAPIs []rpc.API // 節(jié)點(diǎn)當(dāng)前提供的api列表
http *httpServer //
ws *httpServer //
httpAuth *httpServer //
wsAuth *httpServer //
ipc *ipcServer // 存儲(chǔ)ipc http服務(wù)器信息
inprocHandler *rpc.Server // 進(jìn)程內(nèi)RPC請(qǐng)求處理程序來處理API請(qǐng)求
databases map[*closeTrackingDB]struct{} // 所有開放數(shù)據(jù)庫
}
Node的關(guān)閉
在前面我們提到,整個(gè)程序的主線程因?yàn)檎{(diào)用了 stack.Wait()
而進(jìn)入了阻塞狀態(tài)。我們可以看到 Node 結(jié)構(gòu)中聲明了一個(gè)叫做 stop
的 channel。由于這個(gè) Channel 一直沒有被賦值,所以整個(gè) geth 的主進(jìn)程才進(jìn)入了阻塞狀態(tài),持續(xù)并發(fā)的執(zhí)行其他的業(yè)務(wù)協(xié)程。
// Wait函數(shù)阻塞,直到節(jié)點(diǎn)關(guān)閉。
func (n *Node) Wait() {
<-n.stop
}
當(dāng) n.stop
這個(gè) Channel 被賦予值的時(shí)候,geth
主函數(shù)就會(huì)停止當(dāng)前的阻塞狀態(tài),并開始執(zhí)行相應(yīng)的一系列的資源釋放的操作。這個(gè)地方的寫法還是非常有意思的,值得我們參考。
值得注意的是,在目前的 go-ethereum 的 codebase 中,并沒有直接通過給 stop
這個(gè) channel 賦值方式來結(jié)束主進(jìn)程的阻塞狀態(tài),而是使用一種更簡潔粗暴的方式: 調(diào)用 close()
函數(shù)直接關(guān)閉 Channel。我們可以在 node.doClose()
找到相關(guān)的實(shí)現(xiàn)。close()
是go語言的原生函數(shù),用于關(guān)閉 Channel 時(shí)使用。
// doClose釋放New()獲取的資源,并且收集錯(cuò)誤。
func (n *Node) doClose(errs []error) error {
// 關(guān)閉數(shù)據(jù)庫。這個(gè)操作需要鎖,因?yàn)樗枰cOpenDatabase*同步。
n.lock.Lock()
n.state = closedState
errs = append(errs, n.closeDatabases()...)
n.lock.Unlock()
//關(guān)閉賬戶管理器
if err := n.accman.Close(); err != nil {
errs = append(errs, err)
}
//如果是臨時(shí)目錄,則全部刪除
if n.keyDirTemp {
if err := os.RemoveAll(n.keyDir); err != nil {
errs = append(errs, err)
}
}
// 釋放實(shí)例目錄鎖。
n.closeDataDir()
// 解鎖n.Wait.這樣就可以解除Wait造成的阻塞
close(n.stop)
// 報(bào)告可能發(fā)生的任何錯(cuò)誤。
switch len(errs) {
case 0:
return nil
case 1:
return errs[0]
default:
return fmt.Errorf("%v", errs)
}
}
Ethereum Backend
我們可以在 eth/backend.go
中找到 Ethereum
這個(gè)結(jié)構(gòu)體的定義。這個(gè)結(jié)構(gòu)體包含的成員變量以及接收的方法實(shí)現(xiàn)了一個(gè) Ethereum full node 所需要的全部功能和數(shù)據(jù)結(jié)構(gòu)。我們可以在下面的代碼定義中看到,Ethereum結(jié)構(gòu)體中包含 TxPool
,Blockchain
,consensus.Engine
,miner
等最核心的幾個(gè)數(shù)據(jù)結(jié)構(gòu)作為成員變量,我們會(huì)在后面的章節(jié)中詳細(xì)的講述這些核心數(shù)據(jù)結(jié)構(gòu)的主要功能,以及它們的實(shí)現(xiàn)的方法。
// Ethereum 實(shí)現(xiàn)了以太坊全節(jié)點(diǎn)服務(wù).
type Ethereum struct {
config *ethconfig.Config
// 處理器
txPool *txpool.TxPool
blockchain *core.BlockChain
handler *handler // handler 是P2P 網(wǎng)絡(luò)數(shù)據(jù)同步的核心實(shí)例,我們會(huì)在后續(xù)的網(wǎng)絡(luò)同步模塊仔細(xì)的講解它的功能
ethDialCandidates enode.Iterator
snapDialCandidates enode.Iterator
merger *consensus.Merger
// 數(shù)據(jù)庫接口
chainDb ethdb.Database // 區(qū)塊鏈數(shù)據(jù)庫
eventMux *event.TypeMux
engine consensus.Engine
accountManager *accounts.Manager
bloomRequests chan chan *bloombits.Retrieval // 接收bloom data數(shù)據(jù)檢索請(qǐng)求的通道
bloomIndexer *core.ChainIndexer // Bloom索引器在塊導(dǎo)入期間運(yùn)行
closeBloomHandler chan struct{}
APIBackend *EthAPIBackend
miner *miner.Miner
gasPrice *big.Int
etherbase common.Address
networkID uint64
netRPCService *ethapi.NetAPI
p2pServer *p2p.Server
lock sync.RWMutex // 讀寫互斥鎖,保護(hù)可變字段(例如汽油價(jià)格和etherbase)
shutdownTracker *shutdowncheck.ShutdownTracker // 跟蹤節(jié)點(diǎn)是否以及何時(shí)非正常關(guān)閉
}
節(jié)點(diǎn)啟動(dòng)和停止 Mining 的就是通過調(diào)用 Ethereum.StartMining()
和 Ethereum.StopMining()
實(shí)現(xiàn)的。設(shè)置 Mining 的收益賬戶是通過調(diào)用 Ethereum.SetEtherbase()
實(shí)現(xiàn)的。
// 筆者更新源碼和注解
// StartMining使用給定的CPU線程數(shù)啟動(dòng)礦工。如果挖掘已經(jīng)在運(yùn)行,該方法會(huì)調(diào)整允許使用的線程數(shù),并更新交易池所需的最低價(jià)格。
func (s *Ethereum) StartMining(threads int) error {
// 更新共識(shí)引擎中的線程數(shù)
type threaded interface {
SetThreads(threads int)
}
if th, ok := s.engine.(threaded); ok {
log.Info("Updated mining threads", "threads", threads)
if threads == 0 {
threads = -1 // 從內(nèi)部禁用礦工
}
th.SetThreads(threads)
}
// 如果礦工沒有運(yùn)行,初始化它
if !s.IsMining() {
// 將初始價(jià)格點(diǎn)傳播到交易池
s.lock.RLock()
price := s.gasPrice
s.lock.RUnlock()
s.txPool.SetGasPrice(price)
// 配置本地挖掘地址
eb, err := s.Etherbase()
if err != nil {
log.Error("Cannot start mining without etherbase", "err", err)
return fmt.Errorf("etherbase missing: %v", err)
}
var cli *clique.Clique
if c, ok := s.engine.(*clique.Clique); ok {
cli = c
} else if cl, ok := s.engine.(*beacon.Beacon); ok {
if c, ok := cl.InnerEngine().(*clique.Clique); ok {
cli = c
}
}
if cli != nil {
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
if wallet == nil || err != nil {
log.Error("Etherbase account unavailable locally", "err", err)
return fmt.Errorf("signer missing: %v", err)
}
cli.Authorize(eb, wallet.SignData)
}
// 如果開始挖掘,我們可以禁用為加快同步時(shí)間而引入的事務(wù)拒絕機(jī)制。
atomic.StoreUint32(&s.handler.acceptTxs, 1)
go s.miner.Start(eb)
}
return nil
}
這里我們額外關(guān)注一下 handler
這個(gè)成員變量。handler
的定義在 eth/handler.go
中。
我們從從宏觀角度來看,一個(gè)節(jié)點(diǎn)的主工作流需要:
1.從網(wǎng)絡(luò)中獲取/同步 Transaction 和 Block 的數(shù)據(jù)
2. 將網(wǎng)絡(luò)中獲取到 Block 添加到 Blockchain 中。
而 handler
就負(fù)責(zé)提供其中同步區(qū)塊和交易數(shù)據(jù)的功能,例如,downloader.Downloader
負(fù)責(zé)從網(wǎng)絡(luò)中同步 Block ,fetcher.TxFetcher
負(fù)責(zé)從網(wǎng)絡(luò)中同步交易。關(guān)于這些方法的具體實(shí)現(xiàn),我們會(huì)在后續(xù)章節(jié):數(shù)據(jù)同步中詳細(xì)介紹。
type handler struct {
networkID uint64
forkFilter forkid.Filter // Fork ID過濾器,在節(jié)點(diǎn)的生命周期中保持不變
snapSync uint32 // 標(biāo)志是否啟用snap sync(如果我們已經(jīng)有數(shù)據(jù)塊,則禁用)
acceptTxs uint32 // 標(biāo)志我們是否被認(rèn)為是同步的(啟用事務(wù)處理)
checkpointNumber uint64 // 同步進(jìn)度驗(yàn)證器要交叉引用的塊號(hào)
checkpointHash common.Hash // 同步進(jìn)度驗(yàn)證器用于交叉引用的塊哈希值
database ethdb.Database
txpool txPool
chain *core.BlockChain
maxPeers int
downloader *downloader.Downloader
blockFetcher *fetcher.BlockFetcher
txFetcher *fetcher.TxFetcher
peers *peerSet
merger *consensus.Merger
eventMux *event.TypeMux
txsCh chan core.NewTxsEvent
txsSub event.Subscription
minedBlockSub *event.TypeMuxSubscription
requiredBlocks map[uint64]common.Hash
// 用于獲取器,同步器,txsyncLoop的通道
quitSync chan struct{}
chainSync *chainSyncer
wg sync.WaitGroup
peerWG sync.WaitGroup
}
到此,我們就介紹了 geth 及其所需要的基本模塊是如何啟動(dòng)的和關(guān)閉的。我們?cè)诮酉聛韺⒁暯寝D(zhuǎn)入到各個(gè)模塊中,從更細(xì)粒度的角度深入探索 Ethereum 的具體實(shí)現(xiàn)。文章來源:http://www.zghlxwxcb.cn/news/detail-619778.html
附錄
這里補(bǔ)充一個(gè)Go語言的語法知識(shí): 類型斷言。在Ethereum.StartMining()
函數(shù)中,出現(xiàn)了if c, ok := s.engine.(*clique.Clique); ok
的寫法。該寫法是Golang中的語法糖,稱為類型斷言。具體的語法是value, ok := element.(T)
,它的含義是如果element
是T
類型的話,那么ok等于True
, value
等于element
的值。在if c, ok := s.engine.(*clique.Clique); ok
語句中,就是在判斷s.engine
的是否為*clique.Clique
類型。文章來源地址http://www.zghlxwxcb.cn/news/detail-619778.html
var cli *clique.Clique
if c, ok := s.engine.(*clique.Clique); ok {
cli = c
} else if cl, ok := s.engine.(*beacon.Beacon); ok {
if c, ok := cl.InnerEngine().(*clique.Clique); ok {
cli = c
}
}
到了這里,關(guān)于Go Ethereum源碼學(xué)習(xí)筆記 001 Geth Start的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!