IPFSについて勉強した

ipfs.io

IPFS とは / BitTorrent とどう違うのか

P2P分散ファイルシステム

BitTorrentがトラッカーファイルと呼ばれる単位でファイル共有を行うのに対し、IPFS は内部が別のオブジェクトを指し示すポインタ(ディレクトリ相当) or またはバイナリ(ファイル相当)のものがDAG構造をとっていて、それぞれにユニークなキーが振られている。

IPFSの内部構造は Git の内部オブジェクトと非常に似ている。分散ファイルシステムとしての Git との概念的な差はほとんどなくて、両者ともにコンテンツアドレッシング方式の Merkle DAG 構造ということができる。調べた感じ各種ツリーフォーマットの表現の差や、オブジェクトの圧縮方法が違うだけ、といっていってよさそう。

IPFSで配布する際は、URLのような名前空間に対して保存するのではなく、オブジェクトまたはその集合による Merkle によって一意な sha256 のキーが決まり、それが一意性を示すポインタとなる。なのでsha256一致させる高コストな攻撃をされない限り、コンテンツに対してユニークなキーといっていい。

IPFSのP2Pノードとしての振る舞いは BitTorrentに似ており、各ノードがほしいブロックリストと、提供できるブロックリストを公開し、その希少度や見返りのインセンティブによってスコアが付けられる(BitSwapプロトコル)

なので、要約すると Git の分散ファイルシステム + BitTorrentインセンティブ設計に基づいたファイルブロック交換システムといって良さそう。

P2Pなので、アップロードしたファイルはネットワーク参加者全員の同意を取らないと消せない(=ほぼ消せない)。

libp2p(-js) というミドルウェア層があり、p2pネットワーク内での webrtc/websocket/http を抽象している。IPを利用しているにすぎず、よりメタなプロトコルを採用することも、理論上は可能。

公開してみた

まず、 brew install ipfs などして ipfs コマンドを入れるとする。これで入るのはGoのリファレンス実装で、他にJS実装がそこそこ。CとPythonが In Progress.

こんな感じでアップロードして、ノードに参加し広めることができる

mkdir pub-to-ipfs
echo hello > hello.txt
ipfs add -r .
ipfs daemon # IPFSネットワークの参加ノードとなり、ファイルを配布する

https://ipfs.io/ がデフォルトのゲートウェイで、 https://ipfs.io/ipfs/<ObjectID> という風にアクセスするとその中身が取れる。ipfs daemonlocalhost:8080 にローカルのゲートウェイを立ててくれる。

試しに自分のツイッターのアイコンを投げてみた。アクセスできると思う https://ipfs.io/ipfs/QmdqSc9ZKcdzrdfjRmMLms4jH3JtFQCBALyJNTSaEwi59m/saboten.png

Git をホスティングする

https://kotet.github.io/2018/06/04/ipfs-git-readonly.html が非常に参考になった

手元にあった next-editor のソースを投げてみた。 .git オブジェクトをそのまま投げる。

cd .git
ipfs add . -r

Git オブジェクトは 5MB程度なのを確認した。

git clone https://ipfs.io/ipfs/QmQFVzbiueHb6CtuN7aa6W67Uzr9kaeVKsVYLHoFLWDcxz --single-branch

僕の環境で ipfs daemon のノードのPeerが800程度で、Twitter で頼んでこれを clone してもらったところ、およそ1分~6分という結果になった。使い物になるかどうか、微妙なラインでなんとも言えない…。

(アクセス絞るのに --depth 1 しようとしたが、素朴なファイルホスティングだと smart http プロトコルにならず、 dumb http プロトコルだと --depth が使えなかった)

動的なコンテンツを追跡するには ipns というものを使うらしい。まだ使ってないので後で調べる。

わからなかったこと / 次に調べること

やりたかったこととして、 https://next-editor.app は完全な静的サイトで、pure js な Git 実装を積んでて、PWAでオフラインで動くように作っているのだが、 GitHub にアップロードするにはアクセストークンなりCORS迂回のゲートウェイなりの準備が大変。なので、手軽にGitを共有する対象として IPFS へのアップロードする選択肢を用意できるかどうかを検証していた。というか Git って本来分散システムだからGitHubが権威サーバーである必要もないと思っているのだが…。

クライアント単独で git clone し publish までいけたら夢があるし、たぶんいける。検証中。

上記の方法で、サーバーを立ててゲートウェイを通してアップロードできるのはわかった。しかしもっというとブラウザで完全に完結させたくて js-ipfs でIPFSのオブジェクトを作ってブラウザで単独のノードとしてそれを配布したかったが、そのやり方が分からなかった。概念上 WebRTC でP2P への参加ノードになれるはずだが…

共有するためのオブジェクトを作るまではできてて、こんな感じ。

import pify from "pify";
import IPFS from "ipfs";

const streamFiles = (root, files, cb) => {
  const stream = node.files.addReadableStream();
  stream.on("data", data => {
    console.log(`Added ${data.path} hash: ${data.hash}`);
    // The last data event will contain the directory hash
    if (data.path === root) {
      cb(null, data.hash);
    }
  });
  files.forEach(file => stream.write(file));
  stream.end();
};

const repoPath = "ipfs-" + Math.random();
const node = new IPFS({ repo: repoPath });
global.ipfs = node; // DEBUG

node.on("ready", async () => {
  const root = "xxx";
  const files = [
    {
      path: `${root}/file1.txt`,
      content: node.types.Buffer.from("one", "utf8")
    },
    {
      path: `${root}/file2.txt`,
      content: node.types.Buffer.from("two", "utf8")
    }
  ];

  const directoryHash = await pify(streamFiles)(root, files);
  const newFiles = await pify(node.ls)(directoryHash);

  console.log(`${root} - ${directoryHash}`);
  newFiles.forEach(f => console.log(f.name, f.path, f.hash));
});

もうちょい調べる。