IPFS IPLD 數據格式擴展實踐

1. 概述

IPLD(Inter Planetary Linked Data)在IPFS起著非常重要的作用,其概念性的介紹可以參考Protocol Labs的項目官方網站。本文主要介紹如何集成一個類似新區塊鏈項目的數據塊到IPFS系統中,從而實現區塊數據在IPFS系統的存儲、解析等功能。

目前IPFS有go和Javascript等多種語言的實現方式,本文主要從go語言的代碼系統來介紹。在go語言的IPFS系統中除了內部已經實現了JSON、RAW、CBOR以及Protobuf格式的數據存儲外,同時也提供一個Plugin的方式來支持、實現新的IPLD 數據格式的擴展與支持,新的數據格式通過一個Plugin來實現IPLD所要求的接口即可完成集成。由於這個Plugin是基於go語言的插件(把go語言實現代碼轉換成動態庫文件,比如linux的so文件)機制來實現的,其存在windows系統中不支持的限制。

2. 實踐

2.1 IPFS plugin加載介紹

在IPFS daemon啟動過程中會調用一個makeExecutor的函數,該函數的一個主要工作就是加載和初始化IPFS的plugins,IPFS的插件缺省是存放home目錄(以linux為例)的.ipfs/plugins中。代碼參考如下:


IPFS IPLD 數據格式擴展實踐


在LoadPlugins這個函數中完成了plugin的加載和初始化工作:

<code>// Plugin is base interface for all kinds of go-ipfs plugins// It will be included in interfaces of different Pluginstype Plugin interface {   // Name should return unique name of the plugin   Name() string   // Version returns current version of the plugin   Version() string   // Init is called once when the Plugin is being loaded   Init() error}// PluginIPLD is an interface that can be implemented to add handlers for// for different IPLD formatstype PluginIPLD interface {   Plugin   RegisterBlockDecoders(dec ipld.BlockDecoder) error   RegisterInputEncParsers(iec coredag.InputEncParsers) error}/<code>

這裡重點要關注的是:

1) LoadDynamicPlugins需要每個so文件有一個"Plugins"的全局變量符號,通過這個變量來告訴IPFS這個so文件有幾個plugins從而進一步獲取到每個plugin的實例。比如GIT plugin的代碼:

<code>// Plugins is exported list of plugins that will be loadedvar Plugins = []plugin.Plugin{   &gitPlugin{},}/<code>

2) 初始化initialize和run的實現: 通過"Plugins"獲取到plugin的具體實例後initialize會調用每個plugin的Init的函數,通過這個接口我們可以判斷所實現plugin是否成功加載;run會把plugin所實現數據格式的編解碼函數註冊到BlockDecoder中(IPFS數據是以block形式存儲),完成這一步後一個IPLD新數據格式就基本具備數據導入和數據解析的功能了,同時plugin的基本輪廓也形成了。

2.2 IPFS plugin實現

要實現一個IPFS plugin基本上就是要實現:一個變量,這個變量指的就是前面介紹過的"Plugins";兩個接口,即PluginIPLD 和Node兩套接口。下面分別對兩個接口做介紹:

1) IPFS plugin接口

<code>// Plugin is base interface for all kinds of go-ipfs plugins// It will be included in interfaces of different Pluginstype Plugin interface {   // Name should return unique name of the plugin   Name() string   // Version returns current version of the plugin   Version() string   // Init is called once when the Plugin is being loaded   Init() error}// PluginIPLD is an interface that can be implemented to add handlers for// for different IPLD formatstype PluginIPLD interface {   Plugin   RegisterBlockDecoders(dec ipld.BlockDecoder) error   RegisterInputEncParsers(iec coredag.InputEncParsers) error}/<code> 

前面加載流程介紹中已經提到調用plugin的相關接口函數,其中RegisterInputEncParsers 、RegisterBlockDecoders分別向IPFS的BlockDecoder註冊編、解碼函數,RegisterInputEncParsers註冊的函數負責把外部數據按照IPFS DAG Node的格式進行編碼,反之RegisterBlockDecoders註冊的函數負責解析編碼的Node數據,這樣IPFS在查詢到一個該數據格式的Block後就能正確解析對應的數據。實現範例:

<code>func (*gitPlugin) RegisterBlockDecoders(dec format.BlockDecoder) error { dec.Register(cid.GitRaw, git.DecodeBlock) return nil}/<code>

GitRaw是 GIT數據格式所對應的codec類型,這個後面會介紹。

<code>func (*gitPlugin) RegisterInputEncParsers(iec coredag.InputEncParsers) error { iec.AddParser("raw", "git", parseRawGit) iec.AddParser("zlib", "git", parseZlibGit) return nil}/<code>

IPFS 存儲對象接口

IPFS 數據是以M-DAG(Merkle有向無環圖)作為數據結構來描述數據對象的關係,圖中的每一個節點是一個對象(實際數據),兩個節點之間的邊是一個Link。IPFS DAG中的每一個節點就是一個Node。既然數據是一個Node形式來表現,為此對每一個Plugin來說自然就需要實現Node接口所對應的函數(或方法),從而保證整個DAG圖的完整、正確的解析。

DAG中的每一個Node都有一個Cid,在IPFS系統中看到的數據HASH正是來之這個Cid數據結構。Cid 是IPFS分佈式文件系統中標準的文件尋址格式,它集合了內容尋址、加密散列算法和自我描述的格式, 是IPLD 內部核心的識別符。目前有2個版本: CIDv0 和CIDv1,在實際數據格式擴展應用中基本上是v1版本(v0是ProtoBuf格式)。

<code>type Cid struct { version uint64 codec uint64 hash mh.Multihash}type BlockDecoder interface { Register(codec uint64, decoder DecodeBlockFunc) Decode(blocks.Block) (Node, error)}/<code>

對一個新的IPLD數據格式就需要定義一個新的codec,然後BlockDecoder根據這個codec來註冊編解碼函數(前面已介紹)。

<code>// Block provides abstraction for blocks implementations.type Block interface { RawData() []byte Cid() *cid.Cid String() string Loggable() map[string]interface{}}// Node is the base interface all IPLD nodes must implement.//// Nodes are **Immutable** and all methods defined on the interface are// **Thread Safe**.type Node interface { blocks.Block Resolver // ResolveLink is a helper function that calls resolve and asserts the // output is a link ResolveLink(path []string) (*Link, []string, error) // Copy returns a deep copy of this node Copy() Node // Links is a helper function that returns all links within this object Links() []*Link // TODO: not sure if stat deserves to stay Stat() (*NodeStat, error) // Size returns the size in bytes of the serialized object Size() (uint64, error)}/<code>

在完成上述的PluginIPLD以及Node接口函數的實現後一個Plugin就基本完成了,然後通過一個全局的“Plugins“變量來暴露出該plugin so文件所支持的plugin。Plugin實現後接下來就是集成到IPFS系統中,Plugin的集成有兩種方式:一是直接把相關代碼放到IPFS的Plugins目錄中進行統一編譯(可參考IPFS已實現的GIT plugin),這種方式不方便後續維護,每次更新IPFS代碼都需要重新集成;二是直接把plugin的代碼單獨編譯,然後把編譯後的plugin so文件放到IPFS 的plugins輸出目錄即可,這種方式要注意的是需要按照Go語言的Plugin編譯方式來編譯,類似:go build -buildmode=plugin -o=***.so 。

2.3 Plugin驗證

命令行的方式:

<code>cat file | ipfs dag put --input-enc raw –format new_format/<code>

file是需要導入的文件名,new_format是plugin實現的新數據格式,比如GIT plugin介紹中的git.

在提交這個命令後就會調用對應的plugin所註冊的encode函數,比如GIT plugin中的parseRawGit。

代碼方式:

直接調用coredag.ParseInputs,比如coredag. ParseInputs (“raw”, “git”, file, mhType, -1)。實際行命令行執行時調用的就是這個函數完成相關動作。


分享到:


相關文章: