摘要:对应于继续,加入了超时对应于终于到了包的调用,开始真正去连接这个种子节点了,到这里,我们可以认为这个问题解决了。
作者:freewind
比原项目仓库:
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockc...
最开始我对于这个问题一直有个疑惑:区块链是一个分布式的网络,那么一个节点启动后,它怎么知道去哪里找别的节点从而加入网络呢?
看到代码之后,我才明白,原来在代码中硬编码了一些种子地址,这样在启动的时候,可以先通过种子地址加入网络。虽然整个网络是分布式的,但是最开始还是需要一定的中心化。
预编码内容对于配置文件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比较重要,在某些情况下如果断开连接后,还会尝试重连。如果persistent为false的,就没有这个待遇。
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.ucloud.cn/yun/24160.html
摘要:所以这个文章系列叫作剥开比原看代码。所以我的问题是比原初始化时,产生了什么样的配置文件,放在了哪个目录下下面我将结合源代码,来回答这个问题。将用来确认数据目录是有效的,并且将根据传入的不同,来生成不同的内容写入到配置文件中。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee...
摘要:作者比原项目仓库地址地址在前一篇中,我们说到,当比原向其它节点请求区块数据时,会发送一个把需要的区块告诉对方,并把该信息对应的二进制数据放入对应的通道中,等待发送。这个就是真正与连接对象绑定的一个缓存区,写入到它里面的数据,会被发送出去。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https:...
摘要:作者比原项目仓库地址地址在前一篇中,我们已经知道如何连上一个比原节点的端口,并与对方完成身份验证。代码如下可以看到,首先是从众多的中,找到最合适的那个。到这里,我们其实已经知道比原是如何向其它节点请求区块数据,以及何时把信息发送出去。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://...
摘要:启动直到进入所以我们首先需要知道,比原在源代码中是如何启动,并且一步步走进了的世界。后面省略了一些代码,主要是用来获取当前监听的实际以及外网,并记录在日志中。 比原是如何监听p2p端口的 我们知道,在使用bytomd init --chain_id mainnet/testnet/solonet初始化比原的时候,它会根据给定的chain_id的不同,使用不同的端口(参看config/t...
摘要:如果传的是,就会在内部使用默认的随机数生成器生成随机数并生成密钥。使用的是,生成的是一个形如这样的全球唯一的随机数把密钥以文件形式保存在硬盘上。 作者:freewind 比原项目仓库: Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在前一篇,我们探讨了从浏览器的dashb...
阅读 3443·2021-11-19 09:40
阅读 1346·2021-10-11 11:07
阅读 4872·2021-09-22 15:07
阅读 2904·2021-09-02 15:15
阅读 1976·2019-08-30 15:55
阅读 548·2019-08-30 15:43
阅读 895·2019-08-30 11:13
阅读 1462·2019-08-29 15:36