Golang Version: 1.8; i: h: ]* p3 y' H2 p
* `. `: b3 @; }
预备工作$ ?4 j8 h! g# t4 [% [3 P
编译安装
详细步骤见官方 bytom install& Y$ K0 Z) z! y2 I/ Z! K7 V& I* Z
设置debug日志输出
开启debug输出文件、函数、行号等详细信息( n/ U! g5 u; J2 S* e5 x# ]- K6 b
- export BYTOM_DEBUG=debug
初始化并启动bytomd' E; y- [; ?: l# M6 p* x, V
初始化
- ./bytomd init --chain_id testnet
bytomd目前支持两种网络,这里我们使用测试网
mainnet:主网5 L. _( B4 E; k
testnet:测试网
启动bytomd9 z+ {. l% k8 F; o! c
- ./bytomd node --mining --prof_laddr=":8011"
- –prof_laddr=":8080" // 开启pprof输出性能指标
访问:http://127.0.0.1:8080/debug/pprof/
bytomd init初始化 D/ E; y1 D+ d- c1 _
入口函数
- ** cmd/bytomd/main.go **7 W; H, S! q9 v# g, J: ^
- func init() {" c* }: V% y) R& r4 Q: n& E$ g
- log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableColors: true}), [' `" |, n2 f/ V3 M
- // If environment variable BYTOM_DEBUG is not empty,
- // then add the hook to logrus and set the log level to DEBUG
- if os.Getenv("BYTOM_DEBUG") != "" {
- log.AddHook(ContextHook{})
- log.SetLevel(log.DebugLevel)& Q" O" L# {3 H- q9 f4 F
- }
- }
- func main() {" _4 i- T" Q, ?
- cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))+ w9 J) J+ ]" \, A( p& E
- cmd.Execute()
- }
init函数会在main执行之前做初始化操作,可以看到init中bytomd加载BYTOM_DEBUG变量来设置debug日志输出( j0 m2 L/ e' I/ g1 H- M8 W( B) p% g
command cli传参初始化
bytomd的cli解析使用cobra库
** cmd/bytomd/commands **( K4 }' |3 l: A: d& C, z
cmd/bytomd/commands/root.go
初始化–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
初始化node节点运行时所需要的传参
初始化默认配置% X1 F% U( U6 k5 m
用户传参只有一部分参数,那节点所需的其他参数需要从默认配置中加载。: D% U2 D4 Y% @$ D: N& V5 O
- ** cmd/bytomd/commands/root.go **
- var ($ R* A% t6 B" `3 e% ^6 P
- config = cfg.DefaultConfig()
- )
- 在root.go中有一个config全局变量加载了node所需的所有默认参数
- // Default configurable parameters.2 u \8 N. Q- x& r
- func DefaultConfig() *Config {$ M; Z$ e% S; X4 D; z5 m8 D& o
- return &Config{
- BaseConfig: DefaultBaseConfig(), // node基础相关配置
- P2P: DefaultP2PConfig(), // p2p网络相关配置
- Wallet: DefaultWalletConfig(), // 钱包相关配置
- Auth: DefaultRPCAuthConfig(), // 验证相关配置: \1 K8 V( w# H+ W# e
- Web: DefaultWebConfig(), // web相关配置
- }6 L, b" R4 W$ ?
- }
后面的文章会一一介绍每个配置的作用. t! k+ j* D, i# F m
bytomd 守护进程启动与退出
- ** cmd/bytomd/commands/run_node.go **
- func runNode(cmd *cobra.Command, args []string) error {( V8 `$ e2 Z$ ?$ g$ l! ]
- // Create & start node$ |' O0 r" s% K3 @0 a4 g
- n := node.NewNode(config)
- if _, err := n.Start(); err != nil {
- return fmt.Errorf("Failed to start node: %v", err)" m/ \# m) ?# |0 h. `5 v* t# T( {
- } else {
- log.WithField("nodeInfo", n.SyncManager().Switch().NodeInfo()).Info("Started node")
- }
- // Trap signal, run forever.4 q3 O% w& ^( t0 |- k% G( a
- n.RunForever()
- return nil
- }
runNode函数有三步操作:
node.NewNode:初始化node运行环境
n.Start:启动node
n.RunForever:监听退出信号,收到ctrl+c操作则退出node。在linux中守进程一般监听SIGTERM信号(ctrl+c)作为退出守护进程的信号
初始化node运行环境
在bytomd中有五个db数据库存储在–root参数下的data目录1 b1 O; X+ j3 v& b" ^1 _% ^0 h8 |
accesstoken.db // 存储token相关信息(钱包访问控制权限)trusthistory.db // 存储p2p网络同步相关信息txdb.db // 存储交易相关信息txfeeds.db //wallet.db // 存储钱包相关信息
- 4 B* r6 g4 O! d: f
- ** node/node.go **
- func NewNode(config *cfg.Config) *Node {4 m7 z0 M& |0 K" E" `- ?" z, N
- ctx := context.Background()
- initActiveNetParams(config)
- // Get store 初始化txdb数据库
- txDB := dbm.NewDB("txdb", config.DBBackend, config.DBDir())* O" ~+ Q& H5 J* K
- store := leveldb.NewStore(txDB)
- // 初始化accesstoken数据库9 ?% o% U( y; r/ y' Y: E# U
- tokenDB := dbm.NewDB("accesstoken", config.DBBackend, config.DBDir())
- accessTokens := accesstoken.NewStore(tokenDB)( g. T. n9 \8 y& ?7 w0 a: a0 f3 h
- // 初始化event事件调度器,也叫任务调度器。一个任务可以被多次调用+ q3 g. }! a# B1 t: x; q
- // Make event switch8 V# X& z# E/ A' B2 F& t
- eventSwitch := types.NewEventSwitch()
- _, err := eventSwitch.Start()1 ]) o3 i% w1 O5 O! R- y9 b6 g
- if err != nil {
- cmn.Exit(cmn.Fmt("Failed to start switch: %v", err))
- }
- // 初始化交易池5 h$ Q3 }7 f8 |) h N
- txPool := protocol.NewTxPool()# k% Y' H2 x9 ` A% b6 S1 P
- chain, err := protocol.NewChain(store, txPool)5 K1 Z3 b/ L0 F' e
- if err != nil {
- cmn.Exit(cmn.Fmt("Failed to create chain structure: %v", err))8 R p# p3 T' a6 I1 A0 {/ d
- }2 ?& }; ]' j; d: t1 y; `) N
- var accounts *account.Manager = nil
- var assets *asset.Registry = nil, j$ b& \: k b- Z5 w
- var wallet *w.Wallet = nil8 c# X* E, A1 [/ ?
- var txFeed *txfeed.Tracker = nil
- // 初始化txfeeds数据库
- txFeedDB := dbm.NewDB("txfeeds", config.DBBackend, config.DBDir())1 G! V* x, Y1 k# d% Y
- txFeed = txfeed.NewTracker(txFeedDB, chain): A# v b" W. T( C8 k5 s
- if err = txFeed.Prepare(ctx); err != nil {. j6 T' f* W: B/ }
- log.WithField("error", err).Error("start txfeed")* Q- e& ~2 e% ?9 N4 Q
- return nil
- }4 S) r, @- Z8 E1 x/ X/ B. A( W
- // 初始化keystore
- hsm, err := pseudohsm.New(config.KeysDir())% u) X7 x& v2 S: y8 A
- if err != nil {
- cmn.Exit(cmn.Fmt("initialize HSM failed: %v", err))/ d$ |; W3 l4 X) H0 V( m3 H
- }
- // 初始化钱包,默认wallet是开启状态
- if !config.Wallet.Disable {5 }4 f" ?' [6 c, [4 g# f, c4 s
- walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir())6 I, i* ?( x4 x, v$ }+ l' j
- accounts = account.NewManager(walletDB, chain)
- assets = asset.NewRegistry(walletDB, chain)7 l s" x' D7 W; Q7 y$ {- O
- wallet, err = w.NewWallet(walletDB, accounts, assets, hsm, chain)% f; H6 C1 ]8 W7 q
- if err != nil {
- log.WithField("error", err).Error("init NewWallet")
- }
- // Clean up expired UTXO reservations periodically.5 y. {- n. ^* k' i, H7 G
- go accounts.ExpireReservations(ctx, expireReservationsPeriod)
- }
- newBlockCh := make(chan *bc.Hash, maxNewBlockChSize)
- // 初始化网络节点同步管理* a% p5 ^$ o+ V. K8 K' w
- syncManager, _ := netsync.NewSyncManager(config, chain, txPool, newBlockCh)0 B2 B3 ^/ v0 q
- // 初始化pprof,pprof用于输出性能指标,需要制定--prof_laddr参数来开启,在文章开头我们已经开启该功能
- // run the profile server
- profileHost := config.ProfListenAddress9 r. ~5 q+ |% v& S9 b3 d" i+ ?
- if profileHost != "" {
- // 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)
- }()
- }
- // 初始化节点,填充节点所需的所有参数环境
- node := &Node{
- config: config,# ]3 E) Y e8 F) |. j
- syncManager: syncManager,
- evsw: eventSwitch,
- accessTokens: accessTokens,6 s! t) b7 h( E
- wallet: wallet,# A; z" z$ s' ?. U# b
- chain: chain," v# ~: ^1 s$ N7 k
- txfeed: txFeed,
- miningEnable: config.Mining,6 [. ]) y& A( T* ]- c# u) U
- }
- // 初始化挖矿3 o# C, i7 D: R/ [& n+ M
- node.cpuMiner = cpuminer.NewCPUMiner(chain, accounts, txPool, newBlockCh)
- node.miningPool = miningpool.NewMiningPool(chain, accounts, txPool, newBlockCh)
- node.BaseService = *cmn.NewBaseService(nil, "Node", node)
- return node4 W9 \. z; B0 r/ r, W8 ]
- }
目前bytomd只支持cpu挖矿,所以在代码中只有cpuminer的初始化信息
启动node3 @% _# H; f+ q$ M/ V
- ** node/node.go **
- // Lanch web broser or not' [% L. F! Y6 O0 R1 J8 j
- func lanchWebBroser() {0 | P) a2 ?% M$ K0 e6 _- F2 Y
- log.Info("Launching System Browser with :", webAddress)
- if err := browser.Open(webAddress); err != nil {
- log.Error(err.Error())- ^7 {8 {, m. ?9 o2 i
- return+ L- V' a# ?. O! C8 W
- }
- }3 }3 Q; o: q; s: t: O
- func (n *Node) initAndstartApiServer() {0 `/ r0 v/ Y# \4 q) o' H
- n.api = api.NewAPI(n.syncManager, n.wallet, n.txfeed, n.cpuMiner, n.miningPool, n.chain, n.config, n.accessTokens)* r6 {2 e7 [5 c, _# g5 B$ o" P
- listenAddr := env.String("LISTEN", n.config.ApiAddress)
- env.Parse()- p* B, S" ]$ B- S3 B
- n.api.StartServer(*listenAddr)
- }5 w7 J$ {% Y; @ m
- func (n *Node) OnStart() error {- g" u: }* `3 ?4 K& d( C) f) R2 O
- if n.miningEnable {" d' F% g( @( k1 \* @% r* K" }
- n.cpuMiner.Start(), o/ ]6 y+ u/ n% h
- }! }' ~3 \8 V% Q
- n.syncManager.Start()
- n.initAndstartApiServer()
- if !n.config.Web.Closed {
- lanchWebBroser()3 f& U X+ A) G: r1 k( i+ m8 N6 ?
- }; e. c) E% p+ M$ C- Q7 {
- return nil
- }
OnStart() 启动node进程如下:, k7 J- _! T; `
启动挖矿功能启动p2p网络同步启动http协议的apiserver服务打开浏览器访问bytond的交易页面
7 ` Z0 t0 R. b! ^+ v
停止node
bytomd在启动时执行了n.RunForever()函数,该函数是由tendermint框架启动了监听信号的功能:
- ** vendor/github.com/tendermint/tmlibs/common/os.go **: e3 t, ~% ]. P0 A5 ]3 _$ Y
- func TrapSignal(cb func()) {, ?& n0 a! F$ a. N6 U
- c := make(chan os.Signal, 1)! T$ N9 L* w/ Q+ v
- signal.Notify(c, os.Interrupt, syscall.SIGTERM)% C7 y% u5 V4 r& c3 P6 D- y$ {
- go func() {, `! O; p( ]9 W# x
- for sig := range c { s6 t5 y* e$ o8 y, d' A
- fmt.Printf("captured %v, exiting...\n", sig); n$ |% K. g ]; A
- if cb != nil {# ` E1 h( T" M" D: B9 ^+ x
- cb() T& z. S: a* I7 T. x8 M
- }! x, v! ]; g% w5 h$ i
- os.Exit(1)
- }
- }()+ h2 I2 j+ y- j3 r
- select {}6 q+ S: }6 s. b' X
- }
TrapSignal函数监听了SIGTERM信号,bytomd才能成为不退出的守护进程。只有当触发了ctrl+c或kill bytomd_pid才能终止bytomd进程退出。退出时bytomd执行如下操作; L% a) O5 r, J4 f
- ** node/node.go *** M* ^. d3 a& }* p4 o! t Y% t
- func (n *Node) OnStop() {# V1 R& F, R: U5 y% j
- n.BaseService.OnStop()8 b0 j+ h1 o& e9 T
- if n.miningEnable {& ~ Z2 O) ~' ^3 L! Z* ]
- n.cpuMiner.Stop()
- }
- n.syncManager.Stop()5 W6 v$ a3 E3 M
- log.Info("Stopping Node")
- // TODO: gracefully disconnect from peers.
- }
bytomd会将挖矿功能停止,p2p网络停止等操作。