带你读《GO语言公链开发实战》之三:守护进程的初始化与运行

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 本书的目标是引导读者全面了解区块链技术实现原理,笔者也一直坚信,了解某一系统最直接的方式就是研读它的源码,所以本书并不是只介绍区块链技术,而是深入分析其背后的实现原理。通过阅读本书,读者可以全面地了解一条公链的技术实现。本书基于比原链的源代码进行分析,比原链是一个开源的有智能合约功能的公共区块链平台,是国内优秀的公链,目前比原链的代码量不多,而且源码结构清晰,特别适合初学者学习。

点击查看第一章
点击查看第二章

第3章

守护进程的初始化与运行

3.1 概述

节点初始化是节点首次使用时,根据用户传入的参数进行设置,并根据参数进行网络、数据库、本地区块链以及P2P分布式网络等模块的初始化,使得节点能够正常运行。节点初始化由bytomd守护进程执行,在初次运行时一次性完成。
本章主要内容:

  • bytomd守护进程初始化流程。
  • 守护进程初始化的具体实现,包括网络、数据库、本地区块链初始化等。
  • 守护进程启动流程和停止流程。

3.2 bytomd守护进程初始化流程及命令参数

守护进程是一种特殊进程,启动后一直在后台运行,只有当触发特定的信号时,才会执行退出操作。比原链的守护进程是bytomd,初始化流程如图3-1所示。
在编写命令行程序时,通常需要对命令参数进行解析。不同语言一般都会提供解析命令行参数的方法或库,以方便程序员使用。在GO语言标准库中提供了flag包,方便进行命令行解析。

image.png

bytomd进程支持的传参如下:
image.png
image.png

3.3 bytomd守护进程的初始化实现

bytomd守护进程的Cobra流程与bytomcli过程非常相似,所以在此略去,后续主要对bytomd守护进程重要内容进行深入分析。在这里我们看一下bytomd预处理过程中使用到的代码文件结构,命令如下:
image.png

bytomd守护进程启动时,会根据不同的命令行flag参数,初始化不同的模块,最终以守护进程的方式运行。有关bytomd守护进程所有的运行工作都在node.NewNode(config)的具体实现中。下面介绍具体的实现过程。

3.3.1 Node对象

image.png

Node对象说明如下。

  • cmn.BaseService:服务管理。
  • config:当前节点的全局配置。
  • syncManager:区块和交易同步管理。
  • wallet:本地钱包管理。
  • accessTokens:token管理,用户访问凭证。
  • api:API Server接口服务。
  • chain:本地区块链管理对象。
  • txfeed:当前版本中该功能未使用。
  • cpuMiner:CPU挖矿管理对象。
  • miningPool:矿池管理对象。
  • miningEnable:是否启用挖矿模式。

node.NewNode(config)整个过程是为了创建Node对象,Node对象是整个bytomd所有模块运行的基础。
cmn.BaseService是tendermint框架的一个服务管理模块,在这里我们可以把Node作为一个服务,对该服务进行OnStart/OnStop/IsRunning等操作管理。tendermint框架可以保证这些操作不会被重复执行多次。

3.3.2 配置初始化

在执行node.NewNode(config)之前,config的默认配置就已经定义好了。在深入node.NewNode(config)分析之前,我们需要先了解默认配置都有哪些。
image.png

首先,bytomd守护进程声明一个config全局变量,表示整个bytomd守护进程的配置信息。进程启动时config对象被赋予一个默认的配置参数。
image.png

默认的配置参数分有6个,每个针对不同的模块。下面对配置进行说明。我们将默认参数归纳为三块:Base基础配置、P2P网络配置、其他配置。
1. Base基础配置
BaseConfig用于配置bytomd节点所需的基础参数,包括数据目录、日志、监听地址等相关参数。
image.png
image.png

部分参数从配置文件中获取默认值,比如ApiAddress参数,它的tag是api_addr。我们可以从config/toml.go中获取默认值:
image.png

2. P2P网络配置

P2PConfig用于配置bytomd P2P通信协议中使用的参数,包括本机监听端口、通信节点超时、地址簿等相关参数。
image.png
image.png

注意,在比特币中,节点会采用DNS的方式来询问种子节点,进而查询到其他节点的IP地址。而在比原链中,种子节点是IP地址,一般会硬编码到代码里。技术细节我们会在后面的第10章详细讲解。
3. 其他配置
WalletConfig用于配置bytomd本地钱包使用的参数,包括是否启用本地钱包和更新等相关参数。
image.png

在bytomd守护进程声明config = DefaultConfig()之后,init()函数实现了config对象中各属性的赋值。具体实现代码如下:
image.png
image.png

在init()函数中定义了很多不同类型的flag参数,并将flag的参数值绑定到config对象上,比如:
image.png

这条语句的含义为:

  • 定义一个Bool类型的flag参数。
  • 该flag的名称为mining。
  • 该flag的赋值对象为config.Mining。
  • 该flag的描述信息为Enable mining。

至此,bytomd守护进程所需要的配置信息初始化完毕,程序运行真正进入初始阶段。下面对此进行深入分析。

3.3.3 创建文件锁

在比原链中,一份数据目录(--root参数指定)只能同时由一个bytomd守护进程读写,因为LevelDB高性能键值数据库是单进程模式,如果多个进程同时读写一份数据,会造成数据不一致的情况。因此,需要使用文件锁可以保证同一时间一个进程读写一份数据目录,代码如下:
image.png

bytomd启动时,lockDataDirectory函数使用flock在RootDir目录下创建一个LOCK文件。如果bytomd进程在一个文件的inode上加了锁,那么再次启动bytomd进程则会对errors.New中的内容报错并退出进程。flock的作用是检测进程是否已经存在。
flock主要有3种操作类型。

  • LOCK_SH:共享锁,多个进程使用同一把锁用于读锁。
  • LOCK_EX:排他锁,同时只允许一个进程使用,一般用于写锁。
  • LOCK_UN:释放锁。

如果深入研究flock包的函数,我们可以看到,这里使用了LOCK_EX锁,即同时只允许一个进程使用,代码示例如下:
image.png

3.3.4 初始化网络类型

比原链的三种网络模式,分别是mainnet主网、testnet测试网和solonet单机模式。
image.png

其中initActiveNetParams函数根据用户传入的chain_id,初始化网络类型。consensus.ActiveNetParams对象保存了当前使用的网络模式。在比原链代码中会经常引用consensus.ActiveNetParams对象,用来识别当前节点连接的网络类型。
image.png

ActiveNetParams默认使用主网。MainNetParams中的参数说明如下。

  • Bech32HRPSegwit:隔离见证,是一种协议升级,我们会在后面第5章讲解。
  • Checkpoints:检查点,指定一个高度,以及与这个高度相匹配的hash值,用于快速同步时验证区块的正确性。通常在主网升级时,会将历史的块信息硬编码在Checkpoints中。

Checkpoints检查点有两种作用:第一是防止分叉,如果有人试图从检查点之前的区块进行分叉,当前节点不会接受这个分叉;也用于保护网络不受全网51%的算力攻击,因为攻击者不可能逆转检查点之前的交易。第二是用于节点间的快速同步,我们将在第10章中详细讲解。

3.3.5 初始化数据库(持久化存储)

创建一条公链,需要将链上的所有数据(包含块信息、交易信息等)存储在本地键值数据库中。在比原链中使用LevelDB来存储链上数据,代码如下:
image.png

dbm使用tendermint框架的db管理库。dbm.NewDB返回一个DB对象,DB对象提供了数据库接口和许多方法实现,包括使用内存映射、文件系统目录结构、GO中LevelDB等的实现。
dbm.NewDB返回一个DB对象,需要传入三个参数:db的名称,db使用的键值数据库(默认为LevelDB),db数据存储的路径。leveldb.NewStore函数返回一个Store对象,即比原链对LevelDB进行了封装,在LevelDB的基础上增加了区块缓存(cache)、区块验证、区块状态、区块查询等功能。

3.3.6 初始化交易池

当交易被广播到网络中并且被矿工接收到时,矿工会将接收到的交易加入到本地的TxPool交易池中,TxPool对象的作用是管理本地交易池。交易池相当于一个缓冲区,它并不是无限大。默认情况下比原链中交易池最大可以存储10 000笔交易。如果超出这个阈值,则会返回"transaction pool reach the max number"提示。
image.png

protocol.NewTxPool()返回一个TxPool实例对象。此处我们只介绍交易池初始化部分,交易池实现原理的代码将在6.10节中深入剖析。

3.5.7 创建一条本地区块链

当节点第一次启动时,判断本地持久化存储的状态,当状态为初始化时会初始化本地的区块链。区块链的第一个区块(创世区块)会被加入到区块高度为0的地方。代码如下:
image.png

protocol.NewChain返回一个Chain对象,NewChain需要接收两个参数:Store区块链的存储对象,TxPool交易池。Chain对象管理着比原链的整个区块链条。代码如下:
image.png

NewChain函数的执行可分为下面几个步骤:
1)实例化Chain对象。
2)store.GetStoreStatus获取本地区块链的存储状态,如果状态为nil则说明区块链未被初始化。执行initChainStatus初始化本地区块链,该函数初始化创世区块(第一个区块)并添加到本地链上。
3)store.LoadBlockIndex加载块索引,从数据库中读取所有Block Header信息并缓存在内存中,目的是加速访问区块头信息。
4)c.index.SetMainChain,设置当前节点已同步的最新区块。
5)go c.blockProcesser(),启动一个go rutine,用于更新本地区块链上的区块信息。

3.3.8 初始化本地钱包

默认情况下比原链节点会启用本地钱包功能。代码实例如下:
image.png

在比原链的节点启动时,上述代码流程主要逻辑为:
1)创建加密机hsm对象,hsm对象管理keystore文件,该文件是存储私钥的一种格式(JSON)。keystore是一串代码,本质上是加密后的私钥,需配合钱包的密码来使用。
2)创建钱包数据库。
3)创建账户管理对象。
4)创建资产管理对象。
5)实例化Wallet对象。
6)RescanBlocks扫描本地所有区块,触发钱包更新操作。

3.3.9 初始化网络同步管理

P2P通信模块主要由SyncManager管理,SyncManager负责节点业务层信息的同步工作,即区块和交易信息的同步。代码如下:
image.png
image.png

主要参数说明如下:

  • newBlockCh:通道用于新挖掘出的区块进行快速广播给其他节点。通道大小为1024。
  • netsync.NewSyncManager:实例化syncManager同步管理对象,它管理节点与节点之间的区块、交易信息同步。
  • newPoolTxListenner:启动一个goroutine,监听交易池中的交易,将交易发送给syncManager同步管理对象或本地钱包。

详细实现机制将在第10章进行讲解。

3.3.10 初始化Pprof性能分析工具

Pprof是GO语言标准库中自带的性能分析工具。用于内存分析、CPU分析、代码追踪等,还可以生成性能分析图表。(详细参考https://golang.org/pkg/net/http/pprof/ )。在比原链中默认不启用该功能,可以使用--prof_laddr参数启动代码性能分析功能,代码示例如下:
image.png

3.3.11 初始化CPU挖矿功能

在比原链节点源码中,只提供了CPU设备的挖矿功能,以目前全网的算力来看,CPU设备挖矿几乎挖不到BTM币了。目前主流的挖矿设备,有比特大陆定制的挖矿芯片或各大矿池使用GPU设备挖矿。挖矿和矿池细节将在第13章中详细解读。代码实例如下:
image.png

其中,simd参数用于Tenaority CPU指令的优化。

3.4 bytomd守护进程的启动方式和停止方式

我们在GO语言下实现守护进程的方式一般是,监听标准的SIGTERM信号。在监听到SIGTERM信号后,进程处于阻塞状态,以实现守护进程。只有当进程收到来自外部的SIGTERM信号时,进程则处于非阻塞状态,实现进程退出。Linux信号参考http://man7.org/linux/man-pages/man7/signal.7.html 。代码实例如下:
image.png

signal.Notify监听中断和Term信号。启用goroutine取c对象,select进入阻塞状态。当进程接收到Term信号则通知c对象,执行os.Exit退出守护进程。
发送Term信号有两种方式:一种是执行命令kill -15 pid;另一种是进程运行在前台。
当守护进程接收到Term信号后就停止运行,在其退出之前需要做扫尾工作,如退出挖矿模式,退出P2P同步功能等。代码示例如下:
image.png
image.png

3.5 本章小结

本章从源码的角度分析了bytomd启动过程中的Node对象创建和初始化,以及总结bytomd实现的逻辑。

相关文章
|
6天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
23 2
|
4天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
13 2
|
4天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
16 2
|
7天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
4天前
|
Go
go语言中的 跳转语句
【11月更文挑战第4天】
13 4
|
4天前
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
18 1
|
6天前
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。
|
10天前
|
JavaScript Java Go
探索Go语言在微服务架构中的优势
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出。本文将深入探讨Go语言在构建微服务时的性能优势,包括其在内存管理、网络编程、并发模型以及工具链支持方面的特点。通过对比其他流行语言,我们将揭示Go语言如何成为微服务架构中的一股清流。
102 53
|
9天前
|
Ubuntu 编译器 Linux
go语言中SQLite3驱动安装
【11月更文挑战第2天】
31 7
|
9天前
|
关系型数据库 Go 网络安全
go语言中PostgreSQL驱动安装
【11月更文挑战第2天】
38 5