Golang Version: 1.8
预备工作
编译安装
详细步骤见官方 bytom install
设置debug日志输出6 |; [3 d7 l$ X- D/ z
开启debug输出文件、函数、行号等详细信息: x* U& B( o9 ^9 a3 x O
- export BYTOM_DEBUG=debug
初始化并启动bytomd( U g! M8 I/ i% C! j
初始化
- ./bytomd init --chain_id testnet
bytomd目前支持两种网络,这里我们使用测试网 g, |& t8 G5 p9 ]
mainnet:主网
testnet:测试网
启动bytomd+ `# D5 @1 A. g$ J5 S( e
- ./bytomd node --mining --prof_laddr=":8011"
- –prof_laddr=":8080" // 开启pprof输出性能指标
访问:http://127.0.0.1:8080/debug/pprof/
bytomd init初始化- i! F4 b- b% B( z2 Z/ r D
入口函数
- ** cmd/bytomd/main.go **
- func init() {
- log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableColors: true})5 x r$ |5 L& z4 E% D: F6 K
- // If environment variable BYTOM_DEBUG is not empty,
- // then add the hook to logrus and set the log level to DEBUG: x1 D% f3 }! v, s/ c
- if os.Getenv("BYTOM_DEBUG") != "" {
- log.AddHook(ContextHook{})" J& C0 R8 a: Z* B3 i3 b# {
- log.SetLevel(log.DebugLevel)
- }. m* @! z- D+ w" h' a, O' W, }
- }
- func main() {
- cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))
- cmd.Execute()9 i6 a4 w3 r4 i4 M) k. c4 w
- }
init函数会在main执行之前做初始化操作,可以看到init中bytomd加载BYTOM_DEBUG变量来设置debug日志输出; s( T/ ~# c6 h* K4 f
command cli传参初始化( k7 L4 Q/ U# u: ^8 l
bytomd的cli解析使用cobra库" g" \6 G* ^8 A2 E& u6 a0 e
** cmd/bytomd/commands **
cmd/bytomd/commands/root.go/ Y5 Q6 u% s2 T9 y2 u9 O
初始化–root传参。bytomd存储配置、keystore、数据的root目录。在MacOS下,默认路径是~/Library/Bytom/cmd/bytomd/commands/init.go }. f* O2 X2 ]- s# z2 T h+ j
初始化–chain_id传参。选择网络类型,在启动bytomd时我们选择了testnet也就是测试网络cmd/bytomd/commands/version.go
初始化version传参cmd/bytomd/commands/run_node.go7 |" h# s& g2 @ k3 ]+ p9 p( h
初始化node节点运行时所需要的传参 x3 Y7 g! R# S1 Y$ }. M
初始化默认配置( d) p8 Z, q/ @2 Y& ?
用户传参只有一部分参数,那节点所需的其他参数需要从默认配置中加载。
- ** cmd/bytomd/commands/root.go **" Q, G) @; e: T! [! R2 z
- var (7 |( Y `8 v, m
- config = cfg.DefaultConfig()
- )5 n: a. S+ K- J& L! Z% d8 R, |
- 在root.go中有一个config全局变量加载了node所需的所有默认参数7 u- H& i9 a1 v9 {8 `* U3 W# S
- // Default configurable parameters.; F/ V+ ^2 E+ L8 r. s
- func DefaultConfig() *Config {3 e: q- ^, V7 Z+ |6 p( I+ F* O8 F/ {
- return &Config{
- BaseConfig: DefaultBaseConfig(), // node基础相关配置
- P2P: DefaultP2PConfig(), // p2p网络相关配置
- Wallet: DefaultWalletConfig(), // 钱包相关配置2 w) D# }2 h& }0 U& U7 P
- Auth: DefaultRPCAuthConfig(), // 验证相关配置
- Web: DefaultWebConfig(), // web相关配置2 Z" W9 V6 b: m, I. S; r' u
- }
- }
后面的文章会一一介绍每个配置的作用
bytomd 守护进程启动与退出
- ** cmd/bytomd/commands/run_node.go **
- func runNode(cmd *cobra.Command, args []string) error {% ~& y- l( x. t" W, s6 J
- // Create & start node1 ]! P7 ~' z0 t( B; d+ C- Z& Y
- n := node.NewNode(config)" ^- ?& q0 o* \5 ?- g) R" @& Y
- if _, err := n.Start(); err != nil {
- return fmt.Errorf("Failed to start node: %v", err)+ e6 X5 @+ ?+ I
- } else {: O+ a8 a$ }9 B6 \
- log.WithField("nodeInfo", n.SyncManager().Switch().NodeInfo()).Info("Started node")3 w7 L" d$ k4 Q7 H# f1 D9 B$ q
- }
- // Trap signal, run forever.% G7 ^2 }- U' p- ?' m4 L
- n.RunForever()9 O0 P C& _' K: [' ~
- return nil
- }
runNode函数有三步操作:
node.NewNode:初始化node运行环境
n.Start:启动node! \5 G8 E% R7 N1 F0 I8 P3 l
n.RunForever:监听退出信号,收到ctrl+c操作则退出node。在linux中守进程一般监听SIGTERM信号(ctrl+c)作为退出守护进程的信号
初始化node运行环境8 V, m4 f! }7 g3 h! [$ D9 u+ R, V
在bytomd中有五个db数据库存储在–root参数下的data目录4 m8 |+ _" X. I6 n3 U' A9 r6 I
accesstoken.db // 存储token相关信息(钱包访问控制权限)trusthistory.db // 存储p2p网络同步相关信息txdb.db // 存储交易相关信息txfeeds.db //wallet.db // 存储钱包相关信息
- ** node/node.go **
- func NewNode(config *cfg.Config) *Node {0 h+ z8 k! v! v n" y# ?& n
- ctx := context.Background()7 V) E0 i. J, C
- initActiveNetParams(config)
- // Get store 初始化txdb数据库5 D1 D6 d& T/ N5 w
- txDB := dbm.NewDB("txdb", config.DBBackend, config.DBDir())4 P; d% X ~/ Q
- store := leveldb.NewStore(txDB)2 [9 n' W9 N( m1 Q+ r
- // 初始化accesstoken数据库3 @$ n6 b8 }6 J
- tokenDB := dbm.NewDB("accesstoken", config.DBBackend, config.DBDir())1 C6 c. [# L2 Z( s
- accessTokens := accesstoken.NewStore(tokenDB)5 A( }; s/ o. t
- // 初始化event事件调度器,也叫任务调度器。一个任务可以被多次调用& L C$ z' R1 ~8 C% P7 o
- // Make event switch' \& z1 m3 @' l* \- @4 M/ I; N( P
- eventSwitch := types.NewEventSwitch(): \% P2 Q/ g& ~& | Y
- _, err := eventSwitch.Start()9 P; n$ b- d2 R! M! M
- if err != nil {
- cmn.Exit(cmn.Fmt("Failed to start switch: %v", err))1 k9 l, B( X7 W* [8 ?+ z7 a
- }! N' s/ i5 Z* e3 [9 \4 l5 O% [
- // 初始化交易池9 X' n& x# ~" \# i5 {
- txPool := protocol.NewTxPool()& h# z! S* F: \3 j1 ]& ~
- chain, err := protocol.NewChain(store, txPool)
- if err != nil {( m, \# k8 D# \$ x- d- S. C
- cmn.Exit(cmn.Fmt("Failed to create chain structure: %v", err))) r/ l7 R; S% a* B; Q* p/ H- C
- }' ^) K3 F# p: E f# J& e/ p, q. [
- var accounts *account.Manager = nil
- var assets *asset.Registry = nil
- var wallet *w.Wallet = nil
- var txFeed *txfeed.Tracker = nil
- // 初始化txfeeds数据库
- txFeedDB := dbm.NewDB("txfeeds", config.DBBackend, config.DBDir())0 k. Q% c- c7 b
- txFeed = txfeed.NewTracker(txFeedDB, chain)5 }5 Q2 f5 `0 S7 c M! W( s5 R1 A
- if err = txFeed.Prepare(ctx); err != nil {
- log.WithField("error", err).Error("start txfeed")& j5 e" ~4 C7 T: X
- return nil$ b' b0 m/ `, P) t5 G' P, u/ t2 B
- }
- // 初始化keystore$ A0 p9 M* S+ L7 U
- hsm, err := pseudohsm.New(config.KeysDir())0 M# H& k2 D9 \; Z5 i
- if err != nil {
- cmn.Exit(cmn.Fmt("initialize HSM failed: %v", err))
- }3 j% S8 u9 r% V- M& F) f
- // 初始化钱包,默认wallet是开启状态
- if !config.Wallet.Disable {* O# S/ @6 y# f4 s( r4 d- R
- walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir())" F1 u8 p' i$ |0 c* D6 H+ o' l7 J7 z
- accounts = account.NewManager(walletDB, chain)
- assets = asset.NewRegistry(walletDB, chain)
- wallet, err = w.NewWallet(walletDB, accounts, assets, hsm, chain)6 j# i9 H2 a. |
- if err != nil {
- log.WithField("error", err).Error("init NewWallet")- _1 Y3 }7 x7 e
- }" {9 Z4 N* g# V: f h
- // Clean up expired UTXO reservations periodically.
- go accounts.ExpireReservations(ctx, expireReservationsPeriod): \6 W" ]4 l G
- }
- newBlockCh := make(chan *bc.Hash, maxNewBlockChSize)! _. P* p: a* W0 o! Q0 u( I9 ^
- // 初始化网络节点同步管理
- syncManager, _ := netsync.NewSyncManager(config, chain, txPool, newBlockCh)8 `+ s- z+ ]1 K7 @# ]7 t$ \+ F
- // 初始化pprof,pprof用于输出性能指标,需要制定--prof_laddr参数来开启,在文章开头我们已经开启该功能
- // run the profile server$ v6 ?. S' f9 v
- profileHost := config.ProfListenAddress: @- G p7 s) j6 D" p2 {' F. n5 M
- if profileHost != "" {, I( u* d% Q4 Y/ i& u) P8 u
- // 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() {9 v2 p8 Z3 J9 [, t
- http.ListenAndServe(profileHost, nil)4 B0 R* H3 I! ^& I1 p7 o' I
- }()
- }
- // 初始化节点,填充节点所需的所有参数环境) a/ { m( y9 r. G
- node := &Node{8 J# i! {! I" X6 o
- config: config,
- syncManager: syncManager,- j8 `1 _0 u; @+ M! I" n6 [
- evsw: eventSwitch,& W( i" U& t" G8 G$ F- r$ A6 x% t) X
- accessTokens: accessTokens,
- wallet: wallet,# `& ]" ~, I. J, [
- chain: chain,
- txfeed: txFeed,4 w T% [% e/ M
- miningEnable: config.Mining,( Z; f' `4 Y* c
- }/ N$ O4 ^- c3 M* `! L
- // 初始化挖矿: M/ q; Z. [6 ]! F& ~. i
- node.cpuMiner = cpuminer.NewCPUMiner(chain, accounts, txPool, newBlockCh)7 n! V* [1 }8 H2 N2 o& v
- node.miningPool = miningpool.NewMiningPool(chain, accounts, txPool, newBlockCh)9 c- m; J5 u4 H6 v
- node.BaseService = *cmn.NewBaseService(nil, "Node", node)) A' L: J1 ~& Y
- return node* w8 i, X4 ~+ w% N9 \8 l! @8 P) h
- }
目前bytomd只支持cpu挖矿,所以在代码中只有cpuminer的初始化信息8 [! p0 |1 G, m0 K" h
启动node7 f2 T# ~8 z5 X9 V7 ^% H8 l* _
- ** node/node.go **
- // Lanch web broser or not# K+ ?2 V1 N4 ?. W
- func lanchWebBroser() {/ {/ N$ n3 |3 C/ n! m# U n6 h$ y
- log.Info("Launching System Browser with :", webAddress)
- if err := browser.Open(webAddress); err != nil {
- log.Error(err.Error())
- return
- }5 j( B6 m- B0 n( B" {
- }, ]1 w; Y* Y- ?5 D& `# X
- func (n *Node) initAndstartApiServer() {% N1 d9 i/ C+ w% n/ |2 E2 |
- n.api = api.NewAPI(n.syncManager, n.wallet, n.txfeed, n.cpuMiner, n.miningPool, n.chain, n.config, n.accessTokens) P& T9 Q; i8 p& Q/ G1 t
- listenAddr := env.String("LISTEN", n.config.ApiAddress)
- env.Parse()* n b A% u, ?7 ~$ g$ C" G! X
- n.api.StartServer(*listenAddr)' T& _0 E+ e5 N) J! I/ P: P+ n v
- }
- func (n *Node) OnStart() error {( a8 F' Y- s6 _5 B
- if n.miningEnable {
- n.cpuMiner.Start()# _# J z- X' y8 n$ d
- }2 X+ g* Y! L8 r4 |* c4 s) u
- n.syncManager.Start()) ]0 [7 f' v' [' o! b8 X! O, k `
- n.initAndstartApiServer()
- if !n.config.Web.Closed {
- lanchWebBroser()
- }
- return nil/ L: Y% x6 g" m
- }
OnStart() 启动node进程如下:9 \5 p5 K8 _2 W% Z* v" H1 j
启动挖矿功能启动p2p网络同步启动http协议的apiserver服务打开浏览器访问bytond的交易页面3 D" W g* s1 a% X+ d
停止node" L5 \( v+ ~8 o7 w& a6 z* p6 \
bytomd在启动时执行了n.RunForever()函数,该函数是由tendermint框架启动了监听信号的功能:
- ** vendor/github.com/tendermint/tmlibs/common/os.go **
- func TrapSignal(cb func()) {" ~1 T& l+ n' X7 K" q6 L
- c := make(chan os.Signal, 1)5 e+ P! Z& {( t+ i+ ^
- signal.Notify(c, os.Interrupt, syscall.SIGTERM)& H9 ^" b; v8 h) n1 {+ e1 ]
- go func() {
- for sig := range c {
- fmt.Printf("captured %v, exiting...\n", sig)
- if cb != nil {# q8 u! G6 e* s( Y7 b! ^0 r. j# }
- cb()
- }7 b5 V/ o, }! a$ x- ]) X
- os.Exit(1)
- }3 F' M ^# f* ]4 ~
- }() o$ G( O. Q6 S. D! f; t' y( ~4 I
- select {}1 F7 X- Y' O' Y" I C4 H3 G( m$ J
- }
TrapSignal函数监听了SIGTERM信号,bytomd才能成为不退出的守护进程。只有当触发了ctrl+c或kill bytomd_pid才能终止bytomd进程退出。退出时bytomd执行如下操作. A3 z, ]' ?4 S, E8 E; \& X6 U
- ** node/node.go **
- func (n *Node) OnStop() {+ W5 {! C2 r1 Q" E; j1 L. c
- n.BaseService.OnStop()
- if n.miningEnable {
- n.cpuMiner.Stop()
- }
- n.syncManager.Stop()
- log.Info("Stopping Node"), x' e+ p9 {; w- z9 o- \6 B
- // TODO: gracefully disconnect from peers.
- }
bytomd会将挖矿功能停止,p2p网络停止等操作。