精通IPFS:IPFS启动之start函数

本文详细探讨了IPFS系统启动的核心函数start,包括检查仓库、生成libp2p对象、启动libp2p及IPNS、Bitswap等组件的过程。通过start函数,IPFS完成网络连接、数据交换及预加载等关键任务,实现系统启动。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在系统启动总共要执行两个启动函数,一个是 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 文件中,这个函数的执行流程如下:
生成配置对象和选项对象,前者通过参数传递进来,后者通过 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 对象。默认的创建方法执行如下:
首先,设置默认选项。
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
    }
  }
}
通过以上代码,我们可以发现创建 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 方法,开始启动处理,具体处理如下:
检查是否有配置具体的传输方法,比如: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,并且如果当前连接的数量小于规定的数量还会进行连接。
3.执行第三个函数,这个函数的内容也比较多,我们慢慢看。
首先,生成 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 系统就算启动完成了。

作者:乔疯,加密货币爱好者,ipfs 爱好者,黑萤科技CTO。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值