區塊鏈給我們帶來什麼(二)IPFS分散式文件系統
星際文件系統IPFS(InterPlanetary File System)是一個面向全球的、點對點的分散式版本文件系統,目標是為了補充(甚至是取代)目前統治互聯網的超文本傳輸協議(HTTP),將所有具有相同文件系統的計算設備連接在一起。
IPFS想要實現的是一個去中心化的分散式web網路。內容不再通過中心伺服器響應,而是以P2P的方式從鄰近的對等節點拉取;同時全網維護一個統一的路由表,每個節點作自我調整,以保證節點與數據的動態增刪、完整性、去冗餘等細節問題。
為什麼需要IPFS
根據官方網站的介紹,傳統的HTTP協議具有以下不足之處:
- HTTP的效率低下,並且伺服器昂貴。使用HTTP協議從中心化的伺服器集群中一次需要下載一個完整文件,而P2P的方式可以從許多peers(對等節點)中下載不同的數據塊,經研究可以節省60%的帶寬成本。
- 歷史文件被刪除。網頁的平均壽命是100天,部分網站數據不能得到永久保存。這也是受限於中心化伺服器的高存儲成本。
- HTTP的中心化限制了發展機會。如下圖,全球互聯網的域名解析服務,根源上是由13個根伺服器所提供。同時主要的雲服務也由幾家重要的雲服務商所提供。政府和機構可以在這些中心化集群前截取HTTP消息包,窺探和監控網民的生活;黑客們也可以通過DDOS等手段攻擊中心化的伺服器集群,網路癱瘓的案例屢見不鮮。
- 網路應用過於依賴主幹網。當主幹網因為不可抗力因素造成擁塞或宕機等,無法繼續服務時,應用也會受到影響。
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的視頻
- 小紅和小剛以前看過該視頻,於是他們將視頻文件加入IPFS網路,得到相同的哈希指紋B。(現實中,若該視頻在周邊好幾個節點都持有,IPFS會把文件分塊去重,節省節點的存儲成本)
- 小明在本地通過哈希指紋B(形如 /ipfs/B 的路徑名),試圖從IPFS網路拉取該視頻。小明不關心最終的視頻數據來自哪些節點。
- 小明的節點索引DHT中的哈希值所對應的節點列表,並行地從這些節點下載部分數據塊。(注意這裡是部分,IPFS網路會自動從各節點下載部分數據塊,再由本地的manager拼成完整的文件)
- 小明的節點獲得了這個視頻,不僅自己可以觀看,還可以為其他人提供資源。
技術白皮書
以下是對技術白皮書的一些梳理,不感興趣的讀者可直接跳過。
身份信息的生成與驗證
節點通過NodeId唯一標識。它通常是使用S/kademlia的靜態加密難題所創建的公鑰。節點會存儲它的公私鑰對,用戶可以在每次初始化時註冊成為一個「新」節點,但這導致損失積累的網路收益。
type NodeId Multihashtype Multihash []byte // 自描述加密哈希摘要type PublicKey []bytetype 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計算髮送到負債節點的概率:
負債比是信任的衡量標準。對應之前成功交換過很多數據的節點會更寬容,而對不受信任或不了解的節點會嚴格許多。這麼做可以:
- 抵禦那些創造大量新節點的攻擊者。
- 保護之前成功的交易關係,即使某個節點暫時無法提供數據。
- 阻塞已經惡化的交易關係中節點間的通信,直到被再次證明。
BitSwap 賬本
BitSwap節點保存了一個記錄與所有其他節點之間交易的賬本。這可以讓節點追蹤歷史記錄以及避免被篡改。當激活了一個鏈接,BitSwap節點就會互換它們賬本信息。如果這些賬本信息並不完全相同,賬本將會重新初始化,那些應計信貸和債務會丟失。
惡意節點會有意去抹去「這些「賬本,從而期望清除自己的債務。節點是不太可能在失去了應計信用的情況下還能累積足夠的債務去授權認證。而夥伴節點可以自由地將其視為不當行為, 拒絕交易。
賬本的數據結構:
type Ledger struct { owner NodeId //節點id partner NodeId //夥伴節點id bytes_sent int //發送位元組總量 bytes_recv int //收到位元組總量 timestamp Timestamp}
BitSwap 協議詳解
數據結構如下:
// Additional state kepttype 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很類似:
- block:一個可變大小的數據塊。
- list:塊或其他鏈表的集合。
- tree:塊、鏈表和其他樹的集合。
- commit:當前文件數在版本歷史記錄中的一個快照。
Blob
Blob對象代表一個文件,並且包含一個可定址的數據單元。IPFS文件可以使用lists或者blobs來表示。要注意的是,Blob沒有鏈接。
{ "data": "some data here", // blobs have no links}
List
List對象包含一個有序的隊列,該隊列由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和blobs
IPFS提供了以下幾個可選的選擇:
- 使用Rabin Fingerprints來選取合適的塊邊界。
- 使用rsync rolling-checksum演算法來檢測塊在版本之間的改變。
- 允許用戶指定一個可為特定文件而調整的塊分割函數。
IPNS:命名以及易變狀態
目前為止,IPFS桟形成了一個由對等塊交換構建的內容可定址的DAG對象。這提供了發布和獲取不可變對象的服務,甚至可以追蹤這些對象的版本歷史。
然而,我們還缺少一個關鍵部分:易變命名(mutable naming)。我們希望有類似域名與IP的多對一映射的關係,採用可變的命名映射到不可變的哈希值上。
IPFS提供了如下幾種方案:
1. 自驗證的命名系統
使用SFS(Self-Certified Filesystems)中的命名方案,我們可以在指定加密命名空間下構建可自驗證的名稱。
- 為每位用戶分配一個可變的命名空間,例如 /ipns/<NodeId>。
- 一個用戶可以在此路徑下發布一個由自己私鑰簽名的對象,例如路徑 /ipns/XLF2ipQ4jD3UdeX5xp1KBgeHRhemUtaA8Vm/。
- 當其他用戶獲取該對象時,使用公鑰進行驗簽,即驗證所用的公鑰是否與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 recordipfs.benet.ai. TXT "ipfs=XLF2ipQ4jD3U ..."# behaves as symlinkln -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
小結
一種新的技術想要替代舊的技術,無非是從兩方面著手:
- 提升效率
- 降低成本
IPFS綜合了先前P2P系統的優點,包括DHT、BitTorrent、Git和SFS等。它把P2P的格局放到了全網,更好地實現了從多個資源節點獲取內容,不依賴主幹網,也不局限於一個Torrent,提升了資源響應速度與可靠性;同時基於IPFS,我們可以實現一種更廉價、帶獎勵機制的分散式存儲方案(如FileCoin),這為IPFS生態的發展提供了十足的想像空間。
IPFS的實現得益於區塊鏈技術的發展。在區塊鏈誕生之前,對於IPFS的實現存在兩個問題:
- 節點網路在維護路由表的一致性,特別是涉及到節點、資源的動態增刪,節點的信用以及防欺騙和防free loader等方面,往往不得不採用一些中心化的解決方案(例如迅雷下載P2P加速),而這又違背了去中心的理念。
- 對節點實行獎懲機制,涉及到賬本、信用管理,代幣發行以及交易事務處理等等,在分散式架構下難以保證高可靠、高可用和安全防篡改。過去的解決方案也是引入一個中心化的機構作背書。
這些問題在今天來看,使用區塊鏈技術,綜合效率和成本兩方面,是再合適不過的。
在接下來的10年,我們一定會看到IPFS在分散式應用方面大行其道。基於區塊鏈技術的殺手級應用,很可能也會因此到來。
推薦閱讀:
TAG:IPFS | 区块链Blockchain |