专栏名称: IPFS星鉴网
星鉴网是全球首家专注于ipfs生态的垂直媒体,集信息传播、技术推广、应用孵化于一身,内容包含项目资讯、技术研讨、行业分析、人物专访、项目展示等维度,为数十万IPFS爱好者提供最具参考价值的媒体服务,成为ipfs爱好者的每日必读媒体。
目录
相关文章推荐
新疆949交通广播  ·  出现这些情况可能是流感重症!警惕→ ·  3 小时前  
半月谈  ·  初步核查29人失联,救援最新情况→ ·  4 小时前  
半月谈  ·  就在刚刚,中国代表团首金诞生! ·  昨天  
底线思维  ·  日本的分级诊疗制度会耽误救治吗? ·  2 天前  
学习大国  ·  人民日报刊文谈“加价选座” ·  2 天前  
51好读  ›  专栏  ›  IPFS星鉴网

精通IPFS | IPFS 启动之 start 函数

IPFS星鉴网  · 公众号  ·  · 2019-06-17 18:00

正文


在系统启动总共要执行两个启动函数,一个是 preStart 函数,另一个就今天我样研究的 start 函数。这个函数位于 core/components/start.js 文件中,它的主要作用是真正启动系统,它的主体是一个 series,老规矩我们直接来分析它的几个函数。


  1. 执行第一个函数,检查仓库是否被关闭,如果是则打开仓库,否则,调用下一个函数。具体代码如下:

    (cb) => {
        self._repo.closed ? self._repo.open(cb) : cb()
    }

  2. 执行第二个函数,根据仓库的配置文件,生成 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 个网络安全技术应用试点示范项目


    在爱健康金融金融有限公司参与组建彗星信息科技有限公司,并担任第一任技术部负责人,开发出了 彗星播报 等深受大家喜爱的区块链产品。







    请到「今天看啥」查看全文