区块链给我们带来什么(二)IPFS协议——分布式文件系统

in #blockchain7 years ago

星际文件系统IPFS(InterPlanetary File System)是一个面向全球的、点对点的分布式版本文件系统,目标是为了补充(甚至是取代)目前统治互联网的超文本传输协议(HTTP),将所有具有相同文件系统的计算设备连接在一起。

IPFS想要实现的是一个去中心化的分布式web网络。内容不再通过中心服务器响应,而是以P2P的方式从邻近的对等节点拉取;同时全网维护一个统一的路由表,每个节点作自我调整,以保证节点与数据的动态增删、完整性、去冗余等细节问题。

为什么需要IPFS

根据官方网站的介绍,传统的HTTP协议具有以下不足之处:

  1. HTTP的效率低下,并且服务器昂贵。使用HTTP协议从中心化的服务器集群中一次需要下载一个完整文件,而P2P的方式可以从许多peers(对等节点)中下载不同的数据块,经研究可以节省60%的带宽成本。
  2. 历史文件被删除。网页的平均寿命是100天,部分网站数据不能得到永久保存。这也是受限于中心化服务器的高存储成本。
  3. HTTP的中心化限制了发展机会。如下图,全球互联网的域名解析服务,根源上是由13个根服务器所提供。同时主要的云服务也由几家重要的云服务商所提供。政府和机构可以在这些中心化集群前截取HTTP消息包,窥探和监控网民的生活;黑客们也可以通过DDOS等手段攻击中心化的服务器集群,网络瘫痪的案例屡见不鲜。
  4. 网络应用过于依赖主干网。当主干网因为不可抗力因素造成拥塞或宕机等,无法继续服务时,应用也会受到影响。

HTTP协议诞生20年来,协议也从1.0到2.0,但web应用本质上还是基于B/S架构的模式,它的根本劣势仍然无法得到很好的改进。

IPFS的工作原理

IPFS的出现,则是为了解决中心化web的这些问题。它从本质上改变了网络数据的分发机制

  • 每个文件及其其包含的所有数据块,都会转换为一个散列字符串,称为哈希指纹
  • 每个节点维护一张DHT(分布式哈希表),包含相应数据块与目标节点的对应映射关系。整个哈希表被组织成二叉树,平均查询联系节点的复杂度是O(log2N)。例如要查询10000万节点只需20跳。
  • 基于内容寻址而非域名寻址。只需要通过文件或数据块的哈希值,IPFS便可自动在全网节点中找到拥有这些数据块的节点,并从节点上拉去数据。
  • IPFS使用一个叫IPNS的分布式命名系统,将难于记忆的数据哈希值映射为易于记忆的字符串。这可以类比于域名与IP地址的映射关系。

IPFS具有如下一些特性:

  • 相同数据内容被赋予唯一的哈希指纹,通过哈希指纹的对比即可判断数据块是否一致。
  • 节点本身使用类似git的版本控制系统,来管理本地文件与数据块。这既保证了数据块的去冗余,又提供了可追溯的历史版本。
  • IPFS节点在维护哈希路由表、账本一致性方面,需使用区块链技术,一方面是在动态增减内容、节点方面与全网达成共识;另一方面是为激励机制中代币发行与账本管理建设基础平台。
  • 通过发行代币来激励节点存储稀有的数据块。可参考filecoin.io
  • 节点不仅可从其他节点拉取所需数据,同时也可将该新数据存储在自己节点,供其他节点下载。

一个剧情

小明想要观看一部xxx.avi的视频

  1. 小红和小刚以前看过该视频,于是他们将视频文件加入IPFS网络,得到相同的哈希指纹B。(现实中,若该视频在周边好几个节点都持有,IPFS会把文件分块去重,节省节点的存储成本)
  2. 小明在本地通过哈希指纹B(形如 /ipfs/B 的路径名),试图从IPFS网络拉取该视频。小明不关心最终的视频数据来自哪些节点。
  3. 小明的节点索引DHT中的哈希值所对应的节点列表,并行地从这些节点下载部分数据块。(注意这里是部分,IPFS网络会自动从各节点下载部分数据块,再由本地的manager拼成完整的文件)
  4. 小明的节点获得了这个视频,不仅自己可以观看,还可以为其他人提供资源。

