Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文
人们常说,“阅读源代码”是学习编程的一种重要方法。作为程序员,我们在平时的学习工作中,都应该阅读过不少源代码。但是对于大多数人来说,阅读的可能更多是一些代码片断、示例,或者在老师、同事的指导下,先对要阅读的项目代码有了整体的了解之后,再进行针对性的阅读。
/ j. j# z0 k: r  {" n8 X但是如果我们面对的是一个像比原这样比较庞大的项目,身边又没有人指导,只能靠自己去看,这时应该怎么来阅读呢?也许每个人也都能找到自己的办法,或高效,或低效,或放弃。
) u0 j  V; C1 I) J* U( S我在这次阅读比原源代码的过程中,尝试的是这样一种方法:从外部入手,通过与比原节点进行数据交互,来一步步了解比原的内部原理。就像剥石榴一样,一点点小心翼翼的下手,最后才能吃到鲜美的果肉。+ @: e% u- i6 D1 h% v# R
所以这个文章系列叫作“剥开比原看代码”。8 Z& U* I! T0 ^9 P
说明/ ?4 A3 u7 W* Y9 g# z) `
在系列中的每一章,我通常都会由一个或者几个相关的问题入手,然后通过对源代码进行分析,来说明比原的代码是如何实现的。对于与当前问题关系不大的代码,则会简单带过,等真正需要它们出场的时候再详细解说。
+ H) o, H- p+ Y4 ^为了保证文章中引用代码的稳定性,我将基于比原的v1.0.1代码进行分析。随着时间推移,比原的代码也将快速更新,但是我觉得,只要把这个版本的代码理解了,再去看新的代码,应该是一件很容易的事情。
! m$ r/ b; k& \& l- c4 Q7 m, [: P8 l在文章中,将会有一些直接指向github上bytom源代码的链接。为了方便,我专门将bytom v1.0.1的代码放到了一个新的仓库中,这样就不容易与比原官方的最新代码混淆。该仓库地址为:https://github.com/freewind/bytom-v1.0.1/ M7 u- t/ P/ b$ G
当然,你不必clone这个仓库(clone官方仓库http://github.com/Bytom/bytom就够了),然后在必要的时候,使用以下命令将代码切换到v1.0.1的tag,以便与本系列引用的代码一致:
$ [6 c" f8 v2 v; L: R% `0 D' Kgit fetch
) t7 e' ^* c! c. Lgit checkout -b v1.0.1
9 H( K5 i  X( b0 I4 T不论采用哪种阅读方法,我想第一步都应该先在本地把比原节点跑起来,试试各种功能。/ w" m' @+ @! q
对于如何下载、配置和安装的问题,请直接参看官方文档https://github.com/Bytom/bytom/tree/v1.0.1(注意我这里给出的是v1.0.1的文档),这里不多说。
# J0 J. [2 Q8 C: X: y3 @: b+ K本篇问题
& Q# o( J$ O+ R& F3 m$ n当我们本地使用make bytomd编译完比原后,我们可以使用下面的命令来进行初始化:
6 L& Z* s& z! P, y& f( T./bytomd init --chain_id testnet  `- ]- k" W$ A# {1 i4 T
这里指定了使用的chain是testnet(还有别的选项,如mainnet等等)。运行成功后,它将会在本地文件系统生成一些配置文件,供比原启动时使用。
6 }% H% t5 s$ V! A# ^2 F所以我的问题是:
! d$ k$ L, L+ h$ L比原初始化时,产生了什么样的配置文件,放在了哪个目录下?$ F$ X, e  t" `$ b+ N# ]" S& }
下面我将结合源代码,来回答这个问题。" K8 g% W- ]8 E; ~7 e) U
目录位置$ g; g. h  e; G+ q5 m
首先比原在本地会有一个目录专门用于放置各种数据,比如密钥、配置文件、数据库文件等。这个目录对应的代码位于config/config.go#L190-L205:
* t9 R: m9 P  C: Z9 C. ^func DefaultDataDir() string {# ~. d3 Z! m8 A4 h. [
    // Try to place the data folder in the user's home dir
2 w" G- P5 t" ?! p3 Q    home := homeDir()" v' f- \9 y: _0 g0 k' r3 X
    dataDir := "./.bytom"
8 Y% l& t4 Z4 w& }5 ]% c( n    if home != "" {3 u  g2 o$ k) T+ s
        switch runtime.GOOS {' f* ~/ Q% t; y
        case "darwin":; S* z% z( m) B' K3 s, `1 p
            dataDir = filepath.Join(home, "Library", "Bytom")" Y% g. `, {6 w9 c7 w1 [
        case "windows":/ p8 ~1 w/ N. [; \: y- q
            dataDir = filepath.Join(home, "AppData", "Roaming", "Bytom")) z' Z4 [  {) s$ h  X, X, W
        default:8 F) {- K0 d' G/ m
            dataDir = filepath.Join(home, ".bytom")
9 Q+ f1 t5 Q% }7 H3 }: w        }" G3 C7 e$ L# L6 o7 U; f$ K, K
    }' y0 h, z5 u! ~# e, v
    return dataDir; j8 n. \8 t3 C
}4 p3 B9 z# i' a; y2 @9 Q0 |! Z9 `% b
可以看到,在不同的操作系统上,数据目录的位置也不同:
9 x# ?' J& ^$ e8 F) p
  • 苹果系统(darwin):~/Library/Bytom
  • Windows(windows): ~/AppData/Roaming/Bytom
  • 其它(如Linux):~/.bytom& [5 S+ `, \7 T' |3 `0 \1 W

    ' h5 C# r0 [  {5 m( s" H$ ~6 M# H+ e配置文件内容8 `' d" t! G: H9 F
    我们根据自己的操作系统打开相应的目录(我的是~/AppData/Roaming/Bytom),可以看到有一个config.toml,内容大约如下:, G( g) ]% \1 \9 T# |1 }; D9 d$ O
    $ cat config.toml
    0 j9 |0 P6 _  i4 J$ V: X8 N) X0 m4 b# This is a TOML config file.+ f& K6 b$ @  R- P; _# q& n
    # For more information, see https://github.com/toml-lang/toml
    ( B3 I3 _  C1 a* pfast_sync = true
    & D% s7 M, K5 ~8 i' F. Odb_backend = "leveldb"
    8 p+ b0 W% @; S, n6 Capi_addr = "0.0.0.0:9888"
    2 X; E+ v. q+ D* `. Qchain_id = "testnet"
    7 Q$ i  V1 e- z' i9 n[p2p]3 h' r0 K3 g/ @: E- a# s
    laddr = "tcp://0.0.0.0:46656"% H! `* a. d( {; I; k7 g
    seeds = "47.96.42.1:46656,172.104.224.219:46656,45.118.132.164:46656"
    3 y! \4 \! m+ F: a+ ]它已经把一些基本信息告诉我们了,比如:
    ) t3 N; c7 {" j) c- [
  • 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":比原启动后,会主动连接这几个地址获取数据
    % Q! t2 j; z1 ]8 u: q
    % R: x! D6 @& l6 b. n' G6 J
    内容模板8 j3 Y6 y7 @9 U- J* \
    使用不同的chain_id去初始化时,会生成不同内容的配置文件,那么这些内容来自于哪里呢?) O/ T; M' J8 p4 h& s/ N5 E2 f' ?* b
    原来在config/toml.go#L22-L45,预定义了不同的模板内容:
    ; H+ ]2 z  g# w; x  U5 vvar defaultConfigTmpl = `# This is a TOML config file.! e* D3 U: U8 ^$ ?! h3 W# P
    # For more information, see https://github.com/toml-lang/toml  y( N. c8 v; |) ^" u% B
    fast_sync = true
    7 i* |6 E, T+ }db_backend = "leveldb"
    0 E( H! r( t9 F& J. u7 w/ Iapi_addr = "0.0.0.0:9888"
    7 F! |# ?. Y2 U7 r& u2 k% w`1 v( f3 `" D! M$ n
    var mainNetConfigTmpl = `chain_id = "mainnet"
    & i/ \) p1 z1 v' Y" n[p2p]
    8 p1 D# {7 A) Q/ \; qladdr = "tcp://0.0.0.0:46657"0 `- f# E9 T, R
    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"
    7 X6 c9 e7 q) _! I  s5 ?* W. T3 I`6 p) ]! t" a' P) v
    var testNetConfigTmpl = `chain_id = "testnet": C2 |" [1 s8 Q9 a5 `$ s- i
    [p2p]3 o: u; C* C. }/ L: `
    laddr = "tcp://0.0.0.0:46656"
    , ?& |. \/ o/ U+ ?seeds = "47.96.42.1:46656,172.104.224.219:46656,45.118.132.164:46656"4 |$ ^; A# r7 K2 `, F
    `
    7 T! ]- a  W. Y; c0 h0 r  Uvar soloNetConfigTmpl = `chain_id = "solonet"
    8 N8 h$ c  P8 r% \5 G* O[p2p]
    7 G3 y: i) P. T, k2 K/ `laddr = "tcp://0.0.0.0:46658"* ]& z9 @# \, T- J0 ?1 k. o8 c" `
    seeds = ""
    3 ]/ f7 }6 X& {( b`
    / L/ R9 h# m# V) w" I4 e% z  c可以看到,原来这些端口号和seed的地址,都是事先写好在模板里的。8 Y3 e* q  M: J' ?2 y' m3 T! ~
    而且,通过观察这些配置,我们可以发现,如果chain_id不同,则监听的端口和连接的种子都不同:
    " E; c  x% Y8 u
  • mainnet(连接到主网): 46657,会主动连接6个种子
  • testnet(连接到测试网): 46656,会主动连接3个种子
  • solonet(本地单独节点): 46658,不会主动连接别人(也因此不会被别人连接上),适合单机研究) I9 z: W  O, v
    . e7 N/ L, {+ l; v& e- s: G
    写入文件$ {2 N) J. F! Q$ w4 ]
    这里我们需要快速的把bytomd init的执行流程过一遍,才能清楚配置文件的写入时机,也同时把前面的内容串在了一起。% U% [0 s) Z& ~
    首先,当我们运行bytomd init时,它对应的代码入口为cmd/bytomd/main.go#L54:
    9 [+ @: s2 H% ?0 z- |func main() {
    4 _; F3 I9 Z/ x7 P    cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))* w! `" U$ [  g
        cmd.Execute()
    , l3 m! H- E+ X! i! f4 _8 @' F* g}# F0 I% s; l( J% O8 G: B7 X
    其中的config.DefaultDataDir()就对应于前面提到数据目录位置。1 N) B: d7 _  V8 z0 m9 a
    然后执行cmd.Execute(),将根据传入的参数init,选择下面的函数来执行:cmd/bytomd/commands/init.go#L25-L24
    / T( W0 l# q% n& j  R0 L  ^5 k0 Gfunc initFiles(cmd *cobra.Command, args []string) {
    : @# p! Z" o; P2 i& m  A    configFilePath := path.Join(config.RootDir, "config.toml")1 V. g5 x+ P$ f: E5 w' {4 n
        if _, err := os.Stat(configFilePath); !os.IsNotExist(err) {! w/ r* K5 ~. s+ ]9 _& A( x0 m- b
            log.WithField("config", configFilePath).Info("Already exists config file.")
    % B' ?( m- b- j9 d/ r5 U) H) P        return
    6 l/ O; `  V2 u) d$ m, H: l    }) i4 W* Y; E; x1 v" O1 ^. k
        if config.ChainID == "mainnet" {
    3 |& b6 {% P) c7 S4 }, l9 B2 X0 d        cfg.EnsureRoot(config.RootDir, "mainnet")
    7 b9 [4 J' u  d0 O9 b: q    } else if config.ChainID == "testnet" {
    2 Z: S* d2 ]% P3 h3 ~, h% r6 t        cfg.EnsureRoot(config.RootDir, "testnet")
    3 E) W, o' M4 d/ O1 `3 K    } else {
    ( q/ K5 ^, v% Q8 C. p        cfg.EnsureRoot(config.RootDir, "solonet"); p6 H( L& F& z. x' q% N, W) h3 }5 ^
        }7 y- V  L3 g( U- M7 g
        log.WithField("config", configFilePath).Info("Initialized bytom")
    " G& s" ~0 z7 q! h4 Y}
    : f2 p0 y  x$ Z# b其中的configFilePath,就是config.toml的写入地址,即我们前面所说的数据目录下的config.toml文件。& @" p% r  @, T0 D+ O; ]3 q$ Z; U* N
    cfg.EnsureRoot将用来确认数据目录是有效的,并且将根据传入的chain_id不同,来生成不同的内容写入到配置文件中。- k' v. L, _  j' u: P; i
    它对应的代码是config/toml.go#L10) t, k  H6 |* ]; l% A( V/ i
    func EnsureRoot(rootDir string, network string) {
    3 W/ \2 ?  l; R- S  ?    cmn.EnsureDir(rootDir, 0700)
    . @- S; \- L) o  t5 K    cmn.EnsureDir(rootDir+"/data", 0700)8 \, L% l" I) ]* Y- k" [
        configFilePath := path.Join(rootDir, "config.toml")5 l4 d9 }5 F/ o" t- Y- J9 Z& j( k/ y
        // Write default config file if missing.
    3 J; z! h" Y$ r. Y7 x9 w% _    if !cmn.FileExists(configFilePath) {, l7 A0 K& {' Y
            cmn.MustWriteFile(configFilePath, []byte(selectNetwork(network)), 0644)2 _" ^3 @9 @) F( D' Z: B  @
        }9 X7 j# Q* u* n$ P* K
    }% F7 d' a! ~1 G6 q5 v
    可以看到,它对数据目录进行了权限上的确认,并且发现当配置文件存在的时候,不会做任何更改。所以如果我们需要生成新的配置文件,就需要把旧的删除(或改名)。
    4 l# D. N" N+ e" L7 R, N+ V/ y其中的selectNetwork(network)函数,实现了根据chain_id的不同来组装不同的配置文件内容,它对应于master/config/toml.go#L48:
    6 J9 P$ a: I" {$ F6 j% `func selectNetwork(network string) string {
    # g0 m" C# _$ I0 B, q0 W    if network == "testnet" {
    . u1 C! @! E6 E* d7 ~9 C  \        return defaultConfigTmpl + testNetConfigTmpl2 p. U" i& l0 U1 }! K# x4 X
        } else if network == "mainnet" {! D" S, d% J/ Z# L7 i( ?: O% {6 u
            return defaultConfigTmpl + mainNetConfigTmpl5 J/ c. k: r3 H: x- ]
        } else {
    & i$ t9 C5 q/ l* F' h3 W; _/ z0 M        return defaultConfigTmpl + soloNetConfigTmpl' _: r: K% e9 [: W0 K7 P7 w
        }- o3 B0 O3 W4 e1 _. U
    }
    - ^5 ?# H7 M" }6 ]果然就是一个简单的字符串拼接,其中的defaultConfigTmpl和*NetConfgTmpl在前面已经出现,这里不重复。  @9 y/ l, \7 l
    最后调用第三方函数cmn.MustWriteFile(configFilePath, []byte(selectNetwork(network)), 0644),把拼接出来的配置文件内容以权限0644写入到指定的文件地址。, n8 w8 f& ]) j4 y
    到这里,我们这个问题就算回答完毕了。* h% u8 F7 x/ H$ O+ U
    感谢社区用户Freewind的辛勤写作
  • BitMere.com 比特池塘系信息发布平台,比特池塘仅提供信息存储空间服务。
    声明:该文观点仅代表作者本人,本文不代表比特池塘立场,且不构成建议,请谨慎对待。
    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    成为第一个吐槽的人

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

      0

    • 关注

      7

    • 主题

      11