在系統啟動總共要執行兩個啟動函式,一個是 preStart
函式,另一個就今天我樣研究的 start
函式。這個函式位於 core/components/start.js 檔案中,它的主要作用是真正啟動系統,它的主體是一個 series,老規矩我們直接來分析它的幾個函式。
執行第一個函式,檢查倉庫是否被關閉,如果是則開啟倉庫,否則,呼叫下一個函式。具體程式碼如下:
(cb) => {
self._repo.closed ? self._repo.open(cb) : cb()
}執行第二個函式,根據倉庫的配置檔案,生成 libp2p 物件,並呼叫它的啟動方法,然後設定 IPFS 物件的
libp2p
為生成的 libp2p 物件。具體程式碼如下:(cb) => {
self._repo.config.get((err, config) => {
if (err) return cb(err)const libp2p = createLibp2pBundle(self, config)
libp2p.start(err => {
if (err) return cb(err)
self.libp2p = libp2p
cb()
})
})}
createLibp2pBundle
函式位於當前目錄下的libp2p.js
檔案中,這個函式的執行流程如下:檢查是否有配置具體的傳輸方法,比如:TCP。如果沒有指定一個傳輸方法,則丟擲異常。預設情況下,會配置 TCP、WS、wsstar 等3個傳輸方法。
檢查節點的所有 multiaddr 地址,如果沒有指定節點 ID,則地址附加上 /p2p/節點ID。libp2p 物件的
peerInfo
來源於 IPFS 物件的_peerInfo
,後者在preStart
函式中生成並進行初始化,包含的multiaddr
則來自於配置檔案的Addresses.Swarm
陣列、libp2p-nodejs.js
生成的/p2p-websocket-star
、前面建構函式生成的/p2p-circuit/ipfs/節點ID
。最後一個地址只有在配置了modules.streamMuxer
和relay.enabled
的情況下,即啟用了流複用和電路中繼時候,在建構函式中呼叫switch.connection
物件的enableCircuitRelay
方法時生成電路中繼物件時才會生成並加入節點資訊物件的地址中。經過這步處理,最終節點資訊物件的multiaddrs
變成"/ip4/0.0.0.0/tcp/4002/ipfs/節點ID
、/ip4/127.0.0.1/tcp/4003/ws/ipfs/節點ID
、/p2p-websocket-star/ipfs/節點ID
、/p2p-circuit/ipfs/節點ID
的形式。遍歷對等節點指定的所有傳輸方法,如果某個傳輸方法可以處理節點指定的地址,則儲存到
switch.transport
物件中(型別為 TransportManager)。這一步的作用是用節點的地址來過濾傳輸方法,只有能處理某個地址的傳輸方法才會儲存到switch.transport
物件中。但是,每個傳輸方法都會儲存到 libp2p 物件自身的_transport
陣列。序列啟動所有的服務。比如:switch 服務、DHT 服務、節點發現服務等,如果有配置這些服務的話。switch 物件管理所有網路通訊相關的服務,內部也是一個狀態機,通常改變狀態執行不同的方法,當啟動它的服務時,最終會執行
_onStarting
方法,這個方法中會讓所有可以使用(即有地址可以監聽)的傳輸物件開始進行監聽,比如 TCP 傳輸方法監聽在 4002 埠。節點發現服務找到節點後,會觸發事件peer:discovery
,並且會把發現的節點儲存到peerBook
中,如果當前連線的數量小於規定的數量還會進行連線。所有服務啟動完成之後,遍歷
peerBook
中儲存的所有地址,觸發事件peer:discovery
,並且如果當前連線的數量小於規定的數量還會進行連線。
首先,設定預設選項。
const libp2pDefaults = {
datastore,
peerInfo,
peerBook,
config: {
peerDiscovery: {
mdns: {
enabled: get(options, 'config.Discovery.MDNS.Enabled',
get(config, 'Discovery.MDNS.Enabled', true))
},
webRTCStar: {
enabled: get(options, 'config.Discovery.webRTCStar.Enabled',
get(config, 'Discovery.webRTCStar.Enabled', true))
},
bootstrap: {
list: get(options, 'config.Bootstrap',
get(config, 'Bootstrap', []))
}
},
relay: {
enabled: get(options, 'relay.enabled',
get(config, 'relay.enabled', true)),
hop: {
enabled: get(options, 'relay.hop.enabled',
get(config, 'relay.hop.enabled', false)),
active: get(options, 'relay.hop.active',
get(config, 'relay.hop.active', false))
}
},
dht: {
kBucketSize: get(options, 'dht.kBucketSize', 20),
enabled: false,
randomWalk: {
enabled: false // disabled waiting for https://github.com/libp2p/js-libp2p-kad-dht/issues/86
},
validators: {
ipns: ipnsUtils.validator
},
selectors: {
ipns: ipnsUtils.selector
}
},
EXPERIMENTAL: {
pubsub: get(options, 'EXPERIMENTAL.pubsub', false)
}
},
connectionManager: get(options, 'connectionManager',
{
maxPeers: get(config, 'Swarm.ConnMgr.HighWater'),
minPeers: get(config, 'Swarm.ConnMgr.LowWater')
})
}其中,
datastore
、peerInfo
、peerBook
、options
等來自於 IPFS 物件的相關私有屬性,config
來自於最終生成的倉庫的配置檔案和使用者指定的相關配置。然後,呼叫
mergeOptions
方法,合併預設選項與使用者指定的選項。const libp2pOptions = mergeOptions(libp2pDefaults, get(options, 'libp2p', {}))
最後,載入
core/runtime/libp2p-nodejs.js
檔案中定義的 Node 物件(繼承於libp2p
庫定義的物件),並呼叫其建構函式,生成 libp2p 物件。const Node = require('../runtime/libp2p-nodejs')
return new Node(libp2pOptions)libp2p-nodejs.js
檔案中主要定義了建立 libp2p 物件的預設選項,並把前面生成的選項與預設選項進行合併,然後呼叫父類的構造來建立 物件。具體的預設選項為:{
switch: {
blacklistTTL: 2 * 60 * 1e3, // 2 minute base
blackListAttempts: 5, // back off 5 times
maxParallelDials: 150,
maxColdCalls: 50,
dialTimeout: 10e3 // Be strict with dial time
},
modules: {
transport: [
TCP,
WS,
wsstar
],
streamMuxer: [
Multiplex
],
connEncryption: [
SECIO
],
peerDiscovery: [
MulticastDNS,
Bootstrap,
wsstar.discovery
],
dht: KadDHT
},
config: {
peerDiscovery: {
autoDial: true,
mdns: {
enabled: true
},
bootstrap: {
enabled: true
},
websocketStar: {
enabled: true
}
},
dht: {
kBucketSize: 20,
enabled: false,
randomWalk: {
enabled: false
}
},
EXPERIMENTAL: {
pubsub: false
}
}
}
生成配置物件和選項物件,前者透過引數傳遞進來,後者透過 IPFS 物件獲取到。
const options = self._options || {}
config = config || {}確定如何建立 libp2p 物件。如果在選項物件中指定了建立方法,則使用指定的建立方法,否則使用預設的建立方法。預設情況,使用者不會指定建立方法,所以這裡使用預設的建立方法。
const createBundle = typeof options.libp2p === 'function'
? options.libp2p
: defaultBundle從 IPFS 物件獲取建立 libp2p 物件所需要的資訊。
const { datastore } = self._repo
const peerInfo = self._peerInfo
const peerBook = self._peerInfoBook呼叫建立方法建立 libp2p 物件。預設的建立方法執行如下:透過以上程式碼,我們可以發現建立 libp2p 的過程是比較複雜的,libp2p 物件的實際型別為
libp2p
庫中定義的物件。因為 libp2p 是一個非常非常重要的元件/庫,即可以使用在 IPFS/Filecoin 中,也可以有獨立使用,或者在其他專案中使用,鑑於它是如此的重要,所以我們以後會專門來講解它,這裡只是簡單涉及。
libp2p 物件繼承於
EventEmitter
類,所以可以觸發事件,同時本身內部也有一個型別fsm-event
的狀態變數,所以也可以認為是一個狀態機物件。呼叫 libp2p 物件的
start
方法,啟動 libp2p 物件。當 libp2p 物件啟動成功後,把它儲存在 IPFS 物件的同名屬性中。具體程式碼如下:libp2p.start(err => {
if (err) return cb(err)
self.libp2p = libp2p
cb()
}libp2p 物件的
start
方法,把內部狀態設為start
,從而導致 libp2p 呼叫其_onStarting
方法,開始啟動處理,具體處理如下:
執行第三個函式,這個函式的內容也比較多,我們慢慢看。
首先,生成 IPNS 物件,並設定 IPFS 物件的
_ipns
為生成 IPNS 物件。const ipnsRouting = routingConfig(self)
self._ipns = new IPNS(ipnsRouting, self._repo.datastore, self._peerInfo, self._keychain, self._options)然後,生成 Bitswap 物件,並設定 IPFS 物件的
_bitswap
為生成的 Bitswap 物件,同時呼叫後者的啟動方法;self._bitswap = new Bitswap(
self.libp2p,
self._repo.blocks,
{ statsEnabled: true }
)
self._bitswap.start()Bitswap 物件是 IPFS/libp2p 體系中另一個非常的物件,它決定了是從本地倉庫中載入區塊,還是從網路中其他節點請求區塊,還決定了是否相應別的節點請求區塊的請求。它的
start
方法依次啟動了WantManager
物件(一個定時向別的節點傳送請求訊息的物件)、Network
物件(一個指定 libp2p/switch 物件如何處理 bitswap 協義,同時監聽 libp2p 物件節點連線/斷開連線事件的物件)、DecisionEngine
物件(一個確定是否響應別節點請求的物件)。接下來,呼叫 blockService 物件的
setExchange
方法,設定前者交換區塊的物件為新生成的 Bitswap 物件。self._blockService.setExchange(self._bitswap)
本方法執行之前,當呼叫區塊服務物件請求區塊時,都是從本地倉庫中載入區塊;當本方法執行之後,區塊服務物件在請求區塊時都要透過 bitswap 物件來確定區塊是從哪裡獲取。
再接下來,呼叫幾個物件的
start
方法,進行啟動。self._preload.start()
self._ipns.republisher.start()
self._mfsPreload.start(cb)預載入物件的
start
方法只是簡單地把內部變數stopped
設定為假;IPNS 的啟動,以後分析 IPNS 會進行分析,這裡略過。_mfsPreload
的啟動方法也比較簡單,只是呼叫 IPFS 物件的files
物件的stat
方法,載入根目錄。根目錄物件在初始化函式中,儲存init-files/init-docs/
的過程中被初始化。
當 series
方法的幾個函式執行完成後,系統基本啟動完成,最後一個要執行的動作,就是呼叫 start
函式中定義的 done
函式,把狀態設定為執行中。
當 done
函式執行到完成後,IPFS 系統就算啟動完成了。
作者介紹:
喬瘋,區塊鏈狂熱愛好者,熟悉比特幣、EOS、以太坊原始碼及合約的開發,有著數年區塊鏈開發經驗,堅信技術是第一生產力,區塊鏈改變整個人類,開設巴位元專欄以來已經獲得 100多萬次的閱讀量。
參與湖南天河國雲 Ulord 公鏈的開發和麵向區塊鏈行業的風險監控平臺,後者在近期成功入選由工信部評選的 101 個網路安全技術應用試點示範專案。
在愛健康金融金融有限公司參與組建彗星資訊科技有限公司,並擔任第一任技術部負責人,開發出了彗星播報等深受大家喜愛的區塊鏈產品。
具有良好的協調溝通能力和團隊協作精神!熟悉Scrum、XP、看板等敏捷專案管理,擁有PMP證書!
熟悉JAVA、Python、NodeJS、C/C++、Linux下的開發,熟悉分散式架構設計!熟悉網際網路金融行業,具有豐富的網際網路金融產品開發經驗,對互聯金融有著深入的瞭解。
關於星鑑網
星鑑網自2018年3月成立至今,採訪了眾多業內大佬,包括萊位元礦池創始人江卓爾、礦海會創始人阿牛,知名佈道者董天一、戴嘉樂,共識實驗室合夥人任錚,YottaChain創始人王東臨、ORA甲骨數鏈創始人石柱、Lambda創始人何曉陽、NBS創始人李萬勝等等將近50位區塊鏈、IPFS、分散式儲存大咖。
同時星鑑網也曝光了行業內超過30多個傳銷盤、資金盤、空氣幣、礦機騙局,守護了業內投資者的利益。