Golang Version: 1.8* l$ g$ |2 ?3 {- X6 `
预备工作
编译安装
详细步骤见官方 bytom install5 p2 A; \4 _, a& L8 L$ W+ p2 q
设置debug日志输出
开启debug输出文件、函数、行号等详细信息 Y; e( F) w/ L1 b
- export BYTOM_DEBUG=debug
初始化并启动bytomd
初始化
- ./bytomd init --chain_id testnet
bytomd目前支持两种网络,这里我们使用测试网
mainnet:主网3 d! O& A6 x6 e- O: p! H
testnet:测试网
启动bytomd, l9 [2 C# a- I. Z
- ./bytomd node --mining --prof_laddr=":8011"
- –prof_laddr=":8080" // 开启pprof输出性能指标
访问:http://127.0.0.1:8080/debug/pprof/2 x% {2 {1 g9 \4 @& r
bytomd init初始化
入口函数
- ** cmd/bytomd/main.go **- @# I' S; {6 V: B
- func init() {9 o9 y: h+ z$ P# f. n
- log.SetFormatter(&log.TextFormatter{FullTimestamp: true, DisableColors: true})( k/ o3 [5 a/ L
- // If environment variable BYTOM_DEBUG is not empty,$ Q1 t3 i/ b) c3 K* g) W5 K3 J& ^
- // then add the hook to logrus and set the log level to DEBUG7 K4 x7 l0 E7 ~
- if os.Getenv("BYTOM_DEBUG") != "" {% _7 [. R. c2 u% B3 {- d7 `- C
- log.AddHook(ContextHook{})" k, B2 `, y- @7 F: E4 O) u' I: x
- log.SetLevel(log.DebugLevel)
- }
- }% E7 o7 \ ^* k0 v
- func main() {
- cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))
- cmd.Execute()4 v4 c8 w( I: L, r
- }
init函数会在main执行之前做初始化操作,可以看到init中bytomd加载BYTOM_DEBUG变量来设置debug日志输出6 Y( ^6 k1 L% n0 ^) K& V8 S
command cli传参初始化
bytomd的cli解析使用cobra库
** cmd/bytomd/commands **
cmd/bytomd/commands/root.go5 a5 a j: E( [
初始化–root传参。bytomd存储配置、keystore、数据的root目录。在MacOS下,默认路径是~/Library/Bytom/cmd/bytomd/commands/init.go
初始化–chain_id传参。选择网络类型,在启动bytomd时我们选择了testnet也就是测试网络cmd/bytomd/commands/version.go7 \) n9 J1 [9 x9 Y6 j1 {% P# h
初始化version传参cmd/bytomd/commands/run_node.go _2 n3 K# t$ L v
初始化node节点运行时所需要的传参( O/ V( c0 j$ q; }8 t$ \
初始化默认配置 ~( j# e& F# F1 ~+ s: y" {0 S
用户传参只有一部分参数,那节点所需的其他参数需要从默认配置中加载。
- ** cmd/bytomd/commands/root.go **/ V& u% r& y5 I/ K3 X# a
- var (
- config = cfg.DefaultConfig()
- )
- 在root.go中有一个config全局变量加载了node所需的所有默认参数% y4 t. k/ b5 N) J3 i/ F
- // Default configurable parameters.
- func DefaultConfig() *Config {6 I' \# Y a- } E5 e5 P
- return &Config{: Q/ j* I/ h9 r3 w2 y+ k" T
- BaseConfig: DefaultBaseConfig(), // node基础相关配置
- P2P: DefaultP2PConfig(), // p2p网络相关配置
- Wallet: DefaultWalletConfig(), // 钱包相关配置5 W' C. i7 X7 n+ ^1 B' `( }
- Auth: DefaultRPCAuthConfig(), // 验证相关配置! b3 g0 I0 r5 `6 e
- Web: DefaultWebConfig(), // web相关配置+ n: r" Y" J- ^3 |
- }
- }
后面的文章会一一介绍每个配置的作用/ j& e7 D- E# E. @
bytomd 守护进程启动与退出* L9 u* h3 ~: I9 {: G% [, q
- ** cmd/bytomd/commands/run_node.go **
- func runNode(cmd *cobra.Command, args []string) error {5 y6 t2 \4 N, D6 V! v C
- // Create & start node) k- |( }! g6 M/ R- K6 u; r, D. k1 {
- n := node.NewNode(config) E- u3 Y7 Y) N! ^2 B& d
- if _, err := n.Start(); err != nil {
- return fmt.Errorf("Failed to start node: %v", err)
- } else {% k4 H. e. c& Z }8 G
- log.WithField("nodeInfo", n.SyncManager().Switch().NodeInfo()).Info("Started node")
- }
- // Trap signal, run forever.
- n.RunForever()
- return nil% ~2 ^, b( t2 R) \5 H1 n7 L: F, z* D
- }
runNode函数有三步操作:0 ~8 L4 T& P$ {0 s2 @ I
node.NewNode:初始化node运行环境+ q& l& k$ d7 m! ~
n.Start:启动node
n.RunForever:监听退出信号,收到ctrl+c操作则退出node。在linux中守进程一般监听SIGTERM信号(ctrl+c)作为退出守护进程的信号! c |* U) u/ M1 c
初始化node运行环境
在bytomd中有五个db数据库存储在–root参数下的data目录
accesstoken.db // 存储token相关信息(钱包访问控制权限)trusthistory.db // 存储p2p网络同步相关信息txdb.db // 存储交易相关信息txfeeds.db //wallet.db // 存储钱包相关信息1 y& h& \ S( H6 ]
- ** node/node.go **4 D. B$ X# }) z3 `8 U" D- k4 ^
- func NewNode(config *cfg.Config) *Node {
- ctx := context.Background()
- initActiveNetParams(config)+ y" `/ A( F; d, R( C# {
- // Get store 初始化txdb数据库
- txDB := dbm.NewDB("txdb", config.DBBackend, config.DBDir()): T. `. g6 q5 g% d, Q
- store := leveldb.NewStore(txDB)) k+ Q0 Z7 B) c- z3 n7 A
- // 初始化accesstoken数据库4 `) l4 \) {1 H, o: h1 L. E: S
- tokenDB := dbm.NewDB("accesstoken", config.DBBackend, config.DBDir())0 p( `$ X$ d& g# |: w( P
- accessTokens := accesstoken.NewStore(tokenDB)% T8 \+ c( S, {* W% w5 u
- // 初始化event事件调度器,也叫任务调度器。一个任务可以被多次调用
- // Make event switch
- eventSwitch := types.NewEventSwitch()( t: O# G6 t" a( P- I( T8 j
- _, err := eventSwitch.Start()
- if err != nil {; E" X' i0 p* P6 y2 E
- cmn.Exit(cmn.Fmt("Failed to start switch: %v", err))+ k0 P# c3 \1 O, q. f
- }
- // 初始化交易池$ b! D, V, L4 U+ K2 f3 l9 v4 k
- txPool := protocol.NewTxPool()# d) i0 e! E1 P. R- s$ D9 ]
- chain, err := protocol.NewChain(store, txPool)
- if err != nil {
- cmn.Exit(cmn.Fmt("Failed to create chain structure: %v", err))
- }# {9 j- N2 w! b+ X( @# Q j1 U
- var accounts *account.Manager = nil
- var assets *asset.Registry = nil
- var wallet *w.Wallet = nil* m5 K. q; f/ m, z
- var txFeed *txfeed.Tracker = nil
- // 初始化txfeeds数据库
- txFeedDB := dbm.NewDB("txfeeds", config.DBBackend, config.DBDir())! Q$ f: c( U. ^* g1 ]
- txFeed = txfeed.NewTracker(txFeedDB, chain)
- if err = txFeed.Prepare(ctx); err != nil {) }/ M, q9 r( l5 c* F# b3 M ~3 t$ M4 z
- log.WithField("error", err).Error("start txfeed")
- return nil# |; K0 F5 q5 D7 W5 s) d
- }
- // 初始化keystore! U w4 x% _% u
- hsm, err := pseudohsm.New(config.KeysDir())* T& ~4 S" i; Y/ O
- if err != nil {
- cmn.Exit(cmn.Fmt("initialize HSM failed: %v", err))
- }1 J% p, |$ H }9 \
- // 初始化钱包,默认wallet是开启状态
- if !config.Wallet.Disable {, h4 A+ ^: f: ^* A
- walletDB := dbm.NewDB("wallet", config.DBBackend, config.DBDir())+ J' n2 x. l3 |2 Q2 u
- accounts = account.NewManager(walletDB, chain)- O1 }8 _6 z+ x' w$ C+ L1 c- J, S2 _
- assets = asset.NewRegistry(walletDB, chain)
- wallet, err = w.NewWallet(walletDB, accounts, assets, hsm, chain)& R8 n: {) A2 L$ y, D$ I
- if err != nil { s: D; \1 ]. F# e0 @" G8 M: }
- log.WithField("error", err).Error("init NewWallet")) b/ a; q: Q' y: c0 R
- }; \4 c, `5 H8 X# o* ]+ }& l9 N: }) M
- // Clean up expired UTXO reservations periodically.
- go accounts.ExpireReservations(ctx, expireReservationsPeriod)7 ?" ?& G7 m( D# l8 e
- }: x/ L) O" t; T& t P: P/ ~: X
- newBlockCh := make(chan *bc.Hash, maxNewBlockChSize). t' m9 k7 D( s& C$ R
- // 初始化网络节点同步管理
- syncManager, _ := netsync.NewSyncManager(config, chain, txPool, newBlockCh)
- // 初始化pprof,pprof用于输出性能指标,需要制定--prof_laddr参数来开启,在文章开头我们已经开启该功能
- // run the profile server
- profileHost := config.ProfListenAddress$ J2 P# k& F" Z. ]; z) C4 U) ?
- if profileHost != "" {2 l+ S" _' R. M g
- // 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/heap0 `' i a* l5 C* Q! f' q6 j
- go func() {" Q, H$ ?" u% z) X
- http.ListenAndServe(profileHost, nil)
- }()/ f: ]2 n; p* T7 F$ \4 V
- }' V x; P! l# d4 m& l
- // 初始化节点,填充节点所需的所有参数环境9 |$ | j( J5 v4 ?; V |- o
- node := &Node{* O' j' a1 S3 r$ _& ^
- config: config,5 Z3 s4 F) t8 z$ L8 S" R' ~3 V5 M
- syncManager: syncManager," h/ ?: p! i/ v7 o& |2 {
- evsw: eventSwitch,& z* y' V) a l! R
- accessTokens: accessTokens,# P- {6 T, S x
- wallet: wallet,
- chain: chain,
- txfeed: txFeed,
- miningEnable: config.Mining,
- }2 a' o9 s7 k1 Y/ y$ i: Q% r7 s7 P
- // 初始化挖矿) f& j4 R8 @) e3 k! X; N0 v7 m2 y
- node.cpuMiner = cpuminer.NewCPUMiner(chain, accounts, txPool, newBlockCh)0 n. b0 ]% m7 _
- node.miningPool = miningpool.NewMiningPool(chain, accounts, txPool, newBlockCh)6 `' m5 l9 ^/ w( E4 s! X- f
- node.BaseService = *cmn.NewBaseService(nil, "Node", node); G" W7 S. Y: v O5 s& W
- return node& |+ u5 P: n: ~0 v
- }
目前bytomd只支持cpu挖矿,所以在代码中只有cpuminer的初始化信息
启动node
- ** node/node.go **
- // Lanch web broser or not5 d) |4 ?& L$ S# p: T) c( s" q
- func lanchWebBroser() {
- log.Info("Launching System Browser with :", webAddress)
- if err := browser.Open(webAddress); err != nil {! K+ f) }& h8 M0 L6 C4 i: `
- log.Error(err.Error())
- return3 W8 z |; W5 j* u
- }3 v: c1 R% W4 P3 Z5 V" G+ [+ @3 o
- }
- func (n *Node) initAndstartApiServer() {
- n.api = api.NewAPI(n.syncManager, n.wallet, n.txfeed, n.cpuMiner, n.miningPool, n.chain, n.config, n.accessTokens)6 ^9 Y* P9 N. c; {/ r. ?2 s
- listenAddr := env.String("LISTEN", n.config.ApiAddress)( M. F/ c* q; v) E7 w
- env.Parse(): [7 T8 K1 W: C( g
- n.api.StartServer(*listenAddr); u" Y9 K( ]3 b
- }/ `) g, T6 P! h$ ^! Z0 o. h
- func (n *Node) OnStart() error {
- if n.miningEnable {
- n.cpuMiner.Start()$ ?" h% n+ K! P) e+ M; h/ O
- }
- n.syncManager.Start()7 I" w2 Q6 c2 E8 p% N5 u. r# e( n/ b9 Z
- n.initAndstartApiServer()
- if !n.config.Web.Closed {
- lanchWebBroser()
- }% ~) z' p L ?7 g2 ?/ f+ C6 M# [+ m
- return nil1 ~5 y) W/ l6 e9 Y6 k
- }
OnStart() 启动node进程如下:
启动挖矿功能启动p2p网络同步启动http协议的apiserver服务打开浏览器访问bytond的交易页面
3 c6 `8 i9 _8 }: @$ v7 R3 [+ D; ~
停止node
bytomd在启动时执行了n.RunForever()函数,该函数是由tendermint框架启动了监听信号的功能:3 W o. \9 g: e; Q. b
- ** vendor/github.com/tendermint/tmlibs/common/os.go **9 B# e3 F, r" J* o1 r
- func TrapSignal(cb func()) {, k& ^/ q, x" m0 k o
- c := make(chan os.Signal, 1)
- signal.Notify(c, os.Interrupt, syscall.SIGTERM)
- go func() {- l2 c% {3 ^/ p+ Y5 ]) t
- for sig := range c {
- fmt.Printf("captured %v, exiting...\n", sig)% c, y' _3 Q. e& t: o) X+ A9 q
- if cb != nil {
- cb(), E) n$ ]% E/ u; S9 V
- }: }9 f- I3 S6 E! K
- os.Exit(1)
- }0 m' o* [ Q3 L) ?( K: _+ |
- }(), M3 g9 e. O$ f
- select {}& r2 d+ b7 ~" u$ m& _% D" H! o
- }
TrapSignal函数监听了SIGTERM信号,bytomd才能成为不退出的守护进程。只有当触发了ctrl+c或kill bytomd_pid才能终止bytomd进程退出。退出时bytomd执行如下操作
- ** node/node.go **' ~+ E% B# o5 B9 G% X7 a
- func (n *Node) OnStop() {
- n.BaseService.OnStop()0 g2 q! @) s$ N: T! Q
- if n.miningEnable {
- n.cpuMiner.Stop()
- }5 Q! I, ]# R7 S- J
- n.syncManager.Stop()
- log.Info("Stopping Node")
- // TODO: gracefully disconnect from peers.0 c. J* E% U6 |% b7 w$ y3 I+ J0 v
- }
bytomd会将挖矿功能停止,p2p网络停止等操作。