精通IPFS:IPFS 啟動之 init 函式

買賣虛擬貨幣

上一篇文章中,我們瞭解了 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. 儲存檔案鎖物件到倉庫物件中。程式碼如下,略過不講。

  (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下的開發,熟悉分散式架構設計!熟悉網際網路金融行業,具有豐富的網際網路金融產品開發經驗,對互聯金融有著深入的瞭解。

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;