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

精通IPFS:IPFS 启动之 init 函数

2019/6/17 10:58:59 17172人阅读

【导读】  


上一篇文章中,我们了解了 IPFS 启动过程中的 boot 函数,它就象一个大总管,控制到 IPFS 系统的启动整个过程。


在那篇文章中,我们简单的提到了 IPFS 启动过程分两个主要步骤,一个是初始化,另一个是启动。


初始化过程要用到的是 init 函数,这个函数初始化系统,只有系统完整初始化之后才可以启动系统。


init 这个函数位于 core/components/init.js 文件中。下面,进入这个文件来继续我们的探索之旅。


  1. 检查选项是否为函数,如果是则重新设置回调函数和选项对象。

    if (typeof opts === 'function') {
      callback = opts
      opts = {}
    }

    在这里 init 函数的选项参数是我们在前面指定的,默认情况下只有一个 bits: 2048 的属性,另一个 pass 属性,依赖于用户的指定。

  2. 接下来,生成 done 函数变量。内容如下,具体执行稍后分析。

    const done = (err, res) => {
      if (err) {
        self.emit('error', err)
        return callback(err)
      }



      self.preStart((err) => {
        if (err) {
          self.emit('error', err)
          return callback(err)
        }

    self.state.initialized()
    self.emit('init')
    callback(null, res)

      })
    }

  3. 调用 IPFS 的状态对象的 init 方法,进行初始化,此处略去不讲。

    self.state.init()

  4. 如果用户在选项中指定的了具体的仓库对象,则使用用户指定的仓库对象。然后调用 done 函数。

    if (opts.repo) {
      self._repo = opts.repo
      return done(null, true)
    }

    默认情况下,用户是不会指定的,所以代码继续执行。

  5. 设置选项别的一些属性。

    opts.emptyRepo = opts.emptyRepo || false
    opts.bits = Number(opts.bits) || 2048
    opts.log = opts.log || function () {}

  6. 调用 mergeOptions 方法,合并默认配置和用户指定的配置。这个方法在前面启动时已经见过,这里略去不提。

  7. 接下来又是一个 waterfall 函数调用。这个函数里面的流程比较复杂,也比较重要,我们需要一步一步来看。



  8. 保存文件锁对象到仓库对象中。代码如下,略过不讲。

  9.   (lck, cb) => {
        log('aquired repo.lock')
        this.lockfile = lck
        cb()
      }

    处理数据存储和区块存储对象。首先,调用 backends.create 方法生成 datastore 对象,并保存在仓库对象的同名属性中,同时在仓库目录下面生成 datastore 目录及对应的文件。这个 create 简单说一下,它根据第一个参数,从仓库的选项 storageBackends 中获得创建某个目录/文件的方法,再根据第二个参数指定的创建路径,第三个参数创建的配置参数,通过这几个参数在指定的路径下创建指定的目录/文件。


    然后,调用 backends.create 方法生成基础的 blockstore 对象,同时仓库目录下面生成 blocks 目录。

    最后,调用 blockstore 方法,根据配置选项来处理基础的 blockstore 对象。

    具体代码如下:

      (cb) => {
        log('creating datastore,类型为 js-datastore-level')
        this.datastore = backends.create('datastore', path.join(this.path, 'datastore'), this.options)
        const blocksBaseStore = backends.create('blocks', path.join(this.path, 'blocks'), this.options)
        blockstore(
          blocksBaseStore,
          this.options.storageBackendOptions.blocks,
          cb)
      }

    这里的配置选项请参考前一篇中提到的生成仓库对象的过程。


    保存区块存储对象到仓库对象中。在前一个函数最后的处理中,会以最终生成的 blockstore 对象为参数,调用下一个函数。所以,这里的 blocks 参数即为最终生成的 blockstore 对象,在仓库对象中保存这个对象。

      (blocks, cb) => {
        this.blocks = blocks
        cb()
      }

    生成 keys 对象。这个函数比较简单,直接调用 backends.create 方法生成 keys 对象,并保存在仓库对象的同名属性中,同时在仓库目录下面生成 keys 目录。

      (cb) => {
        log('creating keystore')
        this.keys = backends.create('keys', path.join(this.path, 'keys'), this.options)
        cb()
      }

    设置仓库关闭属性。这个函数把仓库对象的 closed 属性设置为假。


    最终,仓库 open 方法的所有业务逻辑都执行完成,所有的目录及文件也已经存在,终于来到了它的最终回调函数。因为,在前面执行过程中没有任何错误,所以这个回调函数直接调用最终的回调函数,从而执行流程回到 init 函数中。


    首先调用仓库对象的 exists 方法,检查仓库是否存在。这个方法内部只检查仓库的版本文件是否存在。接下来,调用第二个函数。


    接下来,处理第二个函数。首先,检查前一个函数返回的仓库是否存在的标识,如果存在,则抛出异常,结束下面的执行。


    然后,检查选项中是否指定有私钥,如果用户提供的私钥是一个对象,则使用私钥对象直接调用下一个方法;


    如果用户提供的私钥不是一个对象,则调用 peerId.createFromPrivKey 方法,根据私钥生成一个节点ID,然后再以之为参数调用下一个方法;


    如果没提供私钥,则调用 peerId.create 方法,生成一个随机的节点ID,然后再以之为参数调用下一个方法。


    具体代码如下:

      (exists, cb) => {
        self.log('repo exists?', exists)
        if (exists === true) {
          return cb(new Error('repo already exists'))
        }

    if (opts.privateKey) {
      self.log('using user-supplied private-key')
      if (typeof opts.privateKey === 'object') {
        cb(null, opts.privateKey)
      } else {
        peerId.createFromPrivKey(Buffer.from(opts.privateKey, 'base64'), cb)
      }
    } else {
      // Generate peer identity keypair + transform to desired format + add to config.
      opts.log(
    generating ${opts.bits}-bit RSA keypair..., false)
      self.log('generating peer id: %s bits', opts.bits)
      peerId.create({ bits: opts.bits }, cb)
    }

      }

    接下来,处理第三个函数。首先,根据生成的节点ID 对象,设置配置对象的 Identity 属性。


    然后,根据是否指定 pass 属性,决定要不要生成 Keychain。因为默认情况下,这个配置没有指定,所以这里不会生成。


    最后,调用仓库对象的 init 方法来初始化仓库。

    具体代码如下:

      (peerId, cb) => {
        self.log('identity generated')
        config.Identity = {
          PeerID: peerId.toB58String(),
          PrivKey: peerId.privKey.bytes.toString('base64')
        }
        privateKey = peerId.privKey
        if (opts.pass) {
          config.Keychain = Keychain.generateOptions()
        }

    // 初始化仓库
    self._repo.init(config, cb)

      }

    在仓库的初始化方法中使用了 series 函数,这个函数会依次调用仓库主对象的 open方法和配置对象、规格对象、版本对象的 set 方法,来真正初始化仓库。


    这几个方法执行完成后,在仓库目录下面就会生成 config、datastore_spec、version 等 3个文件。


    接下来,处理第四个函数。这个函数就是调用仓库对象的 open 方法来打开仓库。


    (_, cb) => self._repo.open(cb)

    在前面调用这个方法时,因为仓库还没有初始化,所以有很多流程没有执行到,这次我们来继续执行这些流程。


    这次调用 root 对象的 open 方法和上次没什么区别,但是在调用 _isInitialized 方法时,因为配置对象、规格对象、版本对象都已经存在,所这次这个对象不会生成错误对象,从而执行的下一个函数不是最终的回调函数,而是下一个函数,即 _openLock 函数。


    这个函数执行的结果就是在仓库目录中生成了一个 repo.lock 目录,表明当前进程正在不执行,从而不允许另一个 IPFS 进程同时执行。


    下面,我们仔细看下仓库 open 方法的余下的流程:


    接下来,处理第五个函数。在仓库的 open 方法执行完成后,就开始处理第五个函数。


    在这里,根据用户是否设置 pass 来进行不同处理。


    如果有设置,则生成 _keychain 对象,并保存到 IPFS 同名属性中;如果没有,则直接调用下一个函数。


    代码如下:

      (cb) => {
        if (opts.pass) {
          const keychainOptions = Object.assign({ passPhrase: opts.pass }, config.Keychain)
          self._keychain = new Keychain(self._repo.keys, keychainOptions)
          self._keychain.importPeer('self', { privKey: privateKey }, cb)
        } else {
          cb(null, true)
        }
      }

    接下来,处理第六个函数。这个函数主要用来生成 IPNS 对象,这个对象我们后面会涉及到,这里也略过不提。代码如下:


      (_, cb) => {
        const offlineDatastore = new OfflineDatastore(self._repo)

    self._ipns = new IPNS(offlineDatastore, self._repo.datastore, self._peerInfo, self._keychain, self._options)
    cb(null, true)

      }

    接下来,处理第七个函数。这个函数主要用来生成一个空的目录对象,同时把 init-files/init-docs/目录下的所有文件保存到仓库中。具体如何处理我们下面来看。


    (_, cb) => {
        if (opts.emptyRepo) {
          return cb(null, true)
        }
        const tasks = [
          (cb) => {
            waterfall([
              (cb) => DAGNode.create(new UnixFs('directory').marshal(), cb),
              (node, cb) => self.dag.put(node, {
                version: 0,
                format: 'dag-pb',
                hashAlg: 'sha2-256'
              }, cb),
              (cid, cb) => self._ipns.initializeKeyspace(privateKey, cid.toBaseEncodedString(), cb)
            ], cb)
          }
        ]

    if (typeof addDefaultAssets === 'function') {
      tasks.push((cb) => addDefaultAssets(self, opts.log, cb))
    }
    parallel(tasks, (err) => {
      if (err) {
        cb(err)
      } else {
        cb(null, true)
      }
    })

    }

    在这段代码中,第一个要执行的任务是创建一个空的目录对象,并用这个目录对象生成一个 DAGNode,然后调用 IPFS 对象的 dag 对象的 put 方法保存生成的 DAG 节点对象,put 方法内部调用 IPFS 对象的 ipld 的同名方法,后者调用 blockservice 对象来保存,这个对象或者调用本地仓库对象来保存,或者调用 bitswap 来保存,在初始化阶段因为 bitswap 对象还没有生成,所以会调用本地仓库对象来保存生成的 DAGNode。


    addDefaultAssets 变量是在文件开始的地方定义的,为一个函数,所以第二个执行的任务就是这个函数。这个函数的主要目的是把 init-files/init-docs/ 目录下的所有文件保存到仓库中,所以当我们初始化完成后,可以在仓库的 blocks 目录下看到很多文件,这些文件就保存了这里提到的文件。保存文件这个过程,我们后面会详细讲解,这里暂且略过。


    9,处理回调。在 waterfall 函数最后一个函数处理完成后,即在 tasks 中的所有任务执行完成后,调用前面指定的 done 回调函数。下面我们看下这个函数的内容。


    这个函数有两个参数,分别表示了前面函数执行的错误和结果,当执行成功之后,就会执行 IPFS 对象的preStart 方法进行预启动。


    在预启动成功之后,调用最终的回调方法,从而执行流程回到 boot 函数,进而开始执行系统的启动方法,开始真正启动系统。


    预启动和启动这两个方法,我们统一留在下一篇文章进行详细的说明。


通过上面的梳理,我们可以发现:


 init 函数顾名思议就是来初始化系统的,包括初始化/生成仓库,生成节点ID,保存 init-docs 文档,调用预启动/启动方法等启动。


这个方法总的来说就是把系统启动所需要的一切都准备好,然后正式启动。



 作者介绍:  


乔疯区块链狂热爱好者,熟悉比特币、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/




精品文推荐


太长脸了!中国开发者戴嘉乐与IPFS官方的首次互动初体验

华为声势浩大布局分布式存储,巨头出手都是大动作

莱比特矿池CEO江卓尔:一切自由皆正义

西安灵动科技CEO晏梓桐,一个IPFS从业者的自我修养

这家公司申请了五个名为“IPFS”的商标,深扒果然不简单

区块链存储潜力有可能占到全球GDP的10%?假的!




103

参与讨论

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

全部评论 0

作者

返回顶部