精通IPFS:系统启动之概览

乔疯
个人专栏
热度: 13944

精通 IPFS,系统启动之概览带你更高层理解一窥 IPFS 系统。

今天,我们开始从源代码来一窥 IPFS 系统,下文我们以 Node.JS 为例来讲解 IPFS 的源码。当我们写下如下代码

const {createNode} = require('ipfs')
const node = createNode()

时,虽然只有简简单单的两句代码,可是内部却执行了非常非常多的代码,接下来我们就来看系统是如何执行,如何初始化系统的。

 

当我们执行 createNode 函数时,真正执行的函数处于 ipfs/core/index.js 文件中,代码如下:

module.exports.createNode = (options) => {
  return new IPFS(options)
}

上面初始化的 IPFS 对象代表了 IPFS 系统,它位于同一文件中,继承自 EventEmitter 对象。当我们初始化 IPFS 对象时,就开始执行它的构造函数,接下来我们分析下这个构造函数。

 

  1. 调用父类构造函数。
  2. 设置系统所使用的环境变量。首先,设置系统默认选项。
    const defaults = {
      init: true,
      start: true,
      EXPERIMENTAL: {},
      preload: {
        enabled: true,
        addresses: [
          '/dnsaddr/node0.preload.ipfs.io/https',
          '/dnsaddr/node1.preload.ipfs.io/https'
        ]
      }
    }
    
    其次,验证选项是否有效。
    options = config.validate(options || {})
    
    接下来,调用 mergeOptions 函数,使用用户指定选项参数来合并默认选项。

    最后,处理选项的 initstart 两个配置,代码如下:

    if (options.init === false) {
      this._options.init = false
    }

    if (!(options.start === false)) {   this._options.start = true }

     
  3. 如果在选项中指定了仓库并且类型为字符串,或者没有指定仓库,那么设置所使用的仓库就是默认仓库,否则,使用用户指定的仓库。
    if (typeof options.repo === 'string' ||
        options.repo === undefined) {
      this._repo = defaultRepo(options.repo)
    } else {
      this._repo = options.repo
    }
    
    默认仓库定义位于 runtime/repo-nodejs.js 文件中,这个文件内容比较简单,具体如下:
    'use strict'

    const os = require('os') const IPFSRepo = require('ipfs-repo') const path = require('path')

    module.exports = (dir) => {   const repoPath = dir || path.join(os.homedir(), '.jsipfs')

      return new IPFSRepo(repoPath) }

    因为我们没有指定仓库的位置,所以它默认位于用户的户主目录下的 .jsipfs 目录。下面,我们看下 IPFSRepo 对象,它位于 ipfs-repo 项目的 index.js 文件中,它的构造函数如下:

    constructor (repoPath, options) { assert.strictEqual(typeof repoPath, 'string', 'missing repoPath')

    this.options = buildOptions(options)
    this.closed = true
    this.path = repoPath

    this._locker = this._getLocker()

    this.root = backends.create('root', this.path, this.options) this.version = version(this.root) this.config = config(this.root) this.spec = spec(this.root) this.apiAddr = apiAddr(this.root)

    }

    在仓库的构造函数中,首先调用 buildOptions 函数来设置仓库的选项。这个函数把用户指定的选项与仓库默认的选项进行合并,同时处理 storageBackendsstorageBackendOptions 两个选项。因为,生成默认仓库时只指定了仓库的目录,而没有指定任务其他选项,所以默认仓库会使用默认选项。默认选项如下,

    {
      lock: 'fs',
      storageBackends: {
        root: require('datastore-fs'),
        blocks: require('datastore-fs'),
        keys: require('datastore-fs'),
        datastore: require('datastore-level')
      },
      storageBackendOptions: {
        root: {
          extension: ''
        },
        blocks: {
          sharding: true,
          extension: '.data'
        },
        keys: {
        }
      }
    }
    
    从默认选项中,我们发现锁使用的是文件,根目录、区块目录和 keys 目录使用的是普通文件系统存储,数据存储使用的是 level 数据系统,默认数据文件后缀为 .data

    处理完仓库选项之后,接下来就可以处理仓库。调用 backends.js 文件中定义的函数来创建仓库的目录,根据选项配置即创建主目录对象,默认情况下创建主目录位于 ~/.jsipfs,接下来依次创建版本文件、配置文件、存储规定等对象。

    注意,这些动作完成之后,仓库对象即初始完成,但此时除了主目录外,别的目录及文件并没有真正创建,它们的真正创建要等到启动初始化时才会进行。
  4. 接下来生成内部需要的对象。
    this._peerInfoBook = new PeerBook()
    this._peerInfo = undefined
    this._bitswap = undefined

    this._blockService = new BlockService(this._repo) this._ipld = new Ipld(ipldOptions(this._blockService, this._options.ipld, this.log)) this._preload = preload(this)

    this._mfsPreload = mfsPreload(this)

    _bitswap 对象在 start 阶段才会真正生成,区块服务对象保存仓库对象、bitswap 对象,系统通过调用区块服务对象的 put、get、delete 操作来处理具体的区块,具体区块可以从本地仓库中中处理,也可以通过 bitswap 对象来处理具体的区块。bitswap 对象此时为空,直到系统启动阶段才会生成并进行设置。

     

  5. 再接下来扩充系统核心组件,主要用于节点的初始化、启动、终止、关闭等。
    this.init = components.init(this)
    this.preStart = components.preStart(this)
    this.start = components.start(this)
    this.stop = components.stop(this)
    this.shutdown = this.stop
    this.isOnline = components.isOnline(this)
    
    这些命令后面再进行具体讲解,此处略去不讲。

     

  6. 再接下来扩充一些交互相关的组件,包括文件相关的操作。
    Object.assign(this, components.filesRegular(this))
    this.version = components.version(this)
    this.id = components.id(this)
    this.repo = components.repo(this)
    this.bootstrap = components.bootstrap(this)
    this.config = components.config(this)
    this.block = components.block(this)
    this.object = components.object(this)
    this.dag = components.dag(this)
    this.files = components.filesMFS(this)
    this.libp2p = null // assigned on start
    this.swarm = components.swarm(this)
    this.name = components.name(this)
    this.bitswap = components.bitswap(this)
    this.pin = components.pin(this)
    this.ping = components.ping(this)
    this.pingPullStream = components.pingPullStream(this)
    this.pingReadableStream = components.pingReadableStream(this)
    this.pubsub = components.pubsub(this)
    this.dht = components.dht(this)
    this.dns = components.dns(this)
    this.key = components.key(this)
    this.stats = components.stats(this)
    this.resolve = components.resolve(this)
    
    上面两步对系统的扩充可以进行直接调用,也可以通过程序来调用。对应于命令行中的相关命令。

     

  7. 最后,调用 boot 函数进行系统启动。

到这里,宏观上 IPFS 节点已经启动,可以调用各种命令来把玩系统。

声明:本文为入驻“火星财经 专栏”作者作品,不代表火星财经官方立场。
转载请联系网页底部:内容合作栏目,邮件进行授权。授权后转载时请注明出处、作者和本文链接。未经许可擅自转载本站文章,将追究相关法律责任,侵权必究。
提示:投资有风险,入市须谨慎,本资讯不作为投资理财建议。
本内容旨在传递行业动态,不构成投资建议或承诺。