Proficient in IPFS: IPFS boot boot function

In the previous article, we learned about the startup of IPFS as a whole. Today we will continue to look at how the boot function actually starts the system. This function is located in the core/boot.js file.

Before we start looking at the boot function, let's start with the async class library. Async is a utility module that provides direct, powerful functions for handling asynchronous JavaScript. Here we briefly talk about three functions, such as waterfall, parallel, and series. These three functions are used frequently.

The waterfall function, which receives a function array or object and a callback function, first calls the first function (or the function corresponding to the first Key), calls the latter function with its result as a parameter, and then calls the result returned by a later function. The next function, and so on, when all function calls are completed, the user-specified callback function is called with the result returned by the last function as a parameter. If one of the functions throws an exception, the following function will not be executed and will immediately pass the error object to the specified callback function.

The parallel function receives a function array or object and a callback function. The functions in the array execute in parallel without waiting for the previous function to complete. When all the function calls are completed, the execution results of all functions are combined into an array and passed to the final. Callback function. If a function throws an exception, it will immediately pass the error object to the specified callback function.

The series function receives an array of functions or an object and a callback function. The functions in the array are executed serially, that is, the previous execution is completed before the next execution. If one of the functions throws an exception, the following function will not be executed and will immediately pass the error object to the specified callback function. boot function execution flow is as follows

  1. Initialize several variables used.
     const options = self._options const doInit = options.init const doStart = options.start 
  2. Call the waterfall function of the async class library. Here, a total of 3 functions are executed, and we look at these three functions.
    • First, execute the first function. The function first checks if the repository status is not closed. If the repository is not closed, the second function is called directly. Otherwise, the open method of the repository (located in the index.js file of the ipfs-repo project) is called to open the repository.

The body of the open method of the repository is also a waterfall function. Inside the warehouse's waterfall function, first call the open method of the root object to open main directory (the default repository uses the file system to save data, using the datastore-fs class library), because the main directory has been created when the repository object is initialized. So this method does not do anything, then call _isInitialized method to check whether the repository has been initialized, this method will check whether the configuration file, specification file, version file exists. For the first time, these files do not exist, the method directly throws an exception, causing all methods under _isInitialized no longer execute, and the process goes directly to the specified error handling. Also, because the lock file does not exist at this time, the callback(err) method is called directly, and returns to the callback function of the open method. For the case that it is not the first time to come in, the specific processing is detailed in the init function execution analysis.

The warehouse open method code is as follows, we will encounter this function later, not to elaborate here.

 open (callback) {    if (!this.closed) {      setImmediate(() => callback(new Error('repo is already open')))      return // early    }    waterfall([      (cb) => this.root.open(ignoringAlreadyOpened(cb)),      (cb) => this._isInitialized(cb),      (cb) => this._openLock(this.path, cb),      (lck, cb) => {        log('aquired repo.lock')        this.lockfile = lck        cb()      },      (cb) => {        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)      },      (blocks, cb) => {        this.blocks = blocks        cb()      },      (cb) => {        log('creating keystore')        this.keys = backends.create('keys', path.join(this.path, 'keys'), this.options)        cb()      },      (cb) => {        this.closed = false        log('all opened')        cb()      }    ], (err) => {      if (err && this.lockfile) {        this._closeLock((err2) => {          if (!err2) {            this.lockfile = null          } else {            log('error removing lock', err2)          }          callback(err)        })      } else {        callback(err)      }    }) } 

In the callback function of the open method, call the isRepoUninitializedError method to check the cause of the error. The reason here is that the repository has not been initialized yet, so this method returns true, so call the second function with false .

The code for the first function is as follows:

 (cb) => {  if (!self._repo.closed) {    return cb(null, true)  }  // 打开仓库  self._repo.open((err, res) => {    if (isRepoUninitializedError(err)) return cb(null, false)    if (err) return cb(err)    cb(null, true)  }) } 
  • Next, execute the second function. If it is not the first time to come in, then the warehouse already exists, then open the warehouse directly.
  • If it is the first time, the repository does not exist, so there is no way to open it, ie the repoOpened parameter is false, so skip the top initialization. Then, check if the doInit variable is true, if true, initialize the repository according to the specified options. The value of the doInit variable here comes from the init attribute in the option. This attribute is just a simple true value, so it is initialized with the default configuration.

    The code for the second function is as follows:

     (repoOpened, cb) => {  if (repoOpened) {    return self.init({ repo: self._repo }, (err) => {      if (err) return cb(Object.assign(err, { emitted: true }))      cb()    })  }  // 如果仓库不存在,这里需要进行初始化。  if (doInit) {    const initOptions = Object.assign(      { bits: 2048, pass: self._options.pass },      typeof options.init === 'object' ? options.init : {}    )    return self.init(initOptions, (err) => {      if (err) return cb(Object.assign(err, { emitted: true }))      cb()    })  }  cb() } 

    Note that the true value in JS is not necessarily only a true , it may be an object, a function, an array, etc., where the detection is true, just to detect whether the user has specified this configuration, and to ensure that it is not false.

    The above self refers to the IPFS object, and the init method is located in the core/components/init.js file. In the next article, we will carefully explain the implementation of this function.

  • Next, execute the third function. Check if no startup is required, and if so, call the final callback function directly.
  • Start the IPFS system by calling the start method of the IPFS object. This function is to look at the initial process of analysis.

     (cb) => {  if (!doStart) {    return cb()  }  self.start((err) => {    if (err) return cb(Object.assign(err, { emitted: true }))    cb()  }) } 
  • Next, execute the final callback function. If none of the first three functions are present, the ready event of the IPFS object is triggered; if there is an error, the corresponding error is triggered.
     (err) => {    if (err) {      if (!err.emitted) {        self.emit('error', err)      }      return    }    self.log('booted')    self.emit('ready') } 

    When the execution of the waterfall function is completed, our IPFS will actually start successfully, and the user can use it to do whatever it wants.

  • Through the above analysis, we found that the IPFS startup is divided into three steps, 1) open the warehouse; 2) IPFS initialization; 3) IPFS startup, and the boot function is a large general manager that controls the startup process of the IPFS system.