技术白皮书

以下是对技术白皮书的一些梳理,不感兴趣的读者可直接跳过。

身份信息的生成与验证

节点通过NodeId唯一标识。它通常是使用S/kademlia的静态加密难题所创建的公钥。节点会存储它的公私钥对,用户可以在每次初始化时注册成为一个“新”节点,但这导致损失积累的网络收益。

type NodeId Multihash
type Multihash []byte  // 自描述加密哈希摘要
type PublicKey []byte
type PrivateKey []byte // 自描述的私钥
type Node struct {
    NodeId NodeID
    PubKey PublicKey
    PriKey PrivateKey
}
//基于S / Kademlia的IPFS身份生成:
difficulty = <integer parameter>
n = Node{}
do {
    n.PubKey, n.PrivKey = PKI.genKeyPair()
    n.NodeId = hash(n.PubKey)
    p = count_preceding_zero_bits(hash(n.NodeId))
} while (p < difficulty)

第一次连接时,对等节点交换公钥并检查:对方的NodeId是否等于公钥的哈希值。若否,则终止连接。

网络

IPFS可使用任何网络,但不承担对IP的获取也不依赖于IP层。它有如下几点特性:

  • 传输方面(Transport):可用任意传输层的协议,甚至包含WebRTC与uTP。
  • 可靠性(Reliability):在下层设施无法保证可靠性时,使用uTP和SCTP来提供可靠性。
  • 连接(Connectivity):使用ICE NET穿透技术。
  • 完整性(Integrity):可使用哈希校验完整性。
  • 可验证性(Authenticity):使用发送者的公钥集合HMAC算法来检查消息的真实性。

路由

IPFS的路由表使用基于S/Kademlia和Coral的分布式松散哈希表(DSHT)。接口如下:

type IPFSRouting interface {
    FindPeer(node NodeId) // 获取特定NodeId的网络地址。
    SetValue(key []bytes, value []bytes) // 往DHT存储一个小的元数据。
    GetValue(key []bytes) // 从DHT获取元数据。
    ProvideValue(key Multihash) // 声明这个节点可提供一个大的数据。
    FindValuePeers(key Multihash, min int) // 获取服务于该大数据的节点。
}

注意:不同的用例将要求基本不同的路由系统(例如广域网中使用DHT,局域网中使用静态HT)。因此,IPFS路由系统可以根据用户的需求替换的。只要使用上面的接口就可以了,系统都能继续正常运行。

块的交换——BitSwap协议

IPFS 中的BitSwap协议受到BitTorrent 的启发,通过对等节点间交换数据块来分发数据的。与BT类似, 每个节点寻找自己需要的数据块集合(wangt_list),同时也提供已有的数据块集合作交换(have_list)。但与BT不同的是,BitSwap不局限于一个torrent中的数据块。BitSwap协议中存在一个永久市场,这个市场包含各个节点所拥有的所有块数据,而不管这些数据块来自于哪个文件。在基本情况下,BitSwap节点必须以块的形式彼此提供直接的值。只有当跨节点的块的分布是互补的,即各取所需的时候,才会获得最好的效果。 通常情况并非如此,在某些情况下,节点必须为自己的块而工作。 在节点没有其对等节点所需的(或根本没有的)情况下,它会以更低的优先级去寻找对等节点想要的块(节点可能无法通过提供数据块而受益)。这会激励节点去缓存和传播稀有片段, 即使节点对这些片段不感兴趣。BitSwap协议包含以下关键部分:BitSwap 信用协议必须带有激励机制,去激励节点去seed 其他节点所需要的块,而它们本身是不需要这些块的。因此,BitSwap的节点很积极去给对端节点发送块,并期待获得报酬。但必须防止水蛭攻击(空负载节点从不共享块)。一个简单的类信用系统需要解决以下问题:

  • 对等节点间通过字节认证的方式追踪平衡,即确保数据块尽量均衡地分散在各节点,而非大量集中在某一两个节点中。
  • 对等节点以一定的概率向债务方节点发送数据块,这个概率随着债务的增加而降低。

