Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
人们常说,“阅读源代码”是学习编程的一种重要方法。作为程序员,我们在平时的学习工作中,都应该阅读过不少源代码。但是对于大多数人来说,阅读的可能更多是一些代码片断、示例,或者在老师、同事的指导下,先对要阅读的项目代码有了整体的了解之后,再进行针对性的阅读。
/ l' n  |( |- [# V' @6 C8 h但是如果我们面对的是一个像比原这样比较庞大的项目,身边又没有人指导,只能靠自己去看,这时应该怎么来阅读呢?也许每个人也都能找到自己的办法,或高效,或低效,或放弃。
3 Z4 ~' X# P8 ^# R# d+ t我在这次阅读比原源代码的过程中,尝试的是这样一种方法:从外部入手,通过与比原节点进行数据交互,来一步步了解比原的内部原理。就像剥石榴一样,一点点小心翼翼的下手,最后才能吃到鲜美的果肉。
$ X! N8 ?3 o9 B所以这个文章系列叫作“剥开比原看代码”。
* e- Z  g. @( m7 j% \/ ^  e/ @( \说明
9 z* x1 r; C, O. l/ f在系列中的每一章,我通常都会由一个或者几个相关的问题入手,然后通过对源代码进行分析,来说明比原的代码是如何实现的。对于与当前问题关系不大的代码,则会简单带过,等真正需要它们出场的时候再详细解说。
. A9 R7 e& n4 c( O  X& a2 Z' ^, U为了保证文章中引用代码的稳定性,我将基于比原的v1.0.1代码进行分析。随着时间推移,比原的代码也将快速更新,但是我觉得,只要把这个版本的代码理解了,再去看新的代码,应该是一件很容易的事情。, q3 \  ^  a* b! `2 g+ t# J
在文章中,将会有一些直接指向github上bytom源代码的链接。为了方便,我专门将bytom v1.0.1的代码放到了一个新的仓库中,这样就不容易与比原官方的最新代码混淆。该仓库地址为:https://github.com/freewind/bytom-v1.0.19 I" p: h% l; m. d1 a- c, n0 v
当然,你不必clone这个仓库(clone官方仓库http://github.com/Bytom/bytom就够了),然后在必要的时候,使用以下命令将代码切换到v1.0.1的tag,以便与本系列引用的代码一致:. ~4 k3 }7 |3 \: Y/ Y
git fetch! f4 f% L* E' l: N
git checkout -b v1.0.1
& {. X9 C# J+ a6 F不论采用哪种阅读方法,我想第一步都应该先在本地把比原节点跑起来,试试各种功能。+ J2 g9 v: I; ]1 E1 y
对于如何下载、配置和安装的问题,请直接参看官方文档https://github.com/Bytom/bytom/tree/v1.0.1(注意我这里给出的是v1.0.1的文档),这里不多说。
0 [- _* O7 }4 \& v本篇问题
7 J0 `* Z' i' t3 R& O% x当我们本地使用make bytomd编译完比原后,我们可以使用下面的命令来进行初始化:( @# B% K, r$ h- i6 l4 Z" w
./bytomd init --chain_id testnet
$ [- T) Z2 ]. i8 z; Z. Z, A这里指定了使用的chain是testnet(还有别的选项,如mainnet等等)。运行成功后,它将会在本地文件系统生成一些配置文件,供比原启动时使用。
  d; {. E/ Y0 ~1 l5 @2 ]所以我的问题是:: k: v7 ~1 ?, {8 L. V' r
比原初始化时,产生了什么样的配置文件,放在了哪个目录下?' f7 |  `: {/ h% I, P
下面我将结合源代码,来回答这个问题。
2 ~+ O% J+ S6 N# F目录位置4 a( I1 Z" P- u$ @( G* T
首先比原在本地会有一个目录专门用于放置各种数据,比如密钥、配置文件、数据库文件等。这个目录对应的代码位于config/config.go#L190-L205:
. I- O4 K; p6 lfunc DefaultDataDir() string {7 v  c  k  v6 B7 d* H
    // Try to place the data folder in the user's home dir$ w7 p  {6 j! D+ C( ?% Z, ^
    home := homeDir()
3 r' o& y' ]7 G) X2 j6 G  ^* U    dataDir := "./.bytom"
* W& G# H4 Q" m' [, e# ^' Q. m    if home != "" {* t. P5 q9 z! F3 O/ u
        switch runtime.GOOS {7 r% X' \; [  @% l* `) T5 c% }
        case "darwin":
, v! p  p; x4 _) R1 R            dataDir = filepath.Join(home, "Library", "Bytom")
' P& l. L, h6 a% `2 G' n: [        case "windows":
8 k3 y/ O4 ?! _2 J/ ^! E6 G            dataDir = filepath.Join(home, "AppData", "Roaming", "Bytom")* e% M' p# e5 y1 p  j- M7 f1 l
        default:
# h: {/ Z: F( s. \) `9 i5 g            dataDir = filepath.Join(home, ".bytom")0 @& P9 K8 B3 K2 Y7 j4 k- y0 `9 f
        }
) l) [, w4 S4 H    }
/ V6 U6 X0 t7 B' t+ z    return dataDir
* ?" _" V: ?: h0 a: i0 K4 L}- Z3 f# T3 f9 `4 r
可以看到,在不同的操作系统上,数据目录的位置也不同:# i; ^1 h4 i; l* T
  • 苹果系统(darwin):~/Library/Bytom
  • Windows(windows): ~/AppData/Roaming/Bytom
  • 其它(如Linux):~/.bytom7 ]0 X3 Y6 F! m6 N. L% @( H. a( Y2 a3 V
    : @8 ?: E! C1 e  y$ N
    配置文件内容8 d' n; F. w/ d: B" h$ `5 T) Q
    我们根据自己的操作系统打开相应的目录(我的是~/AppData/Roaming/Bytom),可以看到有一个config.toml,内容大约如下:3 S  u/ _5 k* {
    $ cat config.toml
    , Y2 ^. j3 u2 m# Q. u9 c8 x# This is a TOML config file.! g! @: a# Q3 t8 u4 X
    # For more information, see https://github.com/toml-lang/toml2 ?0 A/ J  P. w) ]0 t
    fast_sync = true
    2 ^9 R4 c8 @7 \% x! \% Kdb_backend = "leveldb"8 Z& E1 C2 z  @1 {
    api_addr = "0.0.0.0:9888"# v, O1 ~$ w0 Z5 Y0 P# ?4 ?: [  ?
    chain_id = "testnet"
    & u+ |( e' V5 _7 ]% U& E! [% P[p2p]
    , h$ T# ~7 |: S: b& S. y# p4 u- Jladdr = "tcp://0.0.0.0:46656"8 S9 Q. k, x# V3 n
    seeds = "47.96.42.1:46656,172.104.224.219:46656,45.118.132.164:46656"
    $ ^- d5 b# ^. w+ `( F7 O. t/ Z" K它已经把一些基本信息告诉我们了,比如:7 z# ^, R, p: J2 {: |
  • db_backend = "leveldb":说明比原内部使用了leveldb作为数据库(用来保存块数据、帐号、交易信息等)
  • api_addr = "0.0.0.0:9888":我们可以在浏览器中打开http://localhost:9888来访问dashboard页面,进行查看与管理
  • chain_id = "testnet":当前连接的是testnet,即测试网,里面挖出来的比原币是不值钱的
  • laddr = "tcp://0.0.0.0:46656":本地监听46656端口,别的节点如果想连我,就需要访问我的46656端口
  • seeds = "47.96.42.1:46656,172.104.224.219:46656,45.118.132.164:46656":比原启动后,会主动连接这几个地址获取数据9 ^1 y. f+ t  T3 j5 W* Q
    % T" C  G; V) v$ ?
    内容模板/ f' J% B/ s+ a7 R0 ?8 J- r& \
    使用不同的chain_id去初始化时,会生成不同内容的配置文件,那么这些内容来自于哪里呢?
    : I/ u. r7 @0 W: U* E) F# ]  @原来在config/toml.go#L22-L45,预定义了不同的模板内容:
    6 B0 A% C# w0 G* Zvar defaultConfigTmpl = `# This is a TOML config file.
    - m: p) ^. L& g5 M5 m+ K6 M# For more information, see https://github.com/toml-lang/toml8 f# }) a7 [& n5 x9 a! ?8 Q# @
    fast_sync = true- O6 G% k( `( @* _3 V1 F- D
    db_backend = "leveldb"
    1 u8 B, i% f# u4 j: B' n9 wapi_addr = "0.0.0.0:9888"5 {# R) f, m3 A5 P
    `+ }  s( d* r! W8 _0 [) Q4 y
    var mainNetConfigTmpl = `chain_id = "mainnet"
    1 r- r8 w& \* U) S$ N. K[p2p]
    : h! e9 s. G+ Y* y  Q' S  Fladdr = "tcp://0.0.0.0:46657"
    9 D: N0 A: B& j8 b: k8 pseeds = "45.79.213.28:46657,198.74.61.131:46657,212.111.41.245:46657,47.100.214.154:46657,47.100.109.199:46657,47.100.105.165:46657"+ _# {$ d5 i; ]9 v" O7 T
    `
    7 [+ G( s/ `8 v3 jvar testNetConfigTmpl = `chain_id = "testnet"8 p- t+ c& e  ^: y+ \
    [p2p]% S. j( z3 ?& T3 U% H. e
    laddr = "tcp://0.0.0.0:46656"; A, J; f6 L5 F* ]/ N
    seeds = "47.96.42.1:46656,172.104.224.219:46656,45.118.132.164:46656"
    ) O0 ~7 @# n2 U4 h( [`& ~6 N9 D: ?! ]2 M& }( E
    var soloNetConfigTmpl = `chain_id = "solonet"1 p% v) d. u% C0 F
    [p2p]
    3 f" N0 K% L$ d0 Z" dladdr = "tcp://0.0.0.0:46658". O; p" ]7 ?4 D0 l; a. y
    seeds = ""
    # [: }) U1 g5 @% u( M`
    , [4 w: C; i: o9 g- ]可以看到,原来这些端口号和seed的地址,都是事先写好在模板里的。7 o3 X* i9 f6 `8 \6 e. a5 A: C) F- Y
    而且,通过观察这些配置,我们可以发现,如果chain_id不同,则监听的端口和连接的种子都不同:, t2 `. }' v0 j# o  q8 n6 z$ k
  • mainnet(连接到主网): 46657,会主动连接6个种子
  • testnet(连接到测试网): 46656,会主动连接3个种子
  • solonet(本地单独节点): 46658,不会主动连接别人(也因此不会被别人连接上),适合单机研究
    ) J4 G- @# R% q" H3 {) W+ T$ S6 C+ t2 m3 D+ x( x9 F4 }* G
    写入文件
    + o3 w5 Y- y8 v6 h+ g+ B, j& B这里我们需要快速的把bytomd init的执行流程过一遍,才能清楚配置文件的写入时机,也同时把前面的内容串在了一起。
    * n$ M6 M) a, i0 y+ }6 x( W9 S9 Q首先,当我们运行bytomd init时,它对应的代码入口为cmd/bytomd/main.go#L54:
    + b. t7 A" ^1 b& vfunc main() {
    8 E) \9 m, B- j" N$ I' u    cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))
    ' {) I" Z. q6 O4 t" I    cmd.Execute()
    - M. Y9 b- u) U9 M9 h8 L}  Z& a; D. `+ t+ u0 d
    其中的config.DefaultDataDir()就对应于前面提到数据目录位置。
    4 D& n9 y$ d/ ]7 ]然后执行cmd.Execute(),将根据传入的参数init,选择下面的函数来执行:cmd/bytomd/commands/init.go#L25-L246 a; P( {$ p( \' D# N9 w2 V: J
    func initFiles(cmd *cobra.Command, args []string) {8 \' E1 y5 V8 v- Q
        configFilePath := path.Join(config.RootDir, "config.toml")
    0 z  Y6 y$ Y  Q7 G  W* h    if _, err := os.Stat(configFilePath); !os.IsNotExist(err) {
    ( V3 ^' {& |/ O' o        log.WithField("config", configFilePath).Info("Already exists config file.")
    ' L  @' c0 b+ a2 t. s' n        return9 y" Z1 k9 U' L0 f
        }6 e" C+ c' ?! @8 D
        if config.ChainID == "mainnet" {5 ?+ q  ^: y/ v7 \) C" H
            cfg.EnsureRoot(config.RootDir, "mainnet")- n+ [' F9 @* m3 p, \# m9 v- o
        } else if config.ChainID == "testnet" {4 X, ?, x2 O( Q7 L) Y( P6 A
            cfg.EnsureRoot(config.RootDir, "testnet")6 r, q- P, Z/ [. o$ D
        } else {# ?& H; z6 u, a$ @- S
            cfg.EnsureRoot(config.RootDir, "solonet")& `2 y  I  \6 `/ {$ e# s4 Z* K
        }
    + P! R2 Y' G& Z2 b1 N& P: u1 B    log.WithField("config", configFilePath).Info("Initialized bytom")
    ) p( R$ x' C0 r' O3 e% A$ T}
    - F3 Q0 }0 C$ {' {. l其中的configFilePath,就是config.toml的写入地址,即我们前面所说的数据目录下的config.toml文件。
    ) @/ F3 M% a& d. J* V# ?cfg.EnsureRoot将用来确认数据目录是有效的,并且将根据传入的chain_id不同,来生成不同的内容写入到配置文件中。
    & t: r  C/ H8 q  e. A4 T# a它对应的代码是config/toml.go#L10
    ) T/ P5 z0 C+ U3 wfunc EnsureRoot(rootDir string, network string) {5 w+ }4 W  p) ~
        cmn.EnsureDir(rootDir, 0700)/ `( G0 P3 t% ]* F) ~8 V' V
        cmn.EnsureDir(rootDir+"/data", 0700)2 Z. t9 M9 v. ?. k( e* |
        configFilePath := path.Join(rootDir, "config.toml")
    . @6 C4 M3 Y6 |* Y: r* P    // Write default config file if missing.7 E& V3 {- b; T# Q
        if !cmn.FileExists(configFilePath) {: c* w; c# c5 \4 Q& Z
            cmn.MustWriteFile(configFilePath, []byte(selectNetwork(network)), 0644)7 M: V$ q4 U9 g- z% Z. _
        }
    ) V, Q, n! @1 J* H  j: @$ l+ m8 j}/ ], N7 @% Q* z' A" t# u7 l
    可以看到,它对数据目录进行了权限上的确认,并且发现当配置文件存在的时候,不会做任何更改。所以如果我们需要生成新的配置文件,就需要把旧的删除(或改名)。
    ' j3 ~4 b6 c- G+ g$ Q其中的selectNetwork(network)函数,实现了根据chain_id的不同来组装不同的配置文件内容,它对应于master/config/toml.go#L48:" E2 X1 p. }. Y. c: D
    func selectNetwork(network string) string {& {2 e1 |; S1 R) Q1 B, Z
        if network == "testnet" {
    * |! b1 a/ A8 u- g: G        return defaultConfigTmpl + testNetConfigTmpl
    1 v8 d/ L2 u: v  t    } else if network == "mainnet" {
    8 n- F2 i2 u0 [7 A. y        return defaultConfigTmpl + mainNetConfigTmpl
    4 q3 m* t; e7 x, {- N* V    } else {' k+ ~' O1 s9 }7 g  H6 A5 S; f
            return defaultConfigTmpl + soloNetConfigTmpl6 ^& }, g6 l( X) H! {6 k. Q
        }% F( `4 n; t9 k/ E
    }' R9 S' }; A! ?; `/ i: k
    果然就是一个简单的字符串拼接,其中的defaultConfigTmpl和*NetConfgTmpl在前面已经出现,这里不重复。! v) y9 |4 R* L
    最后调用第三方函数cmn.MustWriteFile(configFilePath, []byte(selectNetwork(network)), 0644),把拼接出来的配置文件内容以权限0644写入到指定的文件地址。: w4 a9 O& y  i* H
    到这里,我们这个问题就算回答完毕了。
    , W7 V6 J, @0 v感谢社区用户Freewind的辛勤写作
  • BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
    声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    成为第一个吐槽的人

    一夜雨十年灯潞 初中生
    • 粉丝

      0

    • 关注

      7

    • 主题

      11