国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Go Ethereum源碼學(xué)習(xí)筆記 001 Geth Start

這篇具有很好參考價(jià)值的文章主要介紹了Go Ethereum源碼學(xué)習(xí)筆記 001 Geth Start。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

前言

首先讀者需要具備Go語言基礎(chǔ),至少要通關(guān)菜鳥教程,知道Go語言的基本語法,這些基礎(chǔ)教程網(wǎng)絡(luò)上非常多,請(qǐng)大家自行學(xué)習(xí)。
具備語言基礎(chǔ)了,還需要在開始這一章之前做一些準(zhǔn)備工作:

  1. 安裝Go SDK,即Go語言的開發(fā)環(huán)境;
  2. 安裝GoLand,即Go語言的IDE,當(dāng)然也可以選擇VSCode等其他IDE;
  3. 克隆 Go Ethereum源碼;
  4. 克隆Understanding-Ethereum-Go-version源碼(可選);
  5. 以太坊基礎(chǔ)知識(shí),Ethereum 協(xié)議(黃皮書 )
    做好這些準(zhǔn)備工作,就可以打開 Go Ethereum源碼了,如下圖所示:
    Go Ethereum源碼學(xué)習(xí)筆記 001 Geth Start,Go Ethereum學(xué)習(xí)筆記,golang,學(xué)習(xí),go,區(qū)塊鏈
    以太坊是以區(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

本章概要:

  1. go-ethereum 代碼庫的主要目錄結(jié)構(gòu)。
  2. geth 客戶端/節(jié)點(diǎn)是如何啟動(dòng)的。
  3. 如何修改/添加 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é)的來說:

  1. 基于 go-ethereum 代碼庫中的代碼,我們可以編譯出 geth 客戶端程序。
  2. 通過運(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ù)
  • 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í)的命令的上下文加載到配置中,并生成 stackbackend 這兩個(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ì)外提供了:

  1. General Ethereum APIs, 這些 General APIs 對(duì)外提供了查詢區(qū)塊鏈節(jié)點(diǎn)管理對(duì)象的接口,例如 ChainDb() 返回當(dāng)前節(jié)點(diǎn)的 DB 實(shí)例, AccountManager()返回賬戶管理對(duì)象;
  2. Blockchain 相關(guān)的 APIs, 例如鏈上數(shù)據(jù)的查詢(Block & Transaction), CurrentHeader(), BlockByNumber(), GetTransaction();
  3. 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_backendLesApiBackend(輕節(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

Go Ethereum源碼學(xué)習(xí)筆記 001 Geth Start,Go Ethereum學(xué)習(xí)筆記,golang,學(xué)習(xí),go,區(qū)塊鏈

正如我們前面提到的,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)體中包含 TxPoolBlockchain,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)。

附錄

這里補(bǔ)充一個(gè)Go語言的語法知識(shí): 類型斷言。在Ethereum.StartMining()函數(shù)中,出現(xiàn)了if c, ok := s.engine.(*clique.Clique); ok的寫法。該寫法是Golang中的語法糖,稱為類型斷言。具體的語法是value, ok := element.(T),它的含義是如果elementT類型的話,那么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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • FasterTransformer 001 start up

    FasterTransformer 001 start up

    Faster Transformer是一個(gè)Transformer單層前向計(jì)算的高效實(shí)現(xiàn)。一個(gè)函數(shù)由多個(gè)OP組合實(shí)現(xiàn)。每一個(gè)基本OP都會(huì)對(duì)應(yīng)一次GPU kernel的調(diào)用,和多次顯存讀寫。OP融合可以降低GPU調(diào)度和顯存讀寫,進(jìn)而提升性能。在Faster Transformer,作者將除矩陣乘法以外的所有kernel都進(jìn)行了盡可能的融合。

    2024年02月09日
    瀏覽(12)
  • C++學(xué)習(xí)筆記——001

    C++學(xué)習(xí)筆記——001

    C++ 是一種靜態(tài)類型的、編譯式的、通用的、大小寫敏感的、不規(guī)則的編程語言,支持過程化編程、面向?qū)ο缶幊毯头盒途幊獭++ 是 C 的一個(gè)超集,事實(shí)上,任何合法的 C 程序都是合法的 C++ 程序。 注意: 使用靜態(tài)類型的編程語言是在編譯時(shí)執(zhí)行類型檢查,而不是在運(yùn)行時(shí)執(zhí)

    2024年03月16日
    瀏覽(21)
  • C# 學(xué)習(xí)筆記-001-繼承

    ? ? ? ? ?實(shí)現(xiàn)繼承: ? ? ? ? ? ? ? ? ? ? ? ?表示一個(gè)類型派生于一個(gè)基類型,擁有改類型的所有成員字段和函數(shù)。 ? ? ? ? ?接口繼承: ? ? ? ? ? ? ? ? ? ? ? ? 表示一個(gè)類型只繼承了函數(shù)函數(shù)簽名,沒有繼承任何實(shí)現(xiàn)代碼。 ????????虛方法: ? ? ? ? ? ? ? ?

    2024年01月20日
    瀏覽(13)
  • 小土堆pytorch學(xué)習(xí)筆記001

    小土堆pytorch學(xué)習(xí)筆記001

    目錄 1、Pytorch環(huán)境的配置與安裝。 2、pytorch編輯器的選擇 (1)pycharm (下載社區(qū)版) (2)jupyter (可以交互) 3、為什么torch.cuda.is_available()返回False 4、python學(xué)習(xí)中的兩大法寶函數(shù) (1)dir() 函數(shù):打開、看見 (2)help()函數(shù):說明書 5、Pycharm 及Jupyter的使用對(duì)比: (1)Pych

    2024年01月25日
    瀏覽(17)
  • golang通過go-git下載gitlab源碼

    golang通過go-git下載gitlab源碼

    1 申請(qǐng)令牌 方法1:具體項(xiàng)目下申請(qǐng): 方法2:全局申請(qǐng) 2 獲取token 3 下載代碼 替換下面: username token 參考: https://docs.gitlab.cn/jh/user/profile/personal_access_tokens.html

    2024年01月24日
    瀏覽(26)
  • Go For Web:Golang http 包詳解(源碼剖析)

    Go For Web:Golang http 包詳解(源碼剖析)

    本文作為解決如何通過 Golang 來編寫 Web 應(yīng)用這個(gè)問題的前瞻,對(duì) Golang 中的 Web 基礎(chǔ)部分進(jìn)行一個(gè)簡單的介紹。目前 Go 擁有成熟的 Http 處理包,所以我們?nèi)ゾ帉懸粋€(gè)做任何事情的動(dòng)態(tài) Web 程序應(yīng)該是很輕松的,接下來我們就去學(xué)習(xí)了解一些關(guān)于 Web 的相關(guān)基礎(chǔ),了解一些概念,

    2023年04月14日
    瀏覽(23)
  • go 筆記 第七章 golang 的函數(shù) func 方法

    聲明函數(shù) func 函數(shù)名(入?yún)? 類型, 入?yún)? 類型,… )(出參1 類型, 出參2 類型…){ 函數(shù)體,寫邏輯 出參一定要全部 return, return 出參 } 函數(shù)內(nèi)部不可以聲明帶名字的函數(shù),可以聲明匿名函數(shù)和自執(zhí)行函數(shù) 函數(shù)名大寫可以被其他包調(diào)用,小寫私有,變量名也是一樣 return 后面可以不

    2024年02月15日
    瀏覽(24)
  • Golang掃盲式學(xué)習(xí)——GO并發(fā) | (一)

    Golang掃盲式學(xué)習(xí)——GO并發(fā) | (一)

    并行:同一個(gè)時(shí)間段內(nèi)多個(gè)任務(wù)同時(shí)在不同的CPU核心上執(zhí)行。強(qiáng)調(diào)同一時(shí)刻多個(gè)任務(wù)之間的” 同時(shí)執(zhí)行 “。 并發(fā):同一個(gè)時(shí)間段內(nèi)多個(gè)任務(wù)都在進(jìn)展。強(qiáng)調(diào)多個(gè)任務(wù)間的” 交替執(zhí)行 “。 隨著硬件水平的提高,現(xiàn)在的終端主機(jī)都是多個(gè)CPU,每個(gè)CPU都是多核結(jié)構(gòu)。當(dāng)多個(gè)CPU同

    2024年02月07日
    瀏覽(21)
  • 以太坊創(chuàng)建私有鏈 go-ethereum

    以太坊創(chuàng)建私有鏈 go-ethereum

    目錄 啟動(dòng)節(jié)點(diǎn)同步 同步主網(wǎng)區(qū)塊 同步測試網(wǎng)絡(luò)的區(qū)塊 同步Ropsten測試網(wǎng)絡(luò)的區(qū)塊 ?同步RinkeyBy測試網(wǎng)絡(luò)區(qū)塊 搭建自己的私有鏈? 創(chuàng)建genesis.json init初始化gensis.json? 啟動(dòng)私鏈 安裝好了Geth,現(xiàn)在我們可以嘗試運(yùn)行以下它。執(zhí)行下面的命令,geth就會(huì)開始同步區(qū)塊,并存儲(chǔ)在當(dāng)前

    2024年02月08日
    瀏覽(45)
  • 【區(qū)塊鏈 | 智能合約】Ethereum源代碼(2)- go-ethereum 客戶端入口代碼和Node分析

    上篇提到用 make geth 來編譯geth客戶端。我們來看看make file做了什么: 執(zhí)行了 ci.go 里面做了兩件事情 1,ln -s命令在build/_workspace/ 目錄上生成了go-etherum的一個(gè)文件鏡像,不占用磁盤空間,與源文件同步更新 2

    2024年02月03日
    瀏覽(29)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包