BitSwap 策略BitSwap 对等节点采用很多不同的策略,这些策略对整个数据块的交换执行力产生了不同的巨大影响。功能策略的选择应该致力于达成以下目标:

  • 最大化节点及块交换的交易性能。
  • 防止空负载节点利用和损坏交易。
  • 有效抵抗其他未知策略。
  • 对守信任的节点放宽限制。

有一个实践的例子是使用sigmoid函数,根据债务比例(debt retio)进行放缩:r=\frac{bytesSent}{bytesRecv+1} 根据r计算发送到负债节点的概率:p(send | r)=1-\frac{1}{1+exp(6-3r)} 负债比是信任的衡量标准。对应之前成功交换过很多数据的节点会更宽容,而对不受信任或不了解的节点会严格许多。这么做可以:

  • 抵御那些创造大量新节点的攻击者。
  • 保护之前成功的交易关系,即使某个节点暂时无法提供数据。
  • 阻塞已经恶化的交易关系中节点间的通信,直到被再次证明。

BitSwap 账本BitSwap节点保存了一个记录与所有其他节点之间交易的账本。这可以让节点追踪历史记录以及避免被篡改。当激活了一个链接,BitSwap节点就会互换它们账本信息。如果这些账本信息并不完全相同,账本将会重新初始化,那些应计信贷和债务会丢失。恶意节点会有意去抹去“这些“账本,从而期望清除自己的债务。节点是不太可能在失去了应计信用的情况下还能累积足够的债务去授权认证。而伙伴节点可以自由地将其视为不当行为, 拒绝交易。账本的数据结构:

type Ledger struct {
    owner NodeId     //节点id
    partner NodeId   //伙伴节点id
    bytes_sent int   //发送字节总量
    bytes_recv int   //收到字节总量
    timestamp Timestamp
}

BitSwap 协议详解数据结构如下:

// Additional state kept
type BitSwap struct {
   ledgers map[NodeId]Ledger // Ledgers known to this node, inc inactive
   active map[NodeId]Peer // currently open connections to other nodes
   need_list []Multihash // checksums of blocks this node needs
   have_list []Multihash // checksums of blocks this node has
}
type Peer struct {
   nodeid NodeId
   ledger Ledger // Ledger between the node and this peer
   last_seen Timestamp // timestamp of last received message
   want_list []Multihash // checksums of all blocks wanted by peer 
   // includes blocks wanted by peer's peers
}
// Protocol interface:
interface Peer {
   open (nodeid : NodeId, ledger : Ledger);
   send_want_list (want_list : WantList);
   send_block(block: Block) -> (complete:Bool);
   close(final: Bool);
}

对等连接的生命周期:

  • Open:对等节点间发送ledgers直到他们达成一致。
  • Sending:对等节点间交换want_lists和数据块。
  • Close:对等节点断开连接。
  • Ignored:如果节点采取了不发送的策略,则其对等体被忽略(在一段预设的超时时间段内)

相关API:

  • Peer.open(NodeId,Ledger)
  • Peer.send_want_list(WantList)
  • Peer.send_block(Block)
  • Peer.close(Bool)

Merkle DAG 对象

IPFS建造了一个Merkle DAG(无回路有向图),对象之间的links都是hash加密嵌入在源目标中。这是Git数据结构的一种推广。它给IPFS提供了很多有用的属性:

  • 内容地址化:所有内容都是被多重hash校验和来唯一标识,包括links。
  • 防止篡改:所有内容用它的校验和来验证。如果数据被篡改或损坏,则校验和会发生变化。
  • 去冗余:所有拥有相同内容的对象只被存储一次。这里借鉴了git的tree和commits的原理,详细请看 Git内部原理

IPFS对象的数据结构:

type IPFSLink struct {
    Name string          // 此link的别名
    Hash Multihash       // 目标的加密hash
    Size int             // 目标总大小
}
type IPFSObject struct {
    links []IPFSLink     //links数组
    data []byte          //不透明内容数据
}

