以太坊源码深入分析(2)-- go-ethereum 客户端入口代码和Node分析

一,geth makefile 以及编译逻辑
上篇提到用 make geth 来编译geth客户端。我们来看看make file做了什么:

   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't exist 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 the workspace.
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“[your preferred account]”console >>geth.log

--datadir设置区块链数据存放路径
--networkid 网络设置启动的区块链网路默认值是1表示以太坊公司,0,2,3表示测试网路,大于4表示本地私有网路
--fast同步方式,默认为fast要选择完全同步使用命令--syncmode full
--cache设置缓存大小(最小16 MB /数据库强制)(默认值:128)
console表示启动控制台 >>geth.log将控制台显示内容输出到文件geth.log中去

geth的源码入口main()函数在/cmd/geth/main.go

// /cmd/geth/main.go  
func main() {  
    if err := app.Run(os.Args); err != nil {  
        fmt.Fprintln(os.Stderr, err)  
        os.Exit(1)  
    }  
}  
func geth(ctx *cil.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
}
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把创建eth service、创建DashBoard service、创建Shh service以及创建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来启动

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()阻塞住程序,直到node stop。

三,小结

Node就好像一个组装工厂,把以太坊相关功能装配起来,连接了以太坊的前端和后端,启动RPC供远程调用,启动了P2P server跟网络中的其他节点建立联系,开始了console 供命令行操作。

Node模块并没有做任何跟区块链实质相关的事情,甚至它都不直接创建以太坊相关模块,而让它们封装成一个个的service,每个service自身自灭。就好比一个卖场,各个商家都可以来卖东西,但我不关心你们的死活,你们的死活也不影响我。

这给我们扩展以太坊客户端的功能提供一个思路,我们可以把扩展功能封装成一个Service,塞给Node。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容