星鉴网>技术干货>精通IPFS | IPFS 启动之 start 函数

精通IPFS | IPFS 启动之 start 函数

2019/6/17 10:52:31 9674人阅读

【导读】 方得技术大牛,乔疯教你精通IPFS。

在系统启动总共要执行两个启动函数,一个是 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 文件中,这个函数的执行流程如下:


生成配置对象和选项对象,前者通过参数传递进来,后者通过 IPFS 对象获取到。


const options = self._options || {}config = config || {}


确定如何创建 libp2p 对象。如果在选项对象中指定了创建方法,则使用指定的创建方法,否则使用默认的创建方法。默认情况,用户不会指定创建方法,所以这里使用默认的创建方法。


const createBundle = typeof options.libp2p === 'function'    ? options.libp2p    : defaultBundle


从 IPFS 对象获取创建 libp2p 对象所需要的信息。


const { datastore } = self._repoconst peerInfo = self._peerInfoconst 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 方法,开始启动处理,具体处理如下:


首先,设置默认选项。


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    }  }}


检查是否有配置具体的传输方法,比如: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,并且如果当前连接的数量小于规定的数量还会进行连接。


执行第三个函数,这个函数的内容也比较多,我们慢慢看。


首先,生成 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多个传销盘、资金盘、空气币、矿机骗局,守护了业内投资者的利益。


88

参与讨论

登录后参加评论......

全部评论 0

作者

返回顶部