Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
人们常说,“阅读源代码”是学习编程的一种重要方法。作为程序员,我们在平时的学习工作中,都应该阅读过不少源代码。但是对于大多数人来说,阅读的可能更多是一些代码片断、示例,或者在老师、同事的指导下,先对要阅读的项目代码有了整体的了解之后,再进行针对性的阅读。2 J2 N5 K  g4 A/ V7 M' i9 C( w
但是如果我们面对的是一个像比原这样比较庞大的项目,身边又没有人指导,只能靠自己去看,这时应该怎么来阅读呢?也许每个人也都能找到自己的办法,或高效,或低效,或放弃。
7 o+ o( ^, O  {' x" c我在这次阅读比原源代码的过程中,尝试的是这样一种方法:从外部入手,通过与比原节点进行数据交互,来一步步了解比原的内部原理。就像剥石榴一样,一点点小心翼翼的下手,最后才能吃到鲜美的果肉。/ u9 J  |8 @9 @& P" h' H
所以这个文章系列叫作“剥开比原看代码”。# M/ y0 R" Q% K5 t% x
说明/ }! D" p2 `- Y* I$ M; ?5 Y
在系列中的每一章,我通常都会由一个或者几个相关的问题入手,然后通过对源代码进行分析,来说明比原的代码是如何实现的。对于与当前问题关系不大的代码,则会简单带过,等真正需要它们出场的时候再详细解说。' v1 F4 Y6 j  \* D0 B1 e/ g; \) w
为了保证文章中引用代码的稳定性,我将基于比原的v1.0.1代码进行分析。随着时间推移,比原的代码也将快速更新,但是我觉得,只要把这个版本的代码理解了,再去看新的代码,应该是一件很容易的事情。, |" ?* W3 h9 e5 c1 s% W4 t
在文章中,将会有一些直接指向github上bytom源代码的链接。为了方便,我专门将bytom v1.0.1的代码放到了一个新的仓库中,这样就不容易与比原官方的最新代码混淆。该仓库地址为:https://github.com/freewind/bytom-v1.0.17 k4 |  {1 Z/ K) b8 Y6 Y4 ?
当然,你不必clone这个仓库(clone官方仓库http://github.com/Bytom/bytom就够了),然后在必要的时候,使用以下命令将代码切换到v1.0.1的tag,以便与本系列引用的代码一致:
' [* G# I, [, J' q: Dgit fetch
' R) q0 ^' H7 \, j/ Y, wgit checkout -b v1.0.13 i, t( v8 V7 x) v
不论采用哪种阅读方法,我想第一步都应该先在本地把比原节点跑起来,试试各种功能。
/ t: C6 u% d( T- }. `. Q! K0 l对于如何下载、配置和安装的问题,请直接参看官方文档https://github.com/Bytom/bytom/tree/v1.0.1(注意我这里给出的是v1.0.1的文档),这里不多说。
0 _; D. M# ]+ Z, \- p4 |5 j" l本篇问题
; \6 T. A: D$ M% Z" c当我们本地使用make bytomd编译完比原后,我们可以使用下面的命令来进行初始化:1 Y  o- N; b# @7 y" l" n# l
./bytomd init --chain_id testnet5 K) }1 x3 D; G. `8 C8 G
这里指定了使用的chain是testnet(还有别的选项,如mainnet等等)。运行成功后,它将会在本地文件系统生成一些配置文件,供比原启动时使用。4 W; _( f! J4 s& K$ c
所以我的问题是:
* Q4 ?# L9 C5 g' E% A1 |' ?: E比原初始化时,产生了什么样的配置文件,放在了哪个目录下?
0 r/ Y8 e* g) w2 Z! f下面我将结合源代码,来回答这个问题。
. o% ?6 r" i# {" E; H! }9 h目录位置
4 {3 Y, R/ U# V; {4 t9 h首先比原在本地会有一个目录专门用于放置各种数据,比如密钥、配置文件、数据库文件等。这个目录对应的代码位于config/config.go#L190-L205:' m. Q! H7 N! S; B1 m! @
func DefaultDataDir() string {
$ U. Y0 r* f5 u) t2 [% d) r, A& m    // Try to place the data folder in the user's home dir
+ t% F, l" Y4 V6 ~5 n& ]    home := homeDir()3 G; D" O/ s  o& q# Y2 |  w
    dataDir := "./.bytom"- {( \  L4 Y9 t! W
    if home != "" {
" X! g3 `  f; x6 p9 {/ c        switch runtime.GOOS {
4 L( M* q+ Q. n1 l& @) n' R* B        case "darwin":
0 w' \0 r" r5 O6 }            dataDir = filepath.Join(home, "Library", "Bytom")# g3 Q1 p4 O' D- h9 C
        case "windows":
" w9 c" S- g6 U            dataDir = filepath.Join(home, "AppData", "Roaming", "Bytom")
. ~0 s; h, N% {% i! u  k        default:. e5 p! q1 n! R: Z. b6 o
            dataDir = filepath.Join(home, ".bytom")5 f+ n# q1 A) `" T+ T
        }
3 ^' t: R' J8 ^1 k, ^/ H! T+ t8 s    }7 p# K  L! G1 P& M
    return dataDir
; ~* y" y( N) i. m) w7 b# t}; A2 J" P) h! [) b/ k0 M6 ^- P( C
可以看到,在不同的操作系统上,数据目录的位置也不同:
" o$ h2 u, g6 W2 G9 q
  • 苹果系统(darwin):~/Library/Bytom
  • Windows(windows): ~/AppData/Roaming/Bytom
  • 其它(如Linux):~/.bytom
    ! o, m, _" h2 m' j! l; b* l4 z+ Y) U+ h
    配置文件内容
    8 x" r9 e3 _! \! v% r我们根据自己的操作系统打开相应的目录(我的是~/AppData/Roaming/Bytom),可以看到有一个config.toml,内容大约如下:( w1 a9 w- e: ]3 i8 N4 E( x
    $ cat config.toml+ M7 |  _' g; T( M, z
    # This is a TOML config file.
    5 M* W" \6 s/ {6 R# For more information, see https://github.com/toml-lang/toml
    0 s% @1 I! ]+ P3 l0 W( [! rfast_sync = true) @4 S. u$ R: `" \& h7 r% p) @
    db_backend = "leveldb"
    ) E( r5 b$ ~% s  j2 H$ L$ ~7 japi_addr = "0.0.0.0:9888"
    ; E6 f9 I2 W. d* ~chain_id = "testnet"
    7 G+ @+ h8 @% y- H& l9 a- Y1 d[p2p]
    8 l& I" }* k1 w- @laddr = "tcp://0.0.0.0:46656"
    8 ~' h3 _, L6 P) U, e  Eseeds = "47.96.42.1:46656,172.104.224.219:46656,45.118.132.164:46656"
    2 O2 _* Z; i, @/ t7 b; f它已经把一些基本信息告诉我们了,比如:( \/ I/ c9 y1 h+ B+ h; B: K
  • 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":比原启动后,会主动连接这几个地址获取数据5 b8 A- N( m; m
    ! Q6 x6 `. ~6 R, h
    内容模板( V! f6 V7 R& j
    使用不同的chain_id去初始化时,会生成不同内容的配置文件,那么这些内容来自于哪里呢?/ W+ Y3 i  q$ m  Q: e5 P
    原来在config/toml.go#L22-L45,预定义了不同的模板内容:
    # b; w( ]6 e: ^$ p% nvar defaultConfigTmpl = `# This is a TOML config file.
    ) R! X( M  j4 o- \* u. ~& l# For more information, see https://github.com/toml-lang/toml3 t  `. L" k8 j0 j/ S" v& [
    fast_sync = true* D8 T) O! M- }' O( _# X0 v! B
    db_backend = "leveldb"
    1 S( n+ R( K1 C/ k; V! b+ t4 vapi_addr = "0.0.0.0:9888"
    - s( ^* n; U$ z* _! J: P1 X`
    / k- |- ?. K. y7 d# |var mainNetConfigTmpl = `chain_id = "mainnet"
    2 u( W2 q3 W- s7 Y  g[p2p]+ P/ V1 q& [1 m6 z' [/ A# L7 P3 Y
    laddr = "tcp://0.0.0.0:46657"$ O0 S. ~2 V. T% p2 K6 o
    seeds = "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"
    * @" P0 ?4 S3 ?* b  R`
    0 o( F; F3 J0 g' [6 L( K9 g0 [; [var testNetConfigTmpl = `chain_id = "testnet"
    2 }4 q; y, g, p& v[p2p]
    ; E& Q% X# c2 s/ H* uladdr = "tcp://0.0.0.0:46656"
    ' Z2 a2 @( v0 S  M5 tseeds = "47.96.42.1:46656,172.104.224.219:46656,45.118.132.164:46656"/ u: }- s: H, M$ F
    `( e, M4 i( G- j8 ~/ ?  R
    var soloNetConfigTmpl = `chain_id = "solonet"! M5 B! n/ e+ a! K6 e: R; U" _
    [p2p]& \6 W; a1 f& Z) m* G
    laddr = "tcp://0.0.0.0:46658"
    2 ^$ j% c4 u% T! Q0 cseeds = ""
    & [1 g8 N7 J" j" A! H+ m9 l`/ U/ c4 I  S9 g1 }* a& Y
    可以看到,原来这些端口号和seed的地址,都是事先写好在模板里的。+ }3 o/ M: ^$ `, G# s' C/ Q
    而且,通过观察这些配置,我们可以发现,如果chain_id不同,则监听的端口和连接的种子都不同:
    8 i9 Q5 Q, }, }5 }# V1 e* `
  • mainnet(连接到主网): 46657,会主动连接6个种子
  • testnet(连接到测试网): 46656,会主动连接3个种子
  • solonet(本地单独节点): 46658,不会主动连接别人(也因此不会被别人连接上),适合单机研究" S- g) ?/ x9 L7 i7 T1 ?! J: z; L7 E
    6 @. o5 Q- X2 H4 e0 q, h1 {' E
    写入文件! ?# ~# i& H2 d) \) J+ u
    这里我们需要快速的把bytomd init的执行流程过一遍,才能清楚配置文件的写入时机,也同时把前面的内容串在了一起。
    * D: C* P; D. O0 U( a( f0 V( i( P首先,当我们运行bytomd init时,它对应的代码入口为cmd/bytomd/main.go#L54:/ _# d0 Q% G$ H  ]3 z+ |5 V! E
    func main() {2 w2 f5 w7 j0 I  H4 r7 {
        cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))9 K. P9 K7 W4 W. \
        cmd.Execute()
    * H% I, ~- h- c8 G+ ]; H& d9 u}- U5 W7 E9 b% V8 |2 W/ e: a
    其中的config.DefaultDataDir()就对应于前面提到数据目录位置。# ^/ f( ~2 t" z$ G  g0 F! b, m
    然后执行cmd.Execute(),将根据传入的参数init,选择下面的函数来执行:cmd/bytomd/commands/init.go#L25-L24: F* t7 W! Z0 N* r9 @0 p5 i; W) f. q
    func initFiles(cmd *cobra.Command, args []string) {
    3 U% n- ]" O  K+ M( n    configFilePath := path.Join(config.RootDir, "config.toml")- i& b; f6 @1 w) `, U+ P
        if _, err := os.Stat(configFilePath); !os.IsNotExist(err) {) ?1 x* y3 G; K. y/ n* `: [8 y
            log.WithField("config", configFilePath).Info("Already exists config file.")/ ^* |! d1 D$ ]2 f, _. |/ a7 w
            return
    5 c' K) s8 w, E' p! Y  `* S4 [    }
      g( ^- j& `! q    if config.ChainID == "mainnet" {( u$ R" Z; U  o; w
            cfg.EnsureRoot(config.RootDir, "mainnet")
    ; _5 Y* k/ d; y8 ]    } else if config.ChainID == "testnet" {$ C! x( u# r( f4 T& m2 D+ d5 ~
            cfg.EnsureRoot(config.RootDir, "testnet")% }5 Q) z( O* J, E' M8 H) r
        } else {  V& k1 f: N7 a. Q" A" R6 f
            cfg.EnsureRoot(config.RootDir, "solonet")) j" E7 A7 ?% j
        }
    : e9 _# D% d  Y( S+ c3 G    log.WithField("config", configFilePath).Info("Initialized bytom")
      T5 V) c6 _; l$ y}
    1 c  L5 @9 {3 N6 V) Z, s其中的configFilePath,就是config.toml的写入地址,即我们前面所说的数据目录下的config.toml文件。
    & S% K9 G5 u! R* O  E+ N) vcfg.EnsureRoot将用来确认数据目录是有效的,并且将根据传入的chain_id不同,来生成不同的内容写入到配置文件中。
    $ m4 Z6 ]$ m* t5 |. X" M" b; M' U它对应的代码是config/toml.go#L10- B9 |; o' S# k' q
    func EnsureRoot(rootDir string, network string) {5 n# Y3 d( ~5 @+ \
        cmn.EnsureDir(rootDir, 0700)
      q# y  M5 _$ i' i% H    cmn.EnsureDir(rootDir+"/data", 0700)2 P4 i( d7 r8 E, m
        configFilePath := path.Join(rootDir, "config.toml")0 S+ S- q7 H# p. O
        // Write default config file if missing.6 i: ?! L8 R4 P8 w( a( i- n0 k
        if !cmn.FileExists(configFilePath) {
    7 b( w, U4 _% |( j        cmn.MustWriteFile(configFilePath, []byte(selectNetwork(network)), 0644)
    , w6 Q4 w; p1 [4 t5 ]    }% L7 n2 m: @) P8 B
    }- z! e  N& j3 G
    可以看到,它对数据目录进行了权限上的确认,并且发现当配置文件存在的时候,不会做任何更改。所以如果我们需要生成新的配置文件,就需要把旧的删除(或改名)。# Z9 ]* U% |7 W' }$ r4 g. a+ U& ]- w
    其中的selectNetwork(network)函数,实现了根据chain_id的不同来组装不同的配置文件内容,它对应于master/config/toml.go#L48:
    : i3 L0 a- j0 T% c9 ~! N0 d* Yfunc selectNetwork(network string) string {
    + f2 n: [8 `$ G$ P; B+ X4 N$ ]! t7 E    if network == "testnet" {
    6 a3 q# `; X$ W! j4 Z* v        return defaultConfigTmpl + testNetConfigTmpl' J* ?8 G& X- n; C9 |1 g& G8 e
        } else if network == "mainnet" {3 V! i. D1 j& G7 S
            return defaultConfigTmpl + mainNetConfigTmpl. ~% O1 w: Z  t' E8 s
        } else {3 q8 j; C  b, t
            return defaultConfigTmpl + soloNetConfigTmpl
    3 N! ^6 ?, ?4 z, N- e* j    }6 f. J2 {$ Y9 E2 C: v+ L  f4 C
    }8 i1 @) t. I2 z1 z" a/ ]- X
    果然就是一个简单的字符串拼接,其中的defaultConfigTmpl和*NetConfgTmpl在前面已经出现,这里不重复。
    2 F+ h* n6 _- `) A5 U- z, ~最后调用第三方函数cmn.MustWriteFile(configFilePath, []byte(selectNetwork(network)), 0644),把拼接出来的配置文件内容以权限0644写入到指定的文件地址。
    % v2 e' y, r/ [3 ~+ `0 u, R到这里,我们这个问题就算回答完毕了。1 {9 l2 ?. R, U) C0 f4 S
    感谢社区用户Freewind的辛勤写作
  • BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
    声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    成为第一个吐槽的人

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

      0

    • 关注

      7

    • 主题

      11