精通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 對象時,就開始執行它的構造函數,接下來我們分析下這個構造函數。

調用父類構造函數。

設置系統所使用的環境變量。首先,設置系統默認選項。
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 函數,使用用戶指定選項參數來合併默認選項。
最後,處理選項的 init、start 兩個配置,代碼如下:

if (options.init === false) {

this._options.init = false

}
if (!(options.start === false)) {

this._options.start = true

}

如果在選項中指定了倉庫並且類型爲字符串,或者沒有指定倉庫,那麼設置所使用的倉庫就是默認倉庫,否則,使用用戶指定的倉庫。
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 函數來設置倉庫的選項。這個函數把用戶指定的選項與倉庫默認的選項進行合併,同時處理 storageBackends、storageBackendOptions 兩個選項。因爲,生成默認倉庫時只指定了倉庫的目錄,而沒有指定任務其他選項,所以默認倉庫會使用默認選項。默認選項如下,

{

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,接下來依次創建版本文件、配置文件、存儲規定等對象。

注意,這些動作完成之後,倉庫對象即初始完成,但此時除了主目錄外,別的目錄及文件並沒有真正創建,它們的真正創建要等到啓動初始化時纔會進行。

接下來生成內部需要的對象。
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 對象此時爲空,直到系統啓動階段纔會生成並進行設置。

再接下來擴充系統核心組件,主要用於節點的初始化、啓動、終止、關閉等。
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)

這些命令後面再進行具體講解,此處略去不講。

再接下來擴充一些交互相關的組件,包括文件相關的操作。
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)

上面兩步對系統的擴充可以進行直接調用,也可以通過程序來調用。對應於命令行中的相關命令。

最後,調用 boot 函數進行系統啓動。

到這裏,宏觀上 IPFS 節點已經啓動,可以調用各種命令來把玩系統。

作者:喬瘋,加密貨幣愛好者,ipfs 愛好者,黑螢科技CTO。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章