由于新型肺炎,不得不宅在家里面,闲着也是闲着,不如再认真地过一遍Tendermint源码吧。之前看的比较粗略,理解的也不够深。正好毕设使用了Tendermint平台作为底层支持,能够深入了解下也挺好的。
系统的启动
我们可以知道,tendermint最基本的启动流程通常是
1
2
|
tendermint init
tendermint node
|
在根目录,通过linux命令查找main函数可以得以下结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# grep -r "func main()" .
./abci/cmd/abci-cli/main.go:func main() {
./abci/tests/benchmarks/parallel/parallel.go:func main() {
./abci/tests/benchmarks/simple/simple.go:func main() {
./abci/tests/test_app/main.go:func main() {
./abci/types/protoreplace/protoreplace.go:func main() {
./cmd/contract_tests/main.go:func main() {
./cmd/priv_val_server/main.go:func main() {
./cmd/tendermint/main.go:func main() {
./docs/guides/go-built-in.md:func main() {
./docs/guides/go-built-in.md:func main() {
./docs/guides/go.md:func main() {
./docs/guides/go.md:func main() {
./libs/autofile/cmd/logjack.go:func main() {
./rpc/lib/test/main.go:func main() {
./scripts/json2wal/main.go:func main() {
./scripts/privValUpgrade.go:func main() {
./scripts/wal2json/main.go:func main() {
./test/app/grpc_client.go:func main() {
./tools/tm-signer-harness/main.go:func main() {
|
我们容易得知系统的启动入口函数在cmd/tendermint/main.go
中
main函数比较简单,首先指定了根指令rootCmd
,然后给根指令添加了一系列的二级指令,包括
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
cmd.GenValidatorCmd, //对于验证者生成所需要的密钥对,指令为tendermint gen_validator
cmd.InitFilesCmd, // 生成一个全新的tendermint实例所需要的文件,指令为tendermint init
cmd.ProbeUpnpCmd,
cmd.LiteCmd,
cmd.ReplayCmd,
cmd.ReplayConsoleCmd,
cmd.ResetAllCmd,
cmd.ResetPrivValidatorCmd,
cmd.ShowValidatorCmd,
cmd.TestnetFilesCmd,
cmd.ShowNodeIDCmd,
cmd.GenNodeKeyCmd,
cmd.VersionCmd,
debug.DebugCmd,
|
还有一个
1
2
|
nodeFunc := nm.DefaultNewNode
rootCmd.AddCommand(cmd.NewRunNodeCmd(nodeFunc)) // 启动一个节点,指令为tendermint node
|
最后通过tendermint自己封装的cli库解析环境指令,然后处理命令行,执行对应的执行。
根指令
系统main入口很简单,在执行我们的启动指令前,tendermint处理了什么呢?
从rootCmd
定义中我们可以看到,定义了一个PersistentPreRunE
函数,具体内容为
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
func(cmd *cobra.Command, args []string) (err error) {
if cmd.Name() == VersionCmd.Name() {
return nil
}
config, err = ParseConfig()
if err != nil {
return err
}
if config.LogFormat == cfg.LogFormatJSON {
logger = log.NewTMJSONLogger(log.NewSyncWriter(os.Stdout))
}
logger, err = tmflags.ParseLogLevel(config.LogLevel, logger, cfg.DefaultLogLevel())
if err != nil {
return err
}
if viper.GetBool(cli.TraceFlag) {
logger = log.NewTracingLogger(logger)
}
logger = logger.With("module", "main")
return nil
}
|
可以得知,如果调用的指令是版本查询,则无需处理配置文件,直接进行后续的版本查询操作即可,预执行指令也可以结束了。
如果不是版本查询,需要首先解析相关配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
|
func ParseConfig() (*cfg.Config, error) {
conf := cfg.DefaultConfig()
err := viper.Unmarshal(conf)
if err != nil {
return nil, err
}
conf.SetRoot(conf.RootDir)
cfg.EnsureRoot(conf.RootDir)
if err = conf.ValidateBasic(); err != nil {
return nil, fmt.Errorf("error in config file: %v", err)
}
return conf, err
}
|
本部分比较简单,只是从系统中读取了默认的配置,然后将配置写入到viper中方便后续的全局调用。
处理完配置后,接下来rootCmd
进行日志的配置,我们可以得知日志处理文件在libs
中,我们后续进行深入的探索,目前只知道是对不同的日志级别进行配置。
环境解析
我们现在要看最后这一句话,完成了系统启动前的最后一步配置
1
|
cmd := cli.PrepareBaseCmd(rootCmd, "TM", os.ExpandEnv(filepath.Join("$HOME", cfg.DefaultTendermintDir)))
|
其中默认的Tendermint路径为.tendermint
,然后系统具体做了什么呢?
1
2
3
4
5
6
7
|
func PrepareBaseCmd(cmd *cobra.Command, envPrefix, defaultHome string) Executor {
cobra.OnInitialize(func() { initEnv(envPrefix) })
cmd.PersistentFlags().StringP(HomeFlag, "", defaultHome, "directory for config and data")
cmd.PersistentFlags().Bool(TraceFlag, false, "print out full stack trace on errors")
cmd.PersistentPreRunE = concatCobraCmdFuncs(bindFlagsLoadViper, cmd.PersistentPreRunE)
return Executor{cmd, os.Exit}
}
|
似乎有一些乱,可以一句一句的分析。
- 初始化系统环境变量,把系统中的关于Tendermint的环境变量如
TMROOT
换成TM_ROOT
,同时配置到viper中。
- 获取系统中配置的
home
的值,指的是存储系统中config和data的路径
- 获取系统中配置的
trace
的值,指的是对于错误是否输出全部信息
- 这段分两个逻辑,一个是组合所有的预处理程序,一个是解析配置文件。其中解析配置文件是读取config文件写入到viper中,组合是直接遍历程序写入。
通过完成以上最基本的配置和日志的处理,节点启动前的准备就结束了。