Golang Version: 1.80 T. i: f% _/ a
预备工作
编译安装; B4 f! O2 ^' n, o- n
详细步骤见官方 bytom install' m2 k7 h. G6 w/ d5 D; L
设置debug日志输出
开启debug输出文件、函数、行号等详细信息
- export BYTOM_DEBUG=debug
初始化并启动bytomd
初始化
- ./bytomd init --chain_id testnet
bytomd目前支持两种网络,这里我们使用测试网# V7 L) T0 F2 P: E
mainnet:主网
testnet:测试网
启动bytomd
- ./bytomd node --mining --prof_laddr=":8011"- Z$ w% K j3 F+ ] ^1 x
- –prof_laddr=":8080" // 开启pprof输出性能指标
访问:http://127.0.0.1:8080/debug/pprof/
bytomd init初始化
入口函数
- ** cmd/bytomd/main.go **
- func init() {% u( x; @# P/ V' m) n
- log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableColors: true})
- // If environment variable BYTOM_DEBUG is not empty,5 z$ p4 K* M% P+ h% M, \2 T0 }+ \
- // then add the hook to logrus and set the log level to DEBUG
- if os.Getenv("BYTOM_DEBUG") != "" {$ h( [8 X: v! q* p% r
- log.AddHook(ContextHook{})
- log.SetLevel(log.DebugLevel)
- }
- }
- func main() {4 X8 \- E% f. U& z" B3 ?8 \
- cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))
- cmd.Execute()
- }
init函数会在main执行之前做初始化操作,可以看到init中bytomd加载BYTOM_DEBUG变量来设置debug日志输出2 G' M, x3 ~; d C6 R, p0 ?
command cli传参初始化- s% A" A/ v9 J+ ]; ?
bytomd的cli解析使用cobra库 U0 H! B' A! k" s* i
** cmd/bytomd/commands **/ d! H9 d b5 [& l
cmd/bytomd/commands/root.go6 e4 l, N8 Q2 g1 g
初始化–root传参。bytomd存储配置、keystore、数据的root目录。在MacOS下,默认路径是~/Library/Bytom/cmd/bytomd/commands/init.go
初始化–chain_id传参。选择网络类型,在启动bytomd时我们选择了testnet也就是测试网络cmd/bytomd/commands/version.go
初始化version传参cmd/bytomd/commands/run_node.go; L) O" F9 h9 q* f: Q1 d
初始化node节点运行时所需要的传参, @% w3 r9 r+ n& T
初始化默认配置
用户传参只有一部分参数,那节点所需的其他参数需要从默认配置中加载。/ X+ F& P, `$ m o, v6 S; `
- ** cmd/bytomd/commands/root.go **
- var (
- config = cfg.DefaultConfig()9 `+ l/ E7 F$ [1 n* y+ g; V
- )
- 在root.go中有一个config全局变量加载了node所需的所有默认参数# C% |9 x" _; |7 X* M
- // Default configurable parameters.' z: [+ f$ g# y' Y& I
- func DefaultConfig() *Config {! l6 R# ~# R) v K" Y. o
- return &Config{/ _: a9 ]* [7 v) L5 J
- BaseConfig: DefaultBaseConfig(), // node基础相关配置9 B2 e2 ]/ e" @- J5 ^6 ~
- P2P: DefaultP2PConfig(), // p2p网络相关配置0 O% A- u: k2 x9 a
- Wallet: DefaultWalletConfig(), // 钱包相关配置) C9 O7 z# e: }) y2 w. _
- Auth: DefaultRPCAuthConfig(), // 验证相关配置, E; B7 q) c3 m% e+ d! E
- Web: DefaultWebConfig(), // web相关配置2 n2 g1 N- R/ o* @8 G2 o$ D2 m
- }
- }
后面的文章会一一介绍每个配置的作用
bytomd 守护进程启动与退出3 W& ^ n1 i3 ?$ P
- ** cmd/bytomd/commands/run_node.go **
- func runNode(cmd *cobra.Command, args []string) error {1 X3 D: \; A1 t" z% P! e q' o
- // Create & start node$ \# U0 M. |) P' G& |: O8 N
- n := node.NewNode(config)3 s% M% N, |! X5 a( @3 n9 |+ D
- if _, err := n.Start(); err != nil {0 f; Q7 s1 n* L
- return fmt.Errorf("Failed to start node: %v", err)
- } else {' ]: r& r3 n( \7 X9 ]
- log.WithField("nodeInfo", n.SyncManager().Switch().NodeInfo()).Info("Started node")9 Y' n& w2 V8 q n
- }
- // Trap signal, run forever.* _% N, O+ W6 m6 M4 h
- n.RunForever()
- return nil. ~9 b, C& t( g( _% r; q# l( s/ R
- }
runNode函数有三步操作:
node.NewNode:初始化node运行环境
n.Start:启动node
n.RunForever:监听退出信号,收到ctrl+c操作则退出node。在linux中守进程一般监听SIGTERM信号(ctrl+c)作为退出守护进程的信号* _* P+ y6 C/ G1 |8 A
初始化node运行环境
在bytomd中有五个db数据库存储在–root参数下的data目录$ v1 X/ M( [0 j* w
accesstoken.db // 存储token相关信息(钱包访问控制权限)trusthistory.db // 存储p2p网络同步相关信息txdb.db // 存储交易相关信息txfeeds.db //wallet.db // 存储钱包相关信息
- . q8 B" _. V3 \1 h5 Q* Q/ F8 c6 _
- ** node/node.go ** g8 g" m8 o! j6 l
- func NewNode(config *cfg.Config) *Node {# Y5 r/ @: J" A3 O9 U) f$ F' H ]8 i) j
- ctx := context.Background()5 i6 C d1 B1 L6 [, I$ r8 H. m; A5 Y
- initActiveNetParams(config)# }. c* d/ W2 T
- // Get store 初始化txdb数据库' D, @9 }/ ^- @* ]* E: `
- txDB := dbm.NewDB("txdb", config.DBBackend, config.DBDir())
- store := leveldb.NewStore(txDB)
- // 初始化accesstoken数据库1 n% n! _7 I; |' |
- tokenDB := dbm.NewDB("accesstoken", config.DBBackend, config.DBDir())
- accessTokens := accesstoken.NewStore(tokenDB)
- // 初始化event事件调度器,也叫任务调度器。一个任务可以被多次调用
- // Make event switch
- eventSwitch := types.NewEventSwitch()8 S# t, c6 |. I
- _, err := eventSwitch.Start()' {* \1 Q3 ] A+ \# d
- if err != nil {
- cmn.Exit(cmn.Fmt("Failed to start switch: %v", err))
- }
- // 初始化交易池
- txPool := protocol.NewTxPool()
- chain, err := protocol.NewChain(store, txPool)
- if err != nil {
- cmn.Exit(cmn.Fmt("Failed to create chain structure: %v", err))5 [& ]* }8 U. q. d
- }
- var accounts *account.Manager = nil
- var assets *asset.Registry = nil
- var wallet *w.Wallet = nil& X% x; ~6 Q }& T; h+ e* t; n
- var txFeed *txfeed.Tracker = nil
- // 初始化txfeeds数据库
- txFeedDB := dbm.NewDB("txfeeds", config.DBBackend, config.DBDir())% f: U. Z* ]2 C6 W) a u9 [' y2 Q
- txFeed = txfeed.NewTracker(txFeedDB, chain)
- if err = txFeed.Prepare(ctx); err != nil {! N" U( v4 e" N/ {+ Y
- log.WithField("error", err).Error("start txfeed")5 c1 u0 K+ @& I0 c
- return nil
- }
- // 初始化keystore! ]! \: i9 _' c% Y0 g
- hsm, err := pseudohsm.New(config.KeysDir())
- if err != nil {1 p0 F) g# H% X8 U, m& j
- cmn.Exit(cmn.Fmt("initialize HSM failed: %v", err))/ P9 p7 \ m p' m. l
- }
- // 初始化钱包,默认wallet是开启状态+ K& H, h, G$ Q# n/ r6 D+ z
- if !config.Wallet.Disable {
- walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir())$ G& P% S4 Z( ?- B& P/ X/ W
- accounts = account.NewManager(walletDB, chain)
- assets = asset.NewRegistry(walletDB, chain)
- wallet, err = w.NewWallet(walletDB, accounts, assets, hsm, chain)/ s- x3 E; g+ L; Y: ^
- if err != nil {5 `( k \) o$ i! ]
- log.WithField("error", err).Error("init NewWallet")
- }, U" {5 X2 n: f+ x7 o7 v# X* |" x
- // Clean up expired UTXO reservations periodically., v3 T+ J! e0 S; [6 p3 J7 d
- go accounts.ExpireReservations(ctx, expireReservationsPeriod)" I/ d9 k% G! P1 O2 Q* Z
- }
- newBlockCh := make(chan *bc.Hash, maxNewBlockChSize)
- // 初始化网络节点同步管理
- syncManager, _ := netsync.NewSyncManager(config, chain, txPool, newBlockCh)
- // 初始化pprof,pprof用于输出性能指标,需要制定--prof_laddr参数来开启,在文章开头我们已经开启该功能& ~& d, f+ a! m7 i) W9 `
- // run the profile server
- profileHost := config.ProfListenAddress
- if profileHost != "" {3 J* B, D4 B" G2 a4 N- W9 s
- // Profiling bytomd programs.see (<a href="https://blog.golang.org/profiling-go-programs" target="_blank">https://blog.golang.org/profiling-go-programs</a>)
- // go tool pprof http://profileHose/debug/pprof/heap
- go func() {
- http.ListenAndServe(profileHost, nil)( k# d6 K6 K6 S1 M5 a( s
- }()/ c* U0 t1 q4 D0 ~) y9 Z
- }
- // 初始化节点,填充节点所需的所有参数环境
- node := &Node{+ ]5 w6 c* ]. G' j0 ~3 o' T+ T
- config: config, G+ Q# a! ~0 \# a8 F3 X
- syncManager: syncManager,
- evsw: eventSwitch,. M5 c( N( _% F T J/ S4 J
- accessTokens: accessTokens,$ d0 Z2 d! f" u/ x- R4 h4 U
- wallet: wallet,
- chain: chain,
- txfeed: txFeed,
- miningEnable: config.Mining,5 Q. `8 _$ T6 y: a- U
- }; f) Z- O: b9 O/ X) q
- // 初始化挖矿
- node.cpuMiner = cpuminer.NewCPUMiner(chain, accounts, txPool, newBlockCh)
- node.miningPool = miningpool.NewMiningPool(chain, accounts, txPool, newBlockCh)- L8 N3 g m0 b6 V- A( ^
- node.BaseService = *cmn.NewBaseService(nil, "Node", node)
- return node; a: H; `( n+ t' B2 J3 Q
- }
目前bytomd只支持cpu挖矿,所以在代码中只有cpuminer的初始化信息6 J: n/ y [: V) v9 O
启动node
- ** node/node.go **
- // Lanch web broser or not: E! R# t( ?( j! Y$ r8 s4 D c
- func lanchWebBroser() {
- log.Info("Launching System Browser with :", webAddress)
- if err := browser.Open(webAddress); err != nil {+ `& z3 [. I' E X0 S
- log.Error(err.Error())5 J/ P) m o! p, g
- return# @# c" Q/ f+ ]2 m8 p7 ?
- }
- }
- func (n *Node) initAndstartApiServer() {
- n.api = api.NewAPI(n.syncManager, n.wallet, n.txfeed, n.cpuMiner, n.miningPool, n.chain, n.config, n.accessTokens)5 E0 F u- z# \
- listenAddr := env.String("LISTEN", n.config.ApiAddress), o3 A6 V' {: a$ L4 o
- env.Parse()& U& k' K; a+ Q
- n.api.StartServer(*listenAddr)4 `8 Y t' ]# Y) N1 a/ w- }0 C
- }
- func (n *Node) OnStart() error {
- if n.miningEnable {) I# \0 I* c- }5 A- F& v
- n.cpuMiner.Start()& o& D) w" T7 f6 M6 Y% L
- }
- n.syncManager.Start()
- n.initAndstartApiServer()
- if !n.config.Web.Closed {
- lanchWebBroser()
- }
- return nil
- }
OnStart() 启动node进程如下:/ G, i" o1 [; j# g
启动挖矿功能启动p2p网络同步启动http协议的apiserver服务打开浏览器访问bytond的交易页面: G3 \& G5 E) V9 a
停止node5 u0 q1 x: h- n" B, @: u) O$ w1 n
bytomd在启动时执行了n.RunForever()函数,该函数是由tendermint框架启动了监听信号的功能:, Y4 B# `4 g% D4 X( P( u
- ** vendor/github.com/tendermint/tmlibs/common/os.go **
- func TrapSignal(cb func()) {) g1 k7 l7 I* J/ J% x
- c := make(chan os.Signal, 1)
- signal.Notify(c, os.Interrupt, syscall.SIGTERM)
- go func() {
- for sig := range c {
- fmt.Printf("captured %v, exiting...\n", sig)+ I; p( M+ s, v
- if cb != nil {& q5 J7 y3 e% M2 P6 K+ a( Y
- cb()
- }8 S5 Q) n5 F0 i. J# Q/ X/ r
- os.Exit(1)+ ?" w) p4 G5 a9 f! X7 B# y
- }: D. p9 c% z1 z
- }()
- select {}
- }
TrapSignal函数监听了SIGTERM信号,bytomd才能成为不退出的守护进程。只有当触发了ctrl+c或kill bytomd_pid才能终止bytomd进程退出。退出时bytomd执行如下操作
- ** node/node.go **" P% E8 F/ B4 g, O9 K/ N
- func (n *Node) OnStop() {% d0 j# k) n }, ?
- n.BaseService.OnStop()
- if n.miningEnable {
- n.cpuMiner.Stop()! e: X& @ _" f% l! Z
- }' Q' O2 G2 ~5 }
- n.syncManager.Stop()9 H- f" O4 M7 i6 y6 r2 C, e
- log.Info("Stopping Node")
- // TODO: gracefully disconnect from peers.! B, x! Q2 s, C. b C; v
- }
bytomd会将挖矿功能停止,p2p网络停止等操作。