Golang Version: 1.8
# V* v! m* J) y# d+ k7 ?
预备工作) [7 O- E! s3 ^2 Y) I
编译安装! F1 v+ G* A, p `
详细步骤见官方 bytom install F' D$ x! H- ]% w9 ~
设置debug日志输出
开启debug输出文件、函数、行号等详细信息3 p4 V" _5 J3 x. _) `3 h
- export BYTOM_DEBUG=debug
初始化并启动bytomd+ ` ^& m: q6 A, _, e" {
初始化
- ./bytomd init --chain_id testnet
bytomd目前支持两种网络,这里我们使用测试网/ j& Q' y5 W* L' A' r \
mainnet:主网& |" {! }& Q; J4 s$ ^% o
testnet:测试网
启动bytomd; b/ G! R) O; q! R" o, C/ J3 s
- ./bytomd node --mining --prof_laddr=":8011"
- –prof_laddr=":8080" // 开启pprof输出性能指标
访问:http://127.0.0.1:8080/debug/pprof/
bytomd init初始化
入口函数( ]! I% |& A3 x* y$ d% b
- ** cmd/bytomd/main.go **
- func init() {) X- h) V8 R# [& p' ~& P+ d
- log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableColors: true})1 E1 Q9 F0 h" h/ u+ G
- // 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") != "" {8 B! ^ X5 o3 f1 k8 O- `4 A* K$ m
- log.AddHook(ContextHook{})
- log.SetLevel(log.DebugLevel). y7 K( H# p3 `6 y+ j
- }
- }
- func main() {
- cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))
- cmd.Execute()
- }
init函数会在main执行之前做初始化操作,可以看到init中bytomd加载BYTOM_DEBUG变量来设置debug日志输出
command cli传参初始化/ u" X5 k N) Q G# Q* [
bytomd的cli解析使用cobra库, r) K5 X' _1 s+ I' E3 ]( V- P
** cmd/bytomd/commands **0 G. C' v% u" Y: R: Y1 q
cmd/bytomd/commands/root.go
初始化–root传参。bytomd存储配置、keystore、数据的root目录。在MacOS下,默认路径是~/Library/Bytom/cmd/bytomd/commands/init.go* I% D! _. b5 F* g1 P+ L
初始化–chain_id传参。选择网络类型,在启动bytomd时我们选择了testnet也就是测试网络cmd/bytomd/commands/version.go% o: Z: O; `# n! [8 N8 I6 ^
初始化version传参cmd/bytomd/commands/run_node.go7 b" J! S8 H4 J, o9 N, A
初始化node节点运行时所需要的传参
* J) I n9 }; f: |( ~. A) F
初始化默认配置$ m% P! W0 Y. N/ G* f3 r% T B
用户传参只有一部分参数,那节点所需的其他参数需要从默认配置中加载。& g) i. p0 [; Q/ M1 B0 }) n: w
- ** cmd/bytomd/commands/root.go **2 Y; e C, w d
- var (( A: d) m4 E: k4 X
- config = cfg.DefaultConfig()" c" C0 E0 }8 q; Q! z4 j# G* ~
- )
- 在root.go中有一个config全局变量加载了node所需的所有默认参数) S- \$ T: I# ~! J+ z1 t% d
- // Default configurable parameters.
- func DefaultConfig() *Config {8 u/ _7 Z$ ]! x3 {
- return &Config{) w, r8 {2 m" U( y3 _! V. @7 a
- BaseConfig: DefaultBaseConfig(), // node基础相关配置) }' |2 V- l m6 ~3 B
- P2P: DefaultP2PConfig(), // p2p网络相关配置2 N1 H+ U. n& G9 A! r
- Wallet: DefaultWalletConfig(), // 钱包相关配置1 p7 K" p! E0 c; I0 v
- Auth: DefaultRPCAuthConfig(), // 验证相关配置
- Web: DefaultWebConfig(), // web相关配置 h2 Z4 f/ A2 C K6 X3 q: f
- }/ }( D+ G+ B2 i8 W' t
- }
后面的文章会一一介绍每个配置的作用 D9 Z& z7 g# E2 N% n4 d
bytomd 守护进程启动与退出# H: g* ~, h! F# F4 c
- ** cmd/bytomd/commands/run_node.go **, Y, p1 q+ {5 I8 H2 C t0 q. ?; b l
- func runNode(cmd *cobra.Command, args []string) error {
- // Create & start node
- n := node.NewNode(config)
- if _, err := n.Start(); err != nil {1 ~" A8 M. t3 S! `$ H' g. n* z' }, Z
- return fmt.Errorf("Failed to start node: %v", err)
- } else {" \$ z3 @9 q1 A9 Y
- log.WithField("nodeInfo", n.SyncManager().Switch().NodeInfo()).Info("Started node")2 M4 x/ A6 a! ?. l3 c9 y
- }
- // Trap signal, run forever.. S4 I! `4 ^# U( s
- n.RunForever()' m3 Q0 [* O( t# ~
- return nil4 s* R- ]& s) x" s! A2 Z
- }
runNode函数有三步操作:
node.NewNode:初始化node运行环境
n.Start:启动node
n.RunForever:监听退出信号,收到ctrl+c操作则退出node。在linux中守进程一般监听SIGTERM信号(ctrl+c)作为退出守护进程的信号
初始化node运行环境# C7 R' B1 D! ]# D) W6 Z
在bytomd中有五个db数据库存储在–root参数下的data目录
accesstoken.db // 存储token相关信息(钱包访问控制权限)trusthistory.db // 存储p2p网络同步相关信息txdb.db // 存储交易相关信息txfeeds.db //wallet.db // 存储钱包相关信息
- ** node/node.go **
- func NewNode(config *cfg.Config) *Node {2 E1 X! s1 R: H
- ctx := context.Background()
- initActiveNetParams(config)5 p% X2 A! k2 z3 z: I t) b! A9 v
- // Get store 初始化txdb数据库, S! P" B1 s" Q+ w6 J$ n9 T# Y) f
- txDB := dbm.NewDB("txdb", config.DBBackend, config.DBDir())
- store := leveldb.NewStore(txDB)/ j1 w' Y% [* B7 y& V
- // 初始化accesstoken数据库1 E5 \/ V/ C9 C( o
- tokenDB := dbm.NewDB("accesstoken", config.DBBackend, config.DBDir()). L0 A- V5 M$ ]/ [
- accessTokens := accesstoken.NewStore(tokenDB)
- // 初始化event事件调度器,也叫任务调度器。一个任务可以被多次调用
- // Make event switch* p8 I4 i6 K4 x: S
- eventSwitch := types.NewEventSwitch()# `3 H# \8 V2 x. d, o
- _, err := eventSwitch.Start()7 O1 N/ ]8 W9 W9 W# q) t
- if err != nil {
- cmn.Exit(cmn.Fmt("Failed to start switch: %v", err))
- }
- // 初始化交易池
- txPool := protocol.NewTxPool()& V" i" i9 `5 r
- chain, err := protocol.NewChain(store, txPool)
- if err != nil {) c/ N1 H1 a) F# d
- cmn.Exit(cmn.Fmt("Failed to create chain structure: %v", err))/ R; w* d) S/ x- `: E
- }' Z9 k9 z# x0 h* d( ]1 R
- var accounts *account.Manager = nil
- var assets *asset.Registry = nil( [% d8 i* `& s6 l
- var wallet *w.Wallet = nil
- var txFeed *txfeed.Tracker = nil
- // 初始化txfeeds数据库1 g1 F! Z! R8 L
- txFeedDB := dbm.NewDB("txfeeds", config.DBBackend, config.DBDir())' y. _ U3 p$ l* e5 ~- {
- txFeed = txfeed.NewTracker(txFeedDB, chain)
- if err = txFeed.Prepare(ctx); err != nil { E, \- @3 O% L+ E! L- E
- log.WithField("error", err).Error("start txfeed")
- return nil
- } d( o4 ]% b. T! L4 H" X6 W$ a, X
- // 初始化keystore8 G2 u- L) [8 r: ?) H0 ?' U
- hsm, err := pseudohsm.New(config.KeysDir())
- if err != nil {
- cmn.Exit(cmn.Fmt("initialize HSM failed: %v", err))
- }
- // 初始化钱包,默认wallet是开启状态
- if !config.Wallet.Disable {- R+ ]! v4 C8 y5 Q5 a
- walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir()). l2 y9 s5 I8 \) _
- accounts = account.NewManager(walletDB, chain); ] \: Z6 i b5 A( t& J: z4 w( V
- assets = asset.NewRegistry(walletDB, chain)
- wallet, err = w.NewWallet(walletDB, accounts, assets, hsm, chain)& {! w' `+ U5 @3 E" ]
- if err != nil {
- log.WithField("error", err).Error("init NewWallet")4 e1 ?% c: ?! W4 M) W6 J! E) i
- }
- // Clean up expired UTXO reservations periodically.8 e3 z8 H7 g4 A2 E9 ^ ]9 ]9 y0 f9 w
- go accounts.ExpireReservations(ctx, expireReservationsPeriod)' x& a1 {& \" ^% Q0 w/ v
- }
- newBlockCh := make(chan *bc.Hash, maxNewBlockChSize)' b$ K7 H/ p. b# J
- // 初始化网络节点同步管理
- syncManager, _ := netsync.NewSyncManager(config, chain, txPool, newBlockCh)% i, s6 w( s! d+ l+ O* G
- // 初始化pprof,pprof用于输出性能指标,需要制定--prof_laddr参数来开启,在文章开头我们已经开启该功能
- // run the profile server" ]/ _+ m, k7 |4 }$ ]& ]) m, i5 }4 d& {
- profileHost := config.ProfListenAddress
- 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>)2 ^& a/ w& d( Y8 G9 [3 g
- // go tool pprof http://profileHose/debug/pprof/heap
- go func() {
- http.ListenAndServe(profileHost, nil)& p" K! x) s0 A$ Q
- }()
- }
- // 初始化节点,填充节点所需的所有参数环境
- node := &Node{4 H) f/ n7 h) C" x, N
- config: config,
- syncManager: syncManager,$ Q8 A( @5 n! v4 y
- evsw: eventSwitch,
- accessTokens: accessTokens,3 u, w0 w- g( `
- wallet: wallet,9 K$ p% { R( U4 |; P% `+ N1 `: ^) M8 b0 I
- chain: chain,
- txfeed: txFeed,& v, |& u; U; c& ~9 X h( E4 w- O
- miningEnable: config.Mining,
- }
- // 初始化挖矿
- node.cpuMiner = cpuminer.NewCPUMiner(chain, accounts, txPool, newBlockCh)
- node.miningPool = miningpool.NewMiningPool(chain, accounts, txPool, newBlockCh)
- node.BaseService = *cmn.NewBaseService(nil, "Node", node)
- return node2 m! h( l1 x$ G3 j2 i
- }
目前bytomd只支持cpu挖矿,所以在代码中只有cpuminer的初始化信息- N! }9 |+ c* ?, D2 E
启动node" E) s9 k( r, V9 ^6 a# l5 N
- ** node/node.go **/ s# F( |! T h4 J M4 ?
- // Lanch web broser or not
- func lanchWebBroser() {
- log.Info("Launching System Browser with :", webAddress)+ Z2 {) k" A" m5 g9 w3 @
- if err := browser.Open(webAddress); err != nil {+ h; e8 R2 j; A" b* I' G5 s9 E
- log.Error(err.Error())
- return
- }/ q. @: e/ G) w% }% N- W3 F
- }' o8 b& @& ?/ I) ]1 j
- func (n *Node) initAndstartApiServer() {# D" W) j* `6 F1 D# Z% ]" y4 V
- n.api = api.NewAPI(n.syncManager, n.wallet, n.txfeed, n.cpuMiner, n.miningPool, n.chain, n.config, n.accessTokens)9 c D% L) \8 s; Y( d6 Q( z
- listenAddr := env.String("LISTEN", n.config.ApiAddress)
- env.Parse()
- n.api.StartServer(*listenAddr)5 }& O6 i, \+ u; F2 Z6 j
- }( ~6 v2 C3 H- l
- func (n *Node) OnStart() error {' x; h$ m9 O7 V2 o# w
- if n.miningEnable {
- n.cpuMiner.Start()
- }9 ` V% q2 C% O5 n2 i% j
- n.syncManager.Start()
- n.initAndstartApiServer()/ ?/ \+ Y; h- g
- if !n.config.Web.Closed {
- lanchWebBroser()& F- ^" g8 R5 [- Z
- }
- return nil3 m$ i: U0 ?0 l1 q
- }
OnStart() 启动node进程如下:
启动挖矿功能启动p2p网络同步启动http协议的apiserver服务打开浏览器访问bytond的交易页面( J; g9 ]! Y4 a8 G, b4 J
5 i7 i. k, N( Y3 g4 S0 u
停止node
bytomd在启动时执行了n.RunForever()函数,该函数是由tendermint框架启动了监听信号的功能:& O. B/ ^4 M! P4 W3 ^
- ** vendor/github.com/tendermint/tmlibs/common/os.go **& n8 x2 X z6 u
- func TrapSignal(cb func()) {
- c := make(chan os.Signal, 1)3 p [0 y% h+ ^7 w' ]
- signal.Notify(c, os.Interrupt, syscall.SIGTERM)
- go func() {5 U# Z9 K3 X# @# U. F
- for sig := range c {
- fmt.Printf("captured %v, exiting...\n", sig)7 U3 G: Q. x5 T; D$ ^% @) I
- if cb != nil {
- cb()
- }. s* A! N6 Z& R
- os.Exit(1)5 ~) A4 O5 {- o
- }5 a1 o6 q2 I- O
- }()4 @+ R( S) L5 b
- select {}
- }
TrapSignal函数监听了SIGTERM信号,bytomd才能成为不退出的守护进程。只有当触发了ctrl+c或kill bytomd_pid才能终止bytomd进程退出。退出时bytomd执行如下操作
- ** node/node.go **
- func (n *Node) OnStop() {
- n.BaseService.OnStop()% e q* d6 c4 K6 p& k, m
- if n.miningEnable {1 o6 A# R9 f% A# e
- n.cpuMiner.Stop()' A' A/ |9 ?7 F. R7 m
- }) J6 H. w) u& E9 B8 n# i
- n.syncManager.Stop()% d0 s" ~) Z4 U* I, A7 g
- log.Info("Stopping Node")
- // TODO: gracefully disconnect from peers.
- }
bytomd会将挖矿功能停止,p2p网络停止等操作。