剝開比原看程式碼02:比原啟動後去哪裡連線別的節點
作者:freewind
比原專案倉庫:
Github地址:https://github.com/Bytom/bytom
Gitee地址:https://gitee.com/BytomBlockchain/bytom
比原啟動後去哪裡連線別的節點
最開始我對於這個問題一直有個疑惑:區塊鏈是一個分散式的網路,那麼一個節點啟動後,它怎麼知道去哪裡找別的節點從而加入網路呢?
看到程式碼之後,我才明白,原來在程式碼中硬編碼了一些種子地址,這樣在啟動的時候,可以先通過種子地址加入網路。雖然整個網路是分散式的,但是最開始還是需要一定的中心化。
預編碼內容
對於配置檔案config.toml
,比原的程式碼中硬編碼了配置檔案內容:
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
:
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()
:
func (n *Node) OnStart() error {
// ...
n.syncManager.Start()
// ...
}
可以看到,我們終於走到了呼叫了syncManager.Start()
的地方。
syncManager
中的處理
然後就是在syncManager
內部的一些處理了。
它主要是除了從config.toml
中取得種子節點外,還需要把以前連線過並儲存在本地的AddressBook.json
中的節點也拿出來連線,這樣就算預設的種子節點失敗了,也還是有可能連線上網路(部分解決了前面提到的中心化的擔憂)。
syncManager.Start()
對應於:
func (sm *SyncManager) Start() {
go sm.netStart()
// ...
}
其中sm.netStart()
,對應於:
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,這個方法對應的程式碼位於:
func (sm *SyncManager) DialSeeds(seeds []string) error {
return sm.sw.DialSeeds(sm.addrBook, seeds)
}
其實是是呼叫了sm.sw.DialSeeds
,而sm.sw
是指Switch
。這時可以看到,有一個叫addrBook
的東西參與了進來,它儲存了該結點之前成功連線過的節點地址,我們這裡暫不多做討論。
Switch.DialSeeds
對應於:
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])
對應於:
func (sw *Switch) dialSeed(addr *NetAddress) {
peer, err := sw.DialPeerWithAddress(addr, false)
// ...
}
sw.DialPeerWithAddress(addr, false)
又對應於:
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
對應於:
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
,加入了超時:
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
對應於:
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
包的呼叫,開始真正去連線這個種子節點了,到這裡,我們可以認為這個問題解決了。
相關文章
- 剝開比原看程式碼04:如何連上一個比原節點
- 剝開比原看程式碼05:如何從比原節點拿到區塊資料?
- 剝開比原看程式碼07:比原節點收到“請求區塊資料”的資訊後如何應答?
- 剝開比原看程式碼08:比原的Dashboard是怎麼做出來的?
- 剝開比原看程式碼14:比原的挖礦流程是什麼樣的?
- 剝開比原看程式碼03:比原是如何監聽p2p埠的
- 剝開比原看程式碼11:比原是如何通過介面/create-account建立帳戶的
- 剝開比原看程式碼06:比原是如何把請求區塊資料的資訊發出去的
- 剝開比原看程式碼01:初始化時生成的配置檔案在哪兒
- RAC節點啟動失敗--ASM無法連線ASM
- Derek解讀Bytom原始碼-protobuf生成比原核心程式碼原始碼
- 剝開網線表皮,裡面的8根線分別代表什麼以及作用
- 10.2.0.4以後vip不會自動relocate back回原節點
- 輪詢、長輪詢、短連線、長連線區別對比
- py連線mysql常用驅動的兩種對比MySql
- 2030年全球移動應用主要技術連線佔比預測(附原資料表)
- 2021年全球各世代節日購物方式佔比(附原資料表)
- 不同的連線方式效能對比!
- 做技術的「五比一」原則
- 啟動filezilla後自動連線指定站點及進入指定的目錄
- 哪裡工作更好?Google與Facebook大對比Go
- [原]敏捷開發-專案啟動敏捷
- Oracle 在連線條件裡處理和比較 NULL 值OracleNull
- 一比一還原axios原始碼(零)—— 概要iOS原始碼
- 初探BTrace指令碼 - 看看持續整合哪個環節比較慢指令碼
- 2020-2025年烏茲別克移動使用者佔比(附原資料表)
- JavaScript 字串連線效能比較JavaScript字串
- 學網路安全哪裡培訓比較好?
- win10遠端桌面連線在哪裡開啟 win10開啟遠端桌面連線的方法Win10
- 2030年中東和北非授權物聯網連線佔比(附原資料表)
- Python標準型別的比較原則:字典VS列表(元組)Python型別
- 2024年1月全球城市化連線裝置和服務人數及佔比(附原資料表)
- 一比一還原axios原始碼(六)—— 配置化iOS原始碼
- 一比一還原axios原始碼(八)—— 其他功能iOS原始碼
- 2021年中國自主研發移動遊戲海外重點地區收入佔比(附原資料表) 遊戲
- 全球消費者在決定去哪裡網購時主要考量的因素(附原資料表)
- iOS開發比較有用的程式碼段iOS
- waffle.model改進前、後的程式碼對比