路径IPFS中对象所采用的路径格式是:

# format
/ipfs/<hash-of-object>/<name-path-to-object>
# example
/ipfs/XLYkgq61DYaQ8NhkcqyU7rLcnSa7dSHQ16x/foo.txt

文件

IPFS为模型化版本系统定义了一组对象模型,与Git很类似:

  1. block:一个可变大小的数据块。
  2. list:块或其他链表的集合。
  3. tree:块、链表和其他树的集合。
  4. commit:当前文件数在版本历史记录中的一个快照。

BlobBlob对象代表一个文件,并且包含一个可寻址的数据单元。IPFS文件可以使用lists或者blobs来表示。要注意的是,Blob没有链接。

{
    "data": "some data here",
    // blobs have no links
}

ListList对象包含一个有序的队列,该队列由blob或list对象组成。

{
    "data": ["blob", "list", "blob"],
    // lists have an array of object types as data
    "links": [
        { "hash": "XLYkgq61DYaQ8NhkcqyU7rLcnSa7dSHQ16x","size": 189458 },
        { "hash": "XLHBNmRQ5sJJrdMPuu48pzeyTtRo39tNDR5","size": 19441 },
        { "hash": "XLWVQDqxo9Km9zLyquoC9gAP8CL1gWnHZ7z","size": 5286 }
        // lists have no names in links
    ]
}

Tree在IPFS中,Tree对象与Git的tree类似:它代表一个目录,或者一个名字到哈希值的映射表。哈希值表示blobs,lists,其他的trees,或commits。

{
    "data": ["blob", "list", "blob"],
    // trees have an array of object types as data
    "links": [
        { "hash": "XLYkgq61DYaQ8NhkcqyU7rLcnSa7dSHQ16x","name": "less", "size": 189458 },
        { "hash": "XLHBNmRQ5sJJrdMPuu48pzeyTtRo39tNDR5","name": "script", "size": 19441 },
        { "hash": "XLWVQDqxo9Km9zLyquoC9gAP8CL1gWnHZ7z","name": "template", "size": 5286 }
        // trees do have names
    ]
}

Commit在IPFS中,commit对象代表任何对象在版本历史记录中的一个快照。它与Git的commit也非常类似,但它可以指向任何类型的对象(Git中只能指向tree或其他commit)。

> ipfs file-cat <ccc111-hash> --json
{
    "data": {
        "type": "tree",
        "date": "2014-09-20 12:44:06Z",
        "message": "This is a commit message."
    },
    "links": [
        { "hash": "<ccc000-hash>","name": "parent", "size": 25309 },
        { "hash": "<ttt111-hash>","name": "object", "size": 5198 },
        { "hash": "<aaa111-hash>","name": "author", "size": 109 }
    ]
}
> ipfs file-cat <ttt111-hash> --json
{
    "data": ["tree", "tree", "blob"],
    "links": [
        { "hash": "<ttt222-hash>","name": "ttt222-name", "size": 1234 },
        { "hash": "<ttt333-hash>","name": "ttt333-name", "size": 3456 },
        { "hash": "<bbb222-hash>","name": "bbb222-name", "size": 22 }
    ]
}
> ipfs file-cat <bbb222-hash> --json
{
    "data": "blob222 data",
    "links": []
}

将文件分割成lists和blobsIPFS提供了以下几个可选的选择:

  • 使用Rabin Fingerprints来选取合适的块边界。
  • 使用rsync rolling-checksum算法来检测块在版本之间的改变。
  • 允许用户指定一个可为特定文件而调整的块分割函数。

IPNS:命名以及易变状态

目前为止,IPFS桟形成了一个由对等块交换构建的内容可寻址的DAG对象。这提供了发布和获取不可变对象的服务,甚至可以追踪这些对象的版本历史。然而,我们还缺少一个关键部分:易变命名(mutable naming)。我们希望有类似域名与IP的多对一映射的关系,采用可变的命名映射到不可变的哈希值上。IPFS提供了如下几种方案:1. 自验证的命名系统使用SFS(Self-Certified Filesystems)中的命名方案,我们可以在指定加密命名空间下构建可自验证的名称。

  1. NodeId=hash(node.PubKey) 
  2. 为每位用户分配一个可变的命名空间,例如 /ipns/<NodeId>。
  3. 一个用户可以在此路径下发布一个由自己私钥签名的对象,例如路径 /ipns/XLF2ipQ4jD3UdeX5xp1KBgeHRhemUtaA8Vm/。
  4. 当其他用户获取该对象时,使用公钥进行验签,即验证所用的公钥是否与NodeId匹配。这验证了用户发布对象的真实性,同时也获取到了可变状态。

发布对象中任何links均可以在命名空间中充当子名称:

/ipns/XLF2ipQ4jD3UdeX5xp1KBgeHRhemUtaA8Vm/
/ipns/XLF2ipQ4jD3UdeX5xp1KBgeHRhemUtaA8Vm/docs
/ipns/XLF2ipQ4jD3UdeX5xp1KBgeHRhemUtaA8Vm/docs/ipfs

2. 人类友好名称IPNS使用很长的哈希值作为名称,很难被记住,对人类这种物种来说极不友好。因此我们需要采取一些改进措施,使得路径名称变得易于记忆:Peer Links即用户可以直接将其他用户的对象link到自己的对象上(命名空间、主目录等)。例如:

# Alice links 到Bob上
ipfs link /<alice-pk-hash>/friends/bob /<bob-pk-hash>
# Eve links 到Alice上
ipfs link /<eve-pk-hash/friends/alice /<alice-pk-hash>
# Eve 也可以访问Bob
/<eve-pk-hash/friends/alice/friends/bob
# 访问Verisign 认证域
/<verisign-pk-hash>/foo.com

DNS TXT IPNS记录如果/ipns/是一个有效的域名,则会在DNS TXT记录中查找到相应的记录。本质上是为哈希值起了一个别名。

# this DNS TXT record
ipfs.benet.ai. TXT "ipfs=XLF2ipQ4jD3U ..."
# behaves as symlink
ln -s /ipns/XLF2ipQ4jD3U /ipns/fs.benet.ai

可读的标识符IPFS支持将哈希地址译成可发音的单词:

# proquint语句
/ipns/dahih-dolij-sozuk-vosah-luvar-fuluh
# 将解析成
/ipns/KhAwNprxYVxKqpDZ

缩短名称服务这种方案跟我们今天的DNS和Web URL较为类似。直接上例子:

# 用户可以从下面获取一个link
/ipns/shorten.er/foobar
# 然后放到自己的命名空间
/ipns/XLF2ipQ4jD3UdeX5xp1KBgeHRhemUtaA8Vm

小结

一种新的技术想要替代旧的技术,无非是从两方面着手:

  1. 提升效率
  2. 降低成本

IPFS综合了先前P2P系统的优点,包括DHT、BitTorrent、Git和SFS等。它把P2P的格局放到了全网,更好地实现了从多个资源节点获取内容,不依赖主干网,也不局限于一个Torrent,提升了资源响应速度与可靠性;同时基于IPFS,我们可以实现一种更廉价、带奖励机制的分布式存储方案(如FileCoin),这为IPFS生态的发展提供了十足的想象空间。IPFS的实现得益于区块链技术的发展。在区块链诞生之前,对于IPFS的实现存在两个问题:

  1. 节点网络在维护路由表的一致性,特别是涉及到节点、资源的动态增删,节点的信用以及防欺骗和防free loader等方面,往往不得不采用一些中心化的解决方案(例如迅雷下载P2P加速),而这又违背了去中心的理念。
  2. 对节点实行奖惩机制,涉及到账本、信用管理,代币发行以及交易事务处理等等,在分布式架构下难以保证高可靠、高可用和安全防篡改。过去的解决方案也是引入一个中心化的机构作背书。

这些问题在今天来看,使用区块链技术,综合效率和成本两方面,是再合适不过的。在接下来的10年,我们一定会看到IPFS在分布式应用方面大行其道。基于区块链技术的杀手级应用,很可能也会因此到来。

Sort:  

Congratulations @lowesyang! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!