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)。实际行命令行执行时调用的就是这个函数完成相关动作。


分享到:


相關文章: