剥开比原看代码02:比原启动后去哪里连接别的节点

作者:freewind

比原项目仓库:

Github地址:https://github.com/Bytom/bytom

Gitee地址:https://gitee.com/BytomBlockchain/bytom

比原启动后去哪里连接别的节点

最开始我对于这个问题一直有个疑惑:区块链是一个分布式的网络,那么一个节点启动后,它怎么知道去哪里找别的节点从而加入网络呢?

看到代码之后,我才明白,原来在代码中硬编码了一些种子地址,这样在启动的时候,可以先通过种子地址加入网络。虽然整个网络是分布式的,但是最开始还是需要一定的中心化。

预编码内容

对于配置文件config.toml,比原的代码中硬编码了配置文件内容:

config/toml.go#L22-L45

var defaultConfigTmpl = `# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
fast_sync = true
db_backend = "leveldb"
api_addr = "0.0.0.0:9888"
`

var mainNetConfigTmpl = `chain_id = "mainnet"
[p2p]
laddr = "tcp://0.0.0.0:46657"
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"
`

var testNetConfigTmpl = `chain_id = "testnet"
[p2p]
laddr = "tcp://0.0.0.0:46656"
seeds = "47.96.42.1:46656,172.104.224.219:46656,45.118.132.164:46656"
`

var soloNetConfigTmpl = `chain_id = "solonet"
[p2p]
laddr = "tcp://0.0.0.0:46658"
seeds = ""
`

可以看出,对于不同的chain_id,预设的种子是不同的。

当然,如果我们自己知道某些节点的地址,也可以在初始化生成config.toml后,手动修改该文件添加进去。

启动syncManager

那么,比原在代码中是使用这些种子地址并连接它们的呢?关键在于,连接的代码位于SyncManager中,所以我们要找到启动syncManager的地方。

首先,当我们使用bytomd node启动后,下面的函数将被调用:

cmd/bytomd/commands/run_node.go#L41

func runNode(cmd *cobra.Command, args []string) error {
    // Create & start node
    n := node.NewNode(config)
    if _, err := n.Start(); err != nil {
        // ...
    }
    // ...
}

这里调用了n.Start,其中的Start方法,来自于Node所嵌入的cmn.BaseService

node/node.go#L39

type Node struct {
    cmn.BaseService
    // ...
}

所以n.Start对应的是下面这个方法:

vendor/github.com/tendermint/tmlibs/common/service.go#L97

func (bs *BaseService) Start() (bool, error) {
    // ...
    err := bs.impl.OnStart()
    // ...
}

在这里,由于bs.impl对应于Node,所以将继续调用Node.OnStart():

node/node.go#L169

func (n *Node) OnStart() error {
    // ...
    n.syncManager.Start()
    // ...
}

可以看到,我们终于走到了调用了syncManager.Start()的地方。

syncManager中的处理

然后就是在syncManager内部的一些处理了。

它主要是除了从config.toml中取得种子节点外,还需要把以前连接过并保存在本地的AddressBook.json中的节点也拿出来连接,这样就算预设的种子节点失败了,也还是有可能连接上网络(部分解决了前面提到的中心化的担忧)。

syncManager.Start()对应于:

netsync/handle.go#L141

func (sm *SyncManager) Start() {
    go sm.netStart()
    // ...
}

其中sm.netStart(),对应于:

netsync/handle.go#L121

func (sm *SyncManager) netStart() error {
    // ...
    // If seeds exist, add them to the address book and dial out
    if sm.config.P2P.Seeds != "" {
        // dial out
        seeds := strings.Split(sm.config.P2P.Seeds, ",")
        if err := sm.DialSeeds(seeds); err != nil {
            return err
        }
    }
    // ...
}

其中的sm.config.P2P.Seeds就对应于config.toml中的seeds。关于这两者是怎么对应起来的,会在后面文章中详解。

紧接着,再通过sm.DialSeeds(seeds)去连接这些seed,这个方法对应的代码位于:

netsync/handle.go#L229

func (sm *SyncManager) DialSeeds(seeds []string) error {
    return sm.sw.DialSeeds(sm.addrBook, seeds)
}

其实是是调用了sm.sw.DialSeeds,而sm.sw是指Switch。这时可以看到,有一个叫addrBook的东西参与了进来,它保存了该结点之前成功连接过的节点地址,我们这里暂不多做讨论。

Switch.DialSeeds对应于:

p2p/switch.go#L311

func (sw *Switch) DialSeeds(addrBook *AddrBook, seeds []string) error {
    // ...
    perm := rand.Perm(len(netAddrs))
    for i := 0; i < len(perm)/2; i++ {
        j := perm[i]
        sw.dialSeed(netAddrs[j])
    }
   // ...
}

这里引入了随机数,是为了将发起连接的顺序打乱,这样可以让每个种子都获得公平的连接机会。

sw.dialSeed(netAddrs[j])对应于:

p2p/switch.go#L342

func (sw *Switch) dialSeed(addr *NetAddress) {
    peer, err := sw.DialPeerWithAddress(addr, false)
    // ...
}

sw.DialPeerWithAddress(addr, false)又对应于:

p2p/switch.go#L351

func (sw *Switch) DialPeerWithAddress(addr *NetAddress, persistent bool) (*Peer, error) {
    // ...
    log.WithField("address", addr).Info("Dialing peer")
    peer, err := newOutboundPeerWithConfig(addr, sw.reactorsByCh, sw.chDescs, sw.StopPeerForError, sw.nodePrivKey, sw.peerConfig)
    // ...
}

其中的persistent参数如果是true的话,表明这个peer比较重要,在某些情况下如果断开连接后,还会尝试重连。如果persistentfalse的,就没有这个待遇。

newOutboundPeerWithConfig对应于:

p2p/peer.go#L69

func newOutboundPeerWithConfig(addr *NetAddress, reactorsByCh map[byte]Reactor, chDescs []*ChannelDescriptor, onPeerError func(*Peer, interface{}), ourNodePrivKey crypto.PrivKeyEd25519, config *PeerConfig) (*Peer, error) {
    conn, err := dial(addr, config)
    // ...
}

继续dial,加入了超时:

p2p/peer.go#L284

func dial(addr *NetAddress, config *PeerConfig) (net.Conn, error) {
    conn, err := addr.DialTimeout(config.DialTimeout * time.Second)
    if err != nil {
        return nil, err
    }
    return conn, nil
}

addr.DialTimeout对应于:

p2p/netaddress.go#L141

func (na *NetAddress) DialTimeout(timeout time.Duration) (net.Conn, error) {
    conn, err := net.DialTimeout("tcp", na.String(), timeout)
    if err != nil {
        return nil, err
    }
    return conn, nil
}

终于到了net包的调用,开始真正去连接这个种子节点了,到这里,我们可以认为这个问题解决了。

原文地址:https://www.cnblogs.com/bytom/p/9355508.html

时间: 2024-08-29 16:28:36

剥开比原看代码02:比原启动后去哪里连接别的节点的相关文章

【比原链】比原启动后去哪里连接别的节点

最开始我对于这个问题一直有个疑惑:区块链是一个分布式的网络,那么一个节点启动后,它怎么知道去×××别的节点从而加入网络呢? 看到代码之后,我才明白,原来在代码中硬编码了一些种子地址,这样在启动的时候,可以先通过种子地址加入网络.虽然整个网络是分布式的,但是最开始还是需要一定的中心化. 预编码内容 对于配置文件config.toml,比原的代码中硬编码了配置文件内容: config/toml.go#L22-L45 var defaultConfigTmpl = `# This is a TOML

剥开比原看代码14:比原的挖矿流程是什么样的?

作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom 当我们以bytom init --chain_id=solonet建立比原单机节点用于本地测试时,很快会发现自己将面临一个尴尬的问题:余额为0.就算我们使用bytom node --mining开启挖矿,理论上由于我们是单机状态,本机算力就是全网算力,应该每次都能够挖到,但是不知道为

剥开比原看代码17:比原是如何显示交易的详细信息的?

作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom 在上上篇文章里,我们还剩下一个小问题没有解决,即前端是如何显示一个交易的详细信息的. 先看对应的图片: 这个图片由于太长,分成了两个,实际上可以看作一个. 那么这个页面是怎么来的呢?这是在前面以列表的方式显示交易摘要信息后,可以点击摘要信息右上角的"查看详情"链接打开. 那

剥开比原看代码11:比原是如何通过接口/create-account创建帐户的

作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom 在前面,我们探讨了从浏览器的dashboard中进行注册的时候,数据是如何从前端发到后端的,并且后端是如何创建密钥的.而本文将继续讨论,比原是如何通过/create-account接口来创建帐户的. 在前面我们知道在API.buildHandler中配置了与创建帐户相关的接口配置:

剥开比原看代码16:比原是如何通过/list-transactions显示交易信息的

作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom 在前一篇文章中,我们试图理解比原是如何交易的,但是由于内容太多,我们把它分成了几个小问题,并在前一篇解决了"在dashboard中如何提交交易信息",以及"比原后台是如何操作的". 在本文我们继续研究下一个问题:在提交的交易成功完成后,前端会以列表的方式

剥开比原看代码12:比原是如何通过/create-account-receiver创建地址的?

作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom 在比原的dashboard中,我们可以为一个帐户创建地址(address),这样就可以在两个地址之间转帐了.在本文,我们将结合代码先研究一下,比原是如何创建一个地址的. 首先看看我们在dashboard中的是如何操作的. 我们可以点击左侧的"Accounts",在右边显示我

剥开比原看代码05:如何从比原节点拿到区块数据?

作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockchain/bytom 在前一篇中,我们已经知道如何连上一个比原节点的p2p端口,并与对方完成身份验证.此时,双方结点已经建立起来了信任,并且连接也不会断开,下一步,两者就可以继续交换数据了. 那么,我首先想到的就是,如何才能让对方把它已有的区块数据全都发给我呢? 这其实可以分为三个问题: 我需要发给它什么

JSqlParser系列之二代码结构(原)

JSqlParser系列之二代码结构(原) 博客园 百味木屋原创,转载请注明出处. 上一篇文章简单介绍如何建立JSqlParser工程,本章对JSqlParser工程的代码结构作大致地介绍. 一.目录结构 JSqlParser的目录结构比较简单,主要有表达式,解析器,语句处理几个目录.下面这张图给出了一个具体的SQL语句与表达式: 接下来,简要介绍一下在JSqlParser中几个抽象概念. 二.SQL语句(statement) JSqlPaser将所有的SQL语句抽象为Statement,Sta

带着马原看学习、生活

带着马原看学习生活 我是带着忐忑而又激动的心情接触马克思主义基本原理概论这门学科的,忐忑是因为来自学姐.学长对这门学科的诟病(好在我养成了一种习惯——对于事物的评定,我是一定得经过自己的实际接触,然后再下定论),激动是因为我为哲学的神秘气息所倾倒,渴望与之一会.果然,没有让我失望,这门学科让我对自己的学习以及生活有了更深的理解,在此,我就大胆的说说我的想法吧. 首先,聊聊马原给我带来的关于学习过程的一些理解吧. 学习,狭义的说就是通过阅读.听讲.研究.观察.理解.探索.实践等手段获得知识或技能的