精通IPFS | IPFS 启动之 start 函数
在系统启动总共要执行两个启动函数,一个是 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多个传销盘、资金盘、空气币、矿机骗局,守护了业内投资者的利益。
官网:https://www.ipfsfirst.com/
