Golang Version: 1.8
预备工作 N8 v5 C6 K8 q1 h0 `
编译安装0 }; [! J+ v3 Z' N
详细步骤见官方 bytom install
设置debug日志输出: B' _" ?0 d; B1 k) X
开启debug输出文件、函数、行号等详细信息( l0 {# C/ O9 a0 x; b; k& k( U2 s; O
- export BYTOM_DEBUG=debug
初始化并启动bytomd
初始化
- ./bytomd init --chain_id testnet
bytomd目前支持两种网络,这里我们使用测试网
mainnet:主网
testnet:测试网! P: i; M1 D) M* F' L" X
启动bytomd
- ./bytomd node --mining --prof_laddr=":8011"
- –prof_laddr=":8080" // 开启pprof输出性能指标
访问:http://127.0.0.1:8080/debug/pprof/
bytomd init初始化8 S5 ^% t$ }0 i7 g" b
入口函数& b1 ^1 r% k* s2 S
- ** cmd/bytomd/main.go ** v* o1 F2 G% C7 v
- func init() {
- log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableColors: true})
- // If environment variable BYTOM_DEBUG is not empty,( @ a; y6 b0 K" o2 l
- // then add the hook to logrus and set the log level to DEBUG
- if os.Getenv("BYTOM_DEBUG") != "" {
- log.AddHook(ContextHook{})5 D+ y- V0 a' t% E* z
- log.SetLevel(log.DebugLevel)
- }* i: n( s4 d5 h }6 G' M4 g- N; W
- }
- func main() {
- cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))+ v A9 l) A. @$ {0 @
- cmd.Execute()
- }
init函数会在main执行之前做初始化操作,可以看到init中bytomd加载BYTOM_DEBUG变量来设置debug日志输出
command cli传参初始化1 V* t+ E/ j6 R4 l( K* k9 D
bytomd的cli解析使用cobra库
** cmd/bytomd/commands **3 `0 ~8 @% c! A$ T$ I4 I+ L
cmd/bytomd/commands/root.go9 Z ?) @3 {; ?$ Z
初始化–root传参。bytomd存储配置、keystore、数据的root目录。在MacOS下,默认路径是~/Library/Bytom/cmd/bytomd/commands/init.go
初始化–chain_id传参。选择网络类型,在启动bytomd时我们选择了testnet也就是测试网络cmd/bytomd/commands/version.go+ |" ]6 M. o9 y! B1 D
初始化version传参cmd/bytomd/commands/run_node.go$ K7 B1 U! z0 K5 H& z
初始化node节点运行时所需要的传参
6 `: X- D) M) }! J# |- ^; }
初始化默认配置
用户传参只有一部分参数,那节点所需的其他参数需要从默认配置中加载。
- ** cmd/bytomd/commands/root.go **
- var (, ^: L; T- y: E# R
- config = cfg.DefaultConfig()
- )3 W. q. g6 `7 F9 E; Y
- 在root.go中有一个config全局变量加载了node所需的所有默认参数5 k9 F* u. y2 i2 S& ?% Q! n
- // Default configurable parameters.
- func DefaultConfig() *Config {
- return &Config{
- BaseConfig: DefaultBaseConfig(), // node基础相关配置
- P2P: DefaultP2PConfig(), // p2p网络相关配置
- Wallet: DefaultWalletConfig(), // 钱包相关配置3 Y w4 R8 Y" K h! p4 ~ N& N
- Auth: DefaultRPCAuthConfig(), // 验证相关配置2 j { F- ~$ x
- Web: DefaultWebConfig(), // web相关配置
- }/ B/ _, }" ^/ L1 w. q
- }
后面的文章会一一介绍每个配置的作用, C i7 C, W. r. `2 ?) l9 c
bytomd 守护进程启动与退出7 q Q6 d# o1 t, V# _
- ** cmd/bytomd/commands/run_node.go **
- func runNode(cmd *cobra.Command, args []string) error {# ^8 d" N8 v0 f% n c, j
- // Create & start node
- n := node.NewNode(config)
- if _, err := n.Start(); err != nil {
- return fmt.Errorf("Failed to start node: %v", err)
- } else {
- log.WithField("nodeInfo", n.SyncManager().Switch().NodeInfo()).Info("Started node")6 I- H6 R" y j1 o6 e" o' j+ P1 ]
- }( t r; u" K1 v/ N B$ A
- // Trap signal, run forever.
- n.RunForever()$ \' F2 E& Y2 m
- return nil
- }
runNode函数有三步操作:
node.NewNode:初始化node运行环境% J) d" z8 Z' \* t0 D8 I
n.Start:启动node1 [' |5 l+ N7 ?' ? X
n.RunForever:监听退出信号,收到ctrl+c操作则退出node。在linux中守进程一般监听SIGTERM信号(ctrl+c)作为退出守护进程的信号
初始化node运行环境
在bytomd中有五个db数据库存储在–root参数下的data目录
accesstoken.db // 存储token相关信息(钱包访问控制权限)trusthistory.db // 存储p2p网络同步相关信息txdb.db // 存储交易相关信息txfeeds.db //wallet.db // 存储钱包相关信息6 Q' s& p5 u& Q, x' l" x* g6 B8 i
- ** node/node.go **+ w2 A$ P' h* k
- func NewNode(config *cfg.Config) *Node {6 Q' D5 \* Z" h; v2 g9 H1 Y
- ctx := context.Background()6 U5 |# O) G: [' f8 a$ ]
- initActiveNetParams(config)' @) P3 _, G; u* Q2 K5 l
- // Get store 初始化txdb数据库+ @( w% m9 x9 d+ N
- txDB := dbm.NewDB("txdb", config.DBBackend, config.DBDir())! Z& y+ X& R. n. F/ q* }! ?
- store := leveldb.NewStore(txDB)
- // 初始化accesstoken数据库
- tokenDB := dbm.NewDB("accesstoken", config.DBBackend, config.DBDir())
- accessTokens := accesstoken.NewStore(tokenDB)
- // 初始化event事件调度器,也叫任务调度器。一个任务可以被多次调用- r5 ?. Y7 e0 _1 o5 ^* ^/ \* n
- // Make event switch
- eventSwitch := types.NewEventSwitch()
- _, err := eventSwitch.Start()
- if err != nil {- Z6 Z# G; K& ]$ n. t" S9 \
- cmn.Exit(cmn.Fmt("Failed to start switch: %v", err))7 r4 U$ T+ h: ^6 \6 ^6 x) ~+ N/ L, p
- }
- // 初始化交易池
- txPool := protocol.NewTxPool()
- chain, err := protocol.NewChain(store, txPool)
- if err != nil {
- cmn.Exit(cmn.Fmt("Failed to create chain structure: %v", err))
- }
- var accounts *account.Manager = nil1 z- ~2 J6 f8 G/ W
- var assets *asset.Registry = nil( `7 X1 V4 f( z7 v8 v9 C
- var wallet *w.Wallet = nil) z8 s1 Q3 Q8 I' v3 [+ y3 n. f1 E
- var txFeed *txfeed.Tracker = nil- I4 o6 U. A h8 Q5 u3 _
- // 初始化txfeeds数据库 M/ p- }2 e w0 q! r A, j: e
- txFeedDB := dbm.NewDB("txfeeds", config.DBBackend, config.DBDir())" L; R' S( v2 s0 q5 Z
- txFeed = txfeed.NewTracker(txFeedDB, chain)
- if err = txFeed.Prepare(ctx); err != nil {1 r' Q0 i: R* G: O4 l
- log.WithField("error", err).Error("start txfeed")' D* m/ ]! P/ ^' p4 S# {
- return nil
- }9 D0 k: \0 b9 I: p# l9 `
- // 初始化keystore9 I, K3 ^$ @: {, b+ ^
- hsm, err := pseudohsm.New(config.KeysDir()) L# [; i8 I' p: U. P, |4 H$ \! |
- if err != nil {0 \ g# O% W) H- {
- cmn.Exit(cmn.Fmt("initialize HSM failed: %v", err))
- }3 b" |6 R7 x- x
- // 初始化钱包,默认wallet是开启状态0 b+ F% V+ X3 I5 K( d6 v
- if !config.Wallet.Disable {
- walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir())* Z! W7 l& ?# }% Y
- accounts = account.NewManager(walletDB, chain) R6 Q3 C3 ?7 F1 I
- assets = asset.NewRegistry(walletDB, chain)
- wallet, err = w.NewWallet(walletDB, accounts, assets, hsm, chain)9 |$ z& i8 b3 o
- if err != nil {
- log.WithField("error", err).Error("init NewWallet")- T* @: r1 e% q* A4 J
- }
- // Clean up expired UTXO reservations periodically.
- go accounts.ExpireReservations(ctx, expireReservationsPeriod)
- }9 I4 a$ T: z6 ]( q
- newBlockCh := make(chan *bc.Hash, maxNewBlockChSize)' v3 K6 ~2 v7 c1 M1 E7 q3 Z
- // 初始化网络节点同步管理
- syncManager, _ := netsync.NewSyncManager(config, chain, txPool, newBlockCh)
- // 初始化pprof,pprof用于输出性能指标,需要制定--prof_laddr参数来开启,在文章开头我们已经开启该功能( A2 f% J5 I; }2 L
- // run the profile server; j) S/ u3 D+ N6 T( @3 c# o$ g7 ?
- profileHost := config.ProfListenAddress& Y" w, M, G5 Y8 o9 Y
- 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() {# X% \* P$ V; G" m" b6 }
- http.ListenAndServe(profileHost, nil)
- }()
- }
- // 初始化节点,填充节点所需的所有参数环境
- node := &Node{: h& E' d5 R. _ C
- config: config,
- syncManager: syncManager,; ?3 g5 `# y9 {% d- I
- evsw: eventSwitch,& {) u3 h+ k0 r' E; ^# \
- accessTokens: accessTokens,
- wallet: wallet,
- chain: chain,
- txfeed: txFeed,: u- H! l) }; a/ R8 v
- miningEnable: config.Mining,
- }
- // 初始化挖矿
- node.cpuMiner = cpuminer.NewCPUMiner(chain, accounts, txPool, newBlockCh)3 h0 {# \/ K" S
- node.miningPool = miningpool.NewMiningPool(chain, accounts, txPool, newBlockCh) T F/ y8 } S% B/ K
- node.BaseService = *cmn.NewBaseService(nil, "Node", node)$ ~8 F" P5 N* F" Z
- return node9 \0 N7 l0 X: T! H4 w7 V* N
- }
目前bytomd只支持cpu挖矿,所以在代码中只有cpuminer的初始化信息
启动node
- ** node/node.go **
- // Lanch web broser or not& x/ j% Y% j! m$ N; b
- func lanchWebBroser() {& _( O1 A! C6 h' b# G
- log.Info("Launching System Browser with :", webAddress)4 b' j- {+ e/ d6 W* d
- if err := browser.Open(webAddress); err != nil {) R6 E1 j. [0 K' }8 O$ L
- log.Error(err.Error())
- return6 U2 ^8 d; \% A& u( {3 t; [
- }* \' U7 O3 ]9 q6 ?4 Z9 j
- }9 ?" ~ F" J1 @* t. f! D' L7 w% P
- func (n *Node) initAndstartApiServer() {
- n.api = api.NewAPI(n.syncManager, n.wallet, n.txfeed, n.cpuMiner, n.miningPool, n.chain, n.config, n.accessTokens)& s5 c; i( C( K' T7 \4 ?7 T
- listenAddr := env.String("LISTEN", n.config.ApiAddress): G n$ k# O6 ?0 p7 T
- env.Parse()+ y- t0 M8 ?; w; V/ g! q7 n$ g
- n.api.StartServer(*listenAddr)2 r9 {$ S( x5 g; N4 j& y2 F
- }
- func (n *Node) OnStart() error {3 q, b4 T9 t3 W" Z3 k. Z$ P
- if n.miningEnable {/ K$ K1 g8 l5 @& |0 J
- n.cpuMiner.Start()
- }$ ^8 Z8 ?9 Y2 E+ f* I
- n.syncManager.Start()0 B- A, T% L4 r
- n.initAndstartApiServer()! G+ _' [1 C4 e
- if !n.config.Web.Closed {- T7 e, g4 I3 ?: [
- lanchWebBroser()
- }
- return nil; L* Z l) l$ X# b( }& G. I* b7 M9 f
- }
OnStart() 启动node进程如下:9 p& ^1 J/ P4 ?
启动挖矿功能启动p2p网络同步启动http协议的apiserver服务打开浏览器访问bytond的交易页面
$ _6 y) |( `( t* f1 p
停止node* r. _7 H1 a, g
bytomd在启动时执行了n.RunForever()函数,该函数是由tendermint框架启动了监听信号的功能:
- ** vendor/github.com/tendermint/tmlibs/common/os.go **
- func TrapSignal(cb func()) {5 I$ d: K# Y% L
- c := make(chan os.Signal, 1)+ o0 W+ t# R% {0 ^# u
- signal.Notify(c, os.Interrupt, syscall.SIGTERM), {+ Y( C- Z# K
- go func() {
- for sig := range c {
- fmt.Printf("captured %v, exiting...\n", sig)8 ^6 E3 G; u' \6 ~% F
- if cb != nil {* u4 | o9 s( I h) g* n) X
- cb()5 B7 r3 R- C% n
- }* W5 b% c' S& A3 p0 Z5 r
- os.Exit(1)
- }
- }()
- select {}' U: o9 z4 V) @6 C# w$ P7 H0 i
- }
TrapSignal函数监听了SIGTERM信号,bytomd才能成为不退出的守护进程。只有当触发了ctrl+c或kill bytomd_pid才能终止bytomd进程退出。退出时bytomd执行如下操作, P4 B$ V# X6 ~3 Z6 H
- ** node/node.go **
- func (n *Node) OnStop() {. H! [% \, _( p/ _9 ?) ]* q
- n.BaseService.OnStop()4 o z4 C, f$ ^' v! D
- if n.miningEnable {% B& S0 q' y( D4 L% ~
- n.cpuMiner.Stop()# r' _+ K5 J3 j3 I9 q0 M {
- }* F6 f9 T% w9 r+ z% D. t( ~
- n.syncManager.Stop()
- log.Info("Stopping Node")
- // TODO: gracefully disconnect from peers.
- }
bytomd会将挖矿功能停止,p2p网络停止等操作。