1. 程式人生 > >以太坊原始碼深入分析(2)-- go-ethereum 客戶端入口和Node分析

以太坊原始碼深入分析(2)-- go-ethereum 客戶端入口和Node分析

一,geth makefile 以及編譯邏輯

上篇提到用 make geth 來編譯geth客戶端。我們來看看make file做了什麼:

  geth:

  build/env.sh go run build/ci.go install ./cmd/geth

  @echo "Done building."

  @echo "Run \"$(GOBIN)/geth\" to launch geth."

執行了env.sh

# Create fake Go workspace if it doesn'texist yet.

workspace="$PWD/build/_workspace"

root="$PWD"

ethdir="$workspace/src/github.com/ethereum"

if [ ! -L "$ethdir/go-ethereum"]; then

   mkdir -p "$ethdir"

   cd "$ethdir"

   ln -s ../../../../../. go-ethereum

   cd "$root"

fi

# Set up the environment to use theworkspace.

GOPATH="$workspace"

export GOPATH

 

# Run the command inside the workspace.

cd "$ethdir/go-ethereum"

PWD="$ethdir/go-ethereum"

裡面做了兩件事情

1,ln -s命令在build/_workspace/ 目錄上生成了go-etherum的一個檔案映象,不佔用磁碟空間,與原始檔同步更新

2,把工作目錄 workspace加入GOPATH環境變數

跟蹤進ci.go 關鍵函式

 func doInstall(cmdline []string)

這個方法拼接了完整的geth 編譯命令:

go install -ldflags -X main.gitCommit=722bac84fa503199b9c485c1a2bfba03bc487d -v ./cmd/geth

二,geth 客戶端main函式以及geth的啟動入口

geth啟動命令:

build/bin/geth --datadir =./data/00 --networkid 1 --fast --cache = 1024 --etherbase“[yourpreferred account]”console >>geth.log

--datadir設定區塊鏈資料存放路徑

--networkid 網路設定啟動的區塊鏈網路預設值是1表示以太坊公司,0,2,3表示測試網路,大於4表示本地私有網路

--fast同步方式,預設為fast要選擇完全同步使用命令--syncmode full

--cache設定快取大小(最小16MB /資料庫強制)(預設值:128)

console表示啟動控制檯>>geth.log將控制檯顯示內容輸出到檔案geth.log中去

geth的原始碼入口main()函式在/cmd/geth/main.go 

func main() {
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

// geth is the main entry point into the system if no special subcommand is ran.
// It creates a default node based on the command line arguments and runs it in
// blocking mode, waiting for it to be shut down.
func geth(ctx *cli.Context) error {
	node := makeFullNode(ctx)
	startNode(ctx, node)
	node.Wait()
	return nil
}

main.go/main() -> app.go/Run() - > app.go/HandleAction() - > main.go/geth()

通過這幾個函式跳轉來到了GETH啟動入口。

 從啟動入口可以看到第一個啟動的模組是node模組,通過makeFullNode函式來建立一個節點物件(在ETH裡node可以認為是以太坊全網的一個節點,也可以認為是一個以太坊終端)。

func makeFullNode(ctx *cli.Context) *node.Node {
	stack, cfg := makeConfigNode(ctx)

	utils.RegisterEthService(stack, &cfg.Eth)

	if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
		utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
	}
	// Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
	shhEnabled := enableWhisper(ctx)
	shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DeveloperFlag.Name)
	if shhEnabled || shhAutoEnabled {
		if ctx.GlobalIsSet(utils.WhisperMaxMessageSizeFlag.Name) {
			cfg.Shh.MaxMessageSize = uint32(ctx.Int(utils.WhisperMaxMessageSizeFlag.Name))
		}
		if ctx.GlobalIsSet(utils.WhisperMinPOWFlag.Name) {
			cfg.Shh.MinimumAcceptedPOW = ctx.Float64(utils.WhisperMinPOWFlag.Name)
		}
		utils.RegisterShhService(stack, &cfg.Shh)
	}

	// Add the Ethereum Stats daemon if requested.
	if cfg.Ethstats.URL != "" {
		utils.RegisterEthStatsService(stack, cfg.Ethstats.URL)
	}
	return stack
}

進入 makeConfigNode()方法 

func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
	// Load defaults.
	cfg := gethConfig{
		Eth:       eth.DefaultConfig,
		Shh:       whisper.DefaultConfig,
		Node:      defaultNodeConfig(),
		Dashboard: dashboard.DefaultConfig,
	}

	// Load config file.
	if file := ctx.GlobalString(configFileFlag.Name); file != "" {
		if err := loadConfig(file, &cfg); err != nil {
			utils.Fatalf("%v", err)
		}
	}

	// Apply flags.
	utils.SetNodeConfig(ctx, &cfg.Node)
	stack, err := node.New(&cfg.Node)
	if err != nil {
		utils.Fatalf("Failed to create the protocol stack: %v", err)
	}
	utils.SetEthConfig(ctx, stack, &cfg.Eth)
	if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) {
		cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name)
	}

	utils.SetShhConfig(ctx, stack, &cfg.Shh)
	utils.SetDashboardConfig(ctx, &cfg.Dashboard)

	return stack, cfg
}

makeConfigNode做了兩件事1,函式獲取以太坊相關服務(Eth Node Shh DashBoard)的預設配置 2,通過Node的預設配置來建立一個Node。返回node物件和cfg

 makeFullNode把建立ethservice、建立DashBoard service、建立Shhservice以及建立ethStats service註冊到Node

從這裡我們可以看出來eth DashBoard Shh ethStats都是從node.Service介面派生出來的,它們的例項化物件需要實現node.Service所有介面,這在以後相關模組的分析中會遇到

type Service interface {
	// Protocols retrieves the P2P protocols the service wishes to start.
	Protocols() []p2p.Protocol

	// APIs retrieves the list of RPC descriptors the service provides
	APIs() []rpc.API

	// Start is called after all services have been constructed and the networking
	// layer was also initialized to spawn any goroutines required by the service.
	Start(server *p2p.Server) error

	// Stop terminates all goroutines belonging to the service, blocking until they
	// are all terminated.
	Stop() error
}

Protocols() 返回service要啟動的P2P 協議列表

APIs() 返回service提供的RPC介面

Start() 啟動已經初始化的service

Stop() 停止service所有的goroutines,並阻塞執行緒知道所有goroutines都終止

接下來呼叫startNode來啟動

// startNode boots up the system node and all registered protocols, after which
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
// miner.
func startNode(ctx *cli.Context, stack *node.Node) {
	// Start up the node itself
	utils.StartNode(stack)

	// Unlock any account specifically requested
	ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)

	passwords := utils.MakePasswordList(ctx)
	unlocks := strings.Split(ctx.GlobalString(utils.UnlockedAccountFlag.Name), ",")
	for i, account := range unlocks {
		if trimmed := strings.TrimSpace(account); trimmed != "" {
			unlockAccount(ctx, ks, trimmed, i, passwords)
		}
	}
	// Register wallet event handlers to open and auto-derive wallets
	events := make(chan accounts.WalletEvent, 16)
	stack.AccountManager().Subscribe(events)

	go func() {
		// Create an chain state reader for self-derivation
		rpcClient, err := stack.Attach()
		if err != nil {
			utils.Fatalf("Failed to attach to self: %v", err)
		}
		stateReader := ethclient.NewClient(rpcClient)

		// Open any wallets already attached
		for _, wallet := range stack.AccountManager().Wallets() {
			if err := wallet.Open(""); err != nil {
				log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)
			}
		}
		// Listen for wallet event till termination
		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)

				if event.Wallet.URL().Scheme == "ledger" {
					event.Wallet.SelfDerive(accounts.DefaultLedgerBaseDerivationPath, stateReader)
				} else {
					event.Wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
				}

			case accounts.WalletDropped:
				log.Info("Old wallet dropped", "url", event.Wallet.URL())
				event.Wallet.Close()
			}
		}
	}()
	// Start auxiliary services if enabled
	if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) {
		// Mining only makes sense if a full Ethereum node is running
		if ctx.GlobalBool(utils.LightModeFlag.Name) || ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
			utils.Fatalf("Light clients do not support mining")
		}
		var ethereum *eth.Ethereum
		if err := stack.Service(ðereum); err != nil {
			utils.Fatalf("Ethereum service not running: %v", err)
		}
		// Use a reduced number of threads if requested
		if threads := ctx.GlobalInt(utils.MinerThreadsFlag.Name); threads > 0 {
			type threaded interface {
				SetThreads(threads int)
			}
			if th, ok := ethereum.Engine().(threaded); ok {
				th.SetThreads(threads)
			}
		}
		// Set the gas price to the limits from the CLI and start mining
		ethereum.TxPool().SetGasPrice(utils.GlobalBig(ctx, utils.GasPriceFlag.Name))
		if err := ethereum.StartMining(true); err != nil {
			utils.Fatalf("Failed to start mining: %v", err)
		}
	}
}

首先node start自己。node將之前註冊的所有service交給p2p.Server, 然後啟動p2p.Server物件,Server物件會逐個啟動每個Service。

解鎖賬號,並註冊錢包反饋事件。

啟動RPC。(RPC 提供一種能通過網路或者其他I/O連線訪問的能力,將在後續章節分析)

如果配置支援挖礦,則啟動挖礦。

node.Wait()阻塞住程式,直到nodestop。

三,小結

Node就好像一個組裝工廠,把以太坊相關功能裝配起來,連線了以太坊的前端和後端,啟動RPC供遠端呼叫,啟動了P2P server跟網路中的其他節點建立聯絡,開始了console 供命令列操作。

Node模組並沒有做任何跟區塊鏈實質相關的事情,甚至它都不直接建立以太坊相關模組,而讓它們封裝成一個個的service,每個service自身自滅。就好比一個賣場,各個商家都可以來賣東西,但我不關心你們的死活,你們的死活也不影響我。

這給我們擴充套件以太坊客戶端的功能提供一個思路,我們可以把擴充套件功能封裝成一個Service,塞給Node。

相關推薦

原始碼深入分析2-- go-ethereum 客戶入口Node分析

一,geth makefile 以及編譯邏輯上篇提到用 make geth 來編譯geth客戶端。我們來看看make file做了什麼: geth: build/env.sh go run build/ci.go install ./cmd/geth @echo

原始碼深入分析3-- RPC通訊例項原理程式碼分析

上一節提到,以太坊在node start的時候啟動了RPC服務,以太坊通過Rpc服務來實現以太坊相關介面的遠端呼叫。這節我們用個例項來看看以太坊 RPC是如何工作的,以及以太坊RPC的原始碼的實現一,RPC通訊例項1,RPC啟動命令 :geth --rpcgo-ethereu

原始碼深入分析4-- RPC通訊例項原理程式碼分析

上一節我們試著寫了一個RPC的請求例項,通過分析原始碼知道了RPC服務的建立流程,以及Http RPC server建立過程,Http RPC Client的請求流程。這一節,先分析一下Http RPC server如何處理client的請求。然後再分析一下IPC RPC的處

原始碼深入分析8-- 核心BlockChain原始碼分析

前面幾節都在分析以太坊的通訊協議,怎麼廣播,怎麼同步,怎麼下載。這一節講講以太坊的核心模組BlockChain,也就是以太坊的區塊鏈。一,BlockChain的初始化Ethereum服務初始化的時候會呼叫core.SetupGenesisBlock來載入創始區塊。顧名思義,創

基於的DPOS實現原始碼及測試執行

原始碼 目錄 測試執行 安裝環境 開始之前請先確認已經安裝配置好Go語言環境,安裝包可以從https://golang.org下載,如果連不Go的官網請自行百度下安裝包。gttc現在支援Go版本為 1.9.x、1.10.x 及1.11.x。 $ go ver

ETH DAPP開發1:實戰開發基於truffle

一、開發環境配置 1、硬體配置 2、依賴工具版本 ~/eth_workspace$geth version Geth Version: 1.8.18-stable Architecture: amd64 Protocol Versions: [63 62] Network Id:

基於的DPOS實現創世塊

原始碼 目錄 創世檔案 我們將這個DPOS共識命名為alien,所以大家在文中或程式碼中看到

Dapp開發全過程solidity

繼上篇用php70行程式碼獲取所有以太坊區塊鏈應用程式碼,獲取到以太坊dapp的solidity程式碼,除了用mythril工具掃描出安全問題,還是得深入分析程式碼邏輯。然而solidity語法有些不明白的地方,故藉著loomnetwork的cryptozombies遊戲 學

python--DenyHttp項目2--ACM監考客戶測試版1階段完成總結

tdi text class 測試版 window etl operate comm decode   客戶端: ‘‘‘ DenyManager.py 調用客戶端與客戶端界面 ‘‘‘ from DenyClient import * from DenyGui import

高效能網站架構設計之快取篇2- Redis C#客戶

在上一篇中我簡單的介紹瞭如何利用redis自帶的客戶端連線server並執行命令來操作它,但是如何在我們做的專案或產品中操作這個強大的記憶體資料庫呢?首先我們來了解一下redis的原理吧。 官方文件上是這樣說的:Redis在TCP埠6379上監聽到來的連線,客戶端連線到來時,Redis伺服器為此建立一個TC

兄弟連區塊鏈教程原始碼分析CMD深入分析

cmd包分析 cmd下面總共有13個子包,除了util包之外,每個子包都有一個主函式,每個主函式的init方法中都定義了該主函式支援的命令,如 geth包下面的: func init() {     // Initialize the CLI app and st

原始碼分析---go-ethereum之p2p通訊分析2

本文QQ空間連結:http://user.qzone.qq.com/29185807/blog/1519899372 上一篇分析了p2p模組的初始化與start。繼續上一篇分析。 先回顧下p2p的初始化 github.com/ethereum/go-ethereu

原始碼解讀5BlockChain類的解析及NewBlockChain()分析

一、blockchain的資料結構 type BlockChain struct { chainConfig *params.ChainConfig // 初始化配置 cacheConfig *CacheConfig // 快取配置 db ethdb.Databas

原始碼解讀6blockchain區塊插入校驗分析

以太坊blockchain的管理事務: 1、blockchain模組初始化 2、blockchain模組插入校驗分析 3、blockchain模組區塊鏈分叉處理 4、blockchian模組規範鏈更新 上一節分析了blockchain的初始化,這一節來分析blockchain區塊的插入和校驗

原始碼解讀2客戶geth原始碼目錄解析

下面我們來從Geth原始碼的目錄來看看以太坊都有哪些模組。 一、目錄分析 go-etherreum-master |- accounts /* 實現了高層級Ethereum賬號管理 */ | |- abi // 該包實現了Ether

原始碼分析之 P2P網路三、UDP底層通訊

區塊鏈特輯 :https://blog.csdn.net/fusan2004/article/details/80879343,歡迎查閱,原創作品,轉載請標明!這周工作有點小忙,部門區塊鏈基礎平臺的開發開始進入節奏了,和上一篇間隔間隔有點久了,以後還是要堅持,不能剛開始就犯毛

原始碼分析之 P2P網路二、節點發現流程

區塊鏈特輯 :https://blog.csdn.net/fusan2004/article/details/80879343,歡迎查閱,原創作品,轉載請標明!上一篇文章簡單介紹了下一些基礎的型別定義,從這一篇開始我們將描述p2p網路的更多細節。從關於節點的定義來看,其實不同

原始碼分析---go-ethereum之p2p通訊分析1

這篇文章寫的非常好。裡面對以太坊原始碼的分析也非常到位,在程式碼框架上,表達非常清晰。 那麼為何我還要寫這篇原始碼分析呢,在分析原始碼的同時,也有看這篇文章,但這篇文章主要是在程式碼框架上,程式碼的細節方面,還有待補充。這就是我寫部落格的初衷。 那麼正文開始

原始碼分析:共識1礦工

前言 礦工在PoW中負責了產生區塊的工作,把一大堆交易交給它,它生成一個證明自己工作了很多區塊,然後將區塊加入到本地區塊鏈並且廣播給其他節點。 接下來我們將從以下角度介紹礦工: 角色。礦工不是一個人,而是一類人,可以把這一類人分成若干角色。 一個區塊產生的主要流

原始碼解讀4Block類及其儲存

一、Block類 type Block struct { /******header*******/ header *Header /******header*******/ /******body*********/ uncle