Golang Version: 1.8
7 J6 f/ j) }% T4 d
预备工作5 I1 F3 D; z& I3 k
编译安装
详细步骤见官方 bytom install
设置debug日志输出
开启debug输出文件、函数、行号等详细信息' R |! d+ H/ W" V
- export BYTOM_DEBUG=debug
初始化并启动bytomd
初始化
- ./bytomd init --chain_id testnet
bytomd目前支持两种网络,这里我们使用测试网1 e1 f6 V0 I, n2 v8 o. V
mainnet:主网
testnet:测试网
启动bytomd
- ./bytomd node --mining --prof_laddr=":8011"! R% q7 u1 ^3 E: o& i' N! G
- –prof_laddr=":8080" // 开启pprof输出性能指标
访问:http://127.0.0.1:8080/debug/pprof/
bytomd init初始化
入口函数
- ** cmd/bytomd/main.go **
- func init() {
- log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableColors: true})
- // If environment variable BYTOM_DEBUG is not empty,1 K f e, o, v6 c5 L' V9 f
- // then add the hook to logrus and set the log level to DEBUG3 M# X% @. l) V5 D2 z4 m7 O+ A
- if os.Getenv("BYTOM_DEBUG") != "" {
- log.AddHook(ContextHook{})! v' Q% L5 F) z3 g
- log.SetLevel(log.DebugLevel)
- }* x8 L% n3 c: }
- }5 \$ \9 v' k8 U: E7 l3 O$ o
- func main() {' K. b5 W8 v& ?# W$ E
- cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))
- cmd.Execute()0 Q+ [- f G) a& j" u' t5 e
- }
init函数会在main执行之前做初始化操作,可以看到init中bytomd加载BYTOM_DEBUG变量来设置debug日志输出
command cli传参初始化
bytomd的cli解析使用cobra库
** cmd/bytomd/commands **4 }" s, C: x; Q1 V" G
cmd/bytomd/commands/root.go( H0 V2 X; r" N4 V- T& E
初始化–root传参。bytomd存储配置、keystore、数据的root目录。在MacOS下,默认路径是~/Library/Bytom/cmd/bytomd/commands/init.go @5 A( ^# H; p! s2 t
初始化–chain_id传参。选择网络类型,在启动bytomd时我们选择了testnet也就是测试网络cmd/bytomd/commands/version.go8 M" d* s( r8 d
初始化version传参cmd/bytomd/commands/run_node.go
初始化node节点运行时所需要的传参
+ e; o4 ~: M" `/ U5 ?+ W
初始化默认配置' X' t& X( G1 C* r% f0 I6 V9 d
用户传参只有一部分参数,那节点所需的其他参数需要从默认配置中加载。6 l7 _7 \/ l( [% e# ~4 N* m: {
- ** cmd/bytomd/commands/root.go **
- var (
- config = cfg.DefaultConfig()
- )9 w& y# x1 z+ M& P& l8 c
- 在root.go中有一个config全局变量加载了node所需的所有默认参数
- // Default configurable parameters., ^! O, |( t. N, Z/ D+ t+ \
- func DefaultConfig() *Config {
- return &Config{) h* I) J- `& s+ A1 G% K
- BaseConfig: DefaultBaseConfig(), // node基础相关配置
- P2P: DefaultP2PConfig(), // p2p网络相关配置* @" D: L8 ]; S4 o) J
- Wallet: DefaultWalletConfig(), // 钱包相关配置
- Auth: DefaultRPCAuthConfig(), // 验证相关配置
- Web: DefaultWebConfig(), // web相关配置7 N1 i$ m4 d! U, q7 @
- }
- }
后面的文章会一一介绍每个配置的作用
bytomd 守护进程启动与退出, O! j' j8 M: r1 \% s2 |) M# \
- ** cmd/bytomd/commands/run_node.go **
- func runNode(cmd *cobra.Command, args []string) error {
- // Create & start node: l+ s4 M$ T! ]. V
- n := node.NewNode(config)
- if _, err := n.Start(); err != nil {7 s% P2 J1 F/ U% w6 w6 e( Z
- return fmt.Errorf("Failed to start node: %v", err)
- } else {
- log.WithField("nodeInfo", n.SyncManager().Switch().NodeInfo()).Info("Started node")0 |4 y2 L/ h+ x9 Q) y- }
- }
- // Trap signal, run forever.7 @$ D" e: u: x6 G
- n.RunForever()
- return nil
- }
runNode函数有三步操作:/ F' c! v2 T) k8 f; `& n
node.NewNode:初始化node运行环境$ @+ f E T7 j) L
n.Start:启动node/ @! A' p( b: f8 y6 [$ }
n.RunForever:监听退出信号,收到ctrl+c操作则退出node。在linux中守进程一般监听SIGTERM信号(ctrl+c)作为退出守护进程的信号8 m6 G' `' \. Y( C
初始化node运行环境
在bytomd中有五个db数据库存储在–root参数下的data目录4 W7 t: h) b3 L8 m/ Y: U$ R
accesstoken.db // 存储token相关信息(钱包访问控制权限)trusthistory.db // 存储p2p网络同步相关信息txdb.db // 存储交易相关信息txfeeds.db //wallet.db // 存储钱包相关信息
- ** node/node.go **
- func NewNode(config *cfg.Config) *Node {
- ctx := context.Background()2 N @) ]% G3 K$ ?+ z; I, n
- initActiveNetParams(config)& k6 w1 R: f2 I5 F
- // Get store 初始化txdb数据库
- txDB := dbm.NewDB("txdb", config.DBBackend, config.DBDir())
- store := leveldb.NewStore(txDB)
- // 初始化accesstoken数据库% B" ~/ ^; {2 c! N0 r& ]+ Y
- tokenDB := dbm.NewDB("accesstoken", config.DBBackend, config.DBDir())) ]* w, f2 j; O5 P# D1 W7 c
- accessTokens := accesstoken.NewStore(tokenDB)3 q( Y8 w* y- ^! R/ \: `! Q% K1 b1 p- F
- // 初始化event事件调度器,也叫任务调度器。一个任务可以被多次调用* d# `0 _: g& Q* w/ I7 j
- // Make event switch7 K0 t( o. Y3 w! J2 ^
- eventSwitch := types.NewEventSwitch(), n2 B+ h9 z* f- r& H
- _, err := eventSwitch.Start()
- if err != nil {0 w1 w# a. d1 R5 C. O% m
- cmn.Exit(cmn.Fmt("Failed to start switch: %v", err))# n1 w8 o# U$ s7 h# b$ `9 t3 G
- }
- // 初始化交易池4 ?' l* u. t; I: N- ~! X
- txPool := protocol.NewTxPool()
- chain, err := protocol.NewChain(store, txPool)7 w F9 ^/ u) G0 ^
- if err != nil {
- cmn.Exit(cmn.Fmt("Failed to create chain structure: %v", err))
- }/ C( a, A" |) E7 D3 Z
- var accounts *account.Manager = nil
- var assets *asset.Registry = nil
- var wallet *w.Wallet = nil6 _. x5 V' \2 Y6 k
- var txFeed *txfeed.Tracker = nil% h* l5 g1 `* G- A. ]2 P
- // 初始化txfeeds数据库
- txFeedDB := dbm.NewDB("txfeeds", config.DBBackend, config.DBDir())1 i: z9 U" y8 k7 }1 u8 g. a9 \
- txFeed = txfeed.NewTracker(txFeedDB, chain)
- if err = txFeed.Prepare(ctx); err != nil {
- log.WithField("error", err).Error("start txfeed")
- return nil4 N3 m: t: w; g5 D, v, U# I, D
- }
- // 初始化keystore
- hsm, err := pseudohsm.New(config.KeysDir())1 u6 N! l! y+ E% _3 p4 u
- if err != nil {
- cmn.Exit(cmn.Fmt("initialize HSM failed: %v", err))9 P1 i$ l. C# Z; A3 }7 n
- }+ b2 L( a5 \8 W$ G0 M5 s1 d2 `
- // 初始化钱包,默认wallet是开启状态( u) t. c4 o4 m# D( }9 b. y+ `
- if !config.Wallet.Disable {
- walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir())
- accounts = account.NewManager(walletDB, chain)) _" S G1 j/ {1 h1 w6 M
- assets = asset.NewRegistry(walletDB, chain)
- wallet, err = w.NewWallet(walletDB, accounts, assets, hsm, chain)
- if err != nil {$ q* J8 y& ^$ \, Y4 M5 V
- log.WithField("error", err).Error("init NewWallet")
- }
- // Clean up expired UTXO reservations periodically. t1 v; x" x. S% F2 U: o2 q/ q
- go accounts.ExpireReservations(ctx, expireReservationsPeriod)
- }6 p% P# v# `! U$ Y2 }$ x9 b: d
- newBlockCh := make(chan *bc.Hash, maxNewBlockChSize)) N; J4 S x, a G+ B' n* o
- // 初始化网络节点同步管理6 V6 L; p1 {4 f" u, P1 [- a
- syncManager, _ := netsync.NewSyncManager(config, chain, txPool, newBlockCh). _% m; J( j& a, X
- // 初始化pprof,pprof用于输出性能指标,需要制定--prof_laddr参数来开启,在文章开头我们已经开启该功能
- // run the profile server4 j7 F' `7 A7 x$ g( \! u
- profileHost := config.ProfListenAddress4 W% I- P3 f5 l. _/ k' c
- 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>)4 [ _; g7 g- {0 J8 V- F3 Q% h, A
- // go tool pprof http://profileHose/debug/pprof/heap
- go func() {
- http.ListenAndServe(profileHost, nil)& F/ C/ J+ C6 ], ], x2 J
- }()+ S3 |7 t7 v4 |8 h6 N- e& s9 n8 c
- }
- // 初始化节点,填充节点所需的所有参数环境! `( ~! H& a* O- A7 S
- node := &Node{2 j- _1 }" b W3 p1 d
- config: config,
- syncManager: syncManager,& P* m' o9 k6 L
- evsw: eventSwitch,
- accessTokens: accessTokens,2 E5 f7 x9 v& R7 F) ~
- wallet: wallet,& Y7 f- J# y( ^
- chain: chain,
- txfeed: txFeed,
- miningEnable: config.Mining,. f, ?( z! H, j" e7 }
- }1 g5 N. L8 D: ^
- // 初始化挖矿
- node.cpuMiner = cpuminer.NewCPUMiner(chain, accounts, txPool, newBlockCh)
- node.miningPool = miningpool.NewMiningPool(chain, accounts, txPool, newBlockCh)/ D3 B$ I4 [4 A. b5 A, v
- node.BaseService = *cmn.NewBaseService(nil, "Node", node)
- return node
- }
目前bytomd只支持cpu挖矿,所以在代码中只有cpuminer的初始化信息) s8 N# Z" `5 E+ s! f
启动node
- ** node/node.go **' O' D& {2 Z5 N
- // Lanch web broser or not2 q* n/ V' k& G' a6 q
- func lanchWebBroser() {
- log.Info("Launching System Browser with :", webAddress)
- if err := browser.Open(webAddress); err != nil {7 z+ B4 r0 F+ B1 C
- log.Error(err.Error())
- return
- }, r% o0 |' Y1 `5 J
- }
- func (n *Node) initAndstartApiServer() {" R5 B* N4 }8 U; v
- n.api = api.NewAPI(n.syncManager, n.wallet, n.txfeed, n.cpuMiner, n.miningPool, n.chain, n.config, n.accessTokens)
- listenAddr := env.String("LISTEN", n.config.ApiAddress)
- env.Parse()1 H4 v6 r3 K; u7 ]! r5 m
- n.api.StartServer(*listenAddr)& \, _: E, ^& ]; F
- }
- func (n *Node) OnStart() error {
- if n.miningEnable {
- n.cpuMiner.Start()
- }6 P. _# h' ?& G$ `, ?) b5 |
- n.syncManager.Start()) Q+ U! P) l$ D' k
- n.initAndstartApiServer()
- if !n.config.Web.Closed {
- lanchWebBroser()
- }
- return nil- M. ]. m% y: R& t: p1 S
- }
OnStart() 启动node进程如下:0 i8 b' H5 _" U2 k" l) P
启动挖矿功能启动p2p网络同步启动http协议的apiserver服务打开浏览器访问bytond的交易页面
停止node4 d$ a4 `! C# E6 {" d& w6 F
bytomd在启动时执行了n.RunForever()函数,该函数是由tendermint框架启动了监听信号的功能: E% g7 S/ W! w) x9 p9 a4 F
- ** vendor/github.com/tendermint/tmlibs/common/os.go **5 f/ l1 P Y- j( ]9 T! Z
- func TrapSignal(cb func()) {
- c := make(chan os.Signal, 1)/ n% ]# y& l: `9 H, s
- signal.Notify(c, os.Interrupt, syscall.SIGTERM); q r$ F: ^" Q, X
- go func() {/ q/ H( z) g4 i& P) g
- for sig := range c {
- fmt.Printf("captured %v, exiting...\n", sig)
- if cb != nil {
- cb(), Y+ u" Q7 U) z- u' m3 N4 z
- }5 Y8 [, @2 }0 S+ b; A( O
- os.Exit(1)7 b2 h, `3 N+ s @* n6 C
- }
- }(); M- Y7 F0 Y2 c
- select {}" m8 i' s9 a& D4 N4 I3 X A
- }
TrapSignal函数监听了SIGTERM信号,bytomd才能成为不退出的守护进程。只有当触发了ctrl+c或kill bytomd_pid才能终止bytomd进程退出。退出时bytomd执行如下操作
- ** node/node.go **# H+ O( e7 a! A) |
- func (n *Node) OnStop() {& k# ]; y3 d1 _2 q0 c! L8 [" Q
- n.BaseService.OnStop()$ {7 j( ?; y; r4 o( v/ z
- if n.miningEnable {. y6 C" w+ C) M; @- Y
- n.cpuMiner.Stop()
- }3 P0 k5 z+ z% T. S8 W6 g" J8 r
- n.syncManager.Stop(); X3 a. L2 g! ?6 | `- R
- log.Info("Stopping Node")
- // TODO: gracefully disconnect from peers.
- }
bytomd会将挖矿功能停止,p2p网络停止等操作。