[IPFS] IPFS分散ファイルシステムを試してみる~その2~

#はじめに
前回の記事で分散ファイルシステムIPFSについてインストールやイニシャ
ライズ、コンテンツをリポジトリに登録することを試してきました。
今回はその続きとして、サイズの大きなコンテンツの登録やコンテンツの格納されたディレクトリ自体の登録・参照、ローカル環境とIPFSネットワークの同期までやってみようと思います。

まだgo-ipfsのインストールがお済みでない方はこちらをご覧ください ⇒ [[IPFS] IPFS分散ファイルシステムを試してみる](https://qiita.com/y_kitchens/items/f0d2c850cf14c2a414f9)

また前回IPFSのコマンドツールをipfsと記載していましたがgo-ipfsが正しいようなので今回からgo-ipfsと記載します。

#準備
###環境構築
前回Dockerコンテナを立ち上げコンテナ内で作業していました。
ただ前回のコンテナはローカル環境と作業フォルダをマウントしただけの環境でしたので、今回はポートのマッピングもして再度新しいコンテナを立ち上げようと思います。

前回作成したDocker imageはubuntuOSにwgetをインストールだけのimageですので、今回は前回go-ipfsをインストールした手順もDockerfileに追記し、再度imageを作成し、コンテナを立ち上げようと思います。

###Dockerfileに追記する
それでは前回のDockerfileにgo-ipfsのインストールの手順を追記します。

```text:Dockerfile
FROM ubuntu:latest

RUN apt-get update && apt-get install -y\
	wget

WORKDIR /work

RUN wget https://dist.ipfs.io/go-ipfs/v0.10.0/go-ipfs_v0.10.0_linux-amd64.tar.gz

RUN tar xvzf go-ipfs_v0.10.0_linux-amd64.tar.gz

RUN go-ipfs/install.sh
```
前回手動で実施した内容をそのまま記載しています。

###コンテナを立ち上げる
Dockerfileの記述が終わったら docker buildコマンドでimageを作成し、docker runコマンドでコンテナを作成します。
この時、-vオプションでローカル環境の作業フォルダとコンテナの/workフォルダをマウントするのと、-pオプションでポートをマッピングするのを忘れないようにしてください。

go-ipfsはデフォルトでポート番号4001番と5001番を使用するようなので、-pオプションでマッピングしてあげます。


#IPFSを使ってみる
###initする
コンテナが立ち上がったらipfs versionコマンドでバージョンを確認し、go-ipfsが無事インストールされていることを確認します。
go-ipfsのインストールが確認出来たら ipfs initコマンドでイニシャライズします。(このあたりの手順は前回やったので省略します)

###画像を登録してみる
go-ipfsではサイズの大きいコンテンツは256KBごとにデータ送受信の安定や速度のために分割されて登録されるようです。

今回は画像ファイルコンテンツをリポジトリに登録し、そのあと実際に登録されたコンテンツを別画像ファイルとして取得してみます。

作業フォルダに画像ファイルを格納します。今回は近所の居酒屋のチーズ奴の画像を登録してみます。

![A0FA3338-6E6D-4E5F-B46B-E13926A1AD78_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505203/61c73e50-5f09-f0bb-4c02-f51c07c41cf6.jpeg)

```bash
root@8995ca7fb137:/work# ls -l
-rw-r--r-- 1 root root    237 Oct  3 12:35 Dockerfile
-rw-r--r-- 1 root root 604504 Oct  9 04:57 cheeseyakko_5.jpg
```

この画像ファイルをipfs addコマンドでリポジトリに登録します。

```bash
root@8995ca7fb137:/work# ipfs add cheeseyakko_5.jpg 
added QmbGzMXQ9k2CaG7YZmF6GV5bWFQQ1b7SUyo6XsXLgjh66y cheeseyakko_5.jpg
 590.34 KiB / 590.34 KiB [=========================================================================================================] 100.00%
```

登録できたらipfs lsコマンドでコンテンツを確認してみます。

```bash
root@8995ca7fb137:/work# ipfs ls QmbGzMXQ9k2CaG7YZmF6GV5bWFQQ1b7SUyo6XsXLgjh66y
QmZZV8c9GvEKR6STUK2uo96zUXDMg77jKKDUoMABH7VXco 262144 
QmPU7WmGXy5hLUye67nK8MgfjG346ESJ3hB8GMRwKtznmp 262144 
QmUHJw1BkTjz54w4DNrV2QyNed45tkEEspSUay9tEQV3RW 80216  
```
画像ファイル(604504Byte)は262144Byte毎と余りの80216Byteに分割されたようです。
604504 = 262144 + 262144 + 80216

ipfs object getコマンドでこのコンテンツアドレスの構成を確認してみます。

```bash
root@8995ca7fb137:/work# ipfs object get QmbGzMXQ9k2CaG7YZmF6GV5bWFQQ1b7SUyo6XsXLgjh66y
{"Links":[{"Name":"","Hash":"QmZZV8c9GvEKR6STUK2uo96zUXDMg77jKKDUoMABH7VXco","Size":262158},{"Name":"","Hash":"QmPU7WmGXy5hLUye67nK8MgfjG346ESJ3hB8GMRwKtznmp","Size":262158},{"Name":"","Hash":"QmUHJw1BkTjz54w4DNrV2QyNed45tkEEspSUay9tEQV3RW","Size":80230}],"Data":"\u0008\u0002\u0018\ufffd\ufffd$ \ufffd\ufffd\u0010 \ufffd\ufffd\u0010 \ufffd\ufffd\u0004"}
```
分割されたそれぞれのコンテンツのパーツはコンテンツアドレスにリンクされているのが確認出来ますね。

では、この登録されたコンテンツアドレスから新しいjpgファイルを作成してみます。
ipfs catコマンドで取得したコンテンツデータを新しい画像ファイル(new_yakko.jpg)に格納してみます。

```bash
root@8995ca7fb137:/work# ipfs cat QmbGzMXQ9k2CaG7YZmF6GV5bWFQQ1b7SUyo6XsXLgjh66y > new_yakko.jpg
```

![B626B330-7C34-4C89-AABB-CD2B63810609_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505203/75e6e724-578b-9b39-c0eb-77d091d73f26.jpeg)

```bash
root@8995ca7fb137:/work# ls -l
-rw-r--r-- 1 root root    237 Oct  3 12:35 Dockerfile
-rw-r--r-- 1 root root 604504 Oct  9 04:57 cheeseyakko_5.jpg
-rw-r--r-- 1 root root 604504 Oct  9 05:11 new_yakko.jpg
```
データサイズも同じですね。
今回は試していないのですが、たぶん音声や動画も同じように登録・参照できると思います。

###ディレクトリを作成してみる
通常あるファイルはそれが関連する他のファイルとともにフォルダ(ディレクトリ)に格納し、管理していくと思います。
go-ipfsにもディレクトリをリポジトリに登録し、参照する際はディレクトリのアドレスとリンクされているコンテンツ名を指定する方法があります。
ここでは作業フォルダ配下に新しいフォルダを作成し、その中に格納されているファイル毎リポジトリに登録・参照することを試してみます。

作業フォルダ配下にtest_dirフォルダを作成し、hello.txtとhello2.txtを格納します。

```bash
root@8995ca7fb137:/work# mkdir test_dir
# hello.txtとhello2.txtを作成
root@8995ca7fb137:/work# echo "Hello world" > hello.txt
root@8995ca7fb137:/work# echo "Hello IPFS" > hello2.txt
root@8995ca7fb137:/work# mv hello.txt hello2.txt ./test_dir/
# test_dirフォルダに移動し、中身を確認
root@8995ca7fb137:/work# cd test_dir/
root@8995ca7fb137:/work/test_dir# ls
hello.txt  hello2.txt
root@8995ca7fb137:/work/test_dir# cat hello.txt 
Hello world
root@8995ca7fb137:/work/test_dir# cat hello2.txt 
Hello IPFS
# 作業フォルダに戻る
root@8995ca7fb137:/work/test_dir# cd ..
```

新しいディレクトリとファイルが作成出来たらこのディレクトリをリポジトリに登録します。
ディレクトリを登録する時はipfs add コマンドに-rオプションをつけます。

```bash
root@8995ca7fb137:/work# ipfs add -r test_dir/
added QmePw8gVcBMb8x6kAep6aMBAX23hCSk6iZW3i9VKkiFhu1 test_dir/hello.txt
added QmUVTKsrYJpaxUT7dr9FpKq6AoKHhEM7eG1ZHGL56haKLG test_dir/hello2.txt
added Qmco3Z21nYrNoV1Tijd8WNSYEpuRWbrJkhTkePu3fU1Zon test_dir
 23 B / 23 B [=====================================================================================================================] 100.00%
```
test_dirのコンテンツアドレスの構成を確認すると、test_dirに格納されているhello.txtとhello2.txtとリンクされて登録されているのが分かります。

```bash
root@8995ca7fb137:/work# ipfs object get Qmco3Z21nYrNoV1Tijd8WNSYEpuRWbrJkhTkePu3fU1Zon
{"Links":[{"Name":"hello.txt","Hash":"QmePw8gVcBMb8x6kAep6aMBAX23hCSk6iZW3i9VKkiFhu1","Size":20},{"Name":"hello2.txt","Hash":"QmUVTKsrYJpaxUT7dr9FpKq6AoKHhEM7eG1ZHGL56haKLG","Size":19}],"Data":"\u0008\u0001"}
```

このhello.txtとhello2.txtにアクセスするには、
・test_dirのコンテンツアドレス/ファイル名
・ファイルのコンテンツアドレス
の2通りの方法があります。

まずはtest_dirのコンテンツアドレス/ファイル名で指定する方法を試してみます。

```bash
root@8995ca7fb137:/work# ipfs cat Qmco3Z21nYrNoV1Tijd8WNSYEpuRWbrJkhTkePu3fU1Zon/hello.txt
Hello world
root@8995ca7fb137:/work# ipfs cat Qmco3Z21nYrNoV1Tijd8WNSYEpuRWbrJkhTkePu3fU1Zon/hello2.txt
Hello IPFS
```
通常のロケーション型ファイルシステムのような指定方法ですが、あくまでディレクトリのアドレスにリンクされたアドレスのコンテンツ名を指定している点は重要です。
コンテンツ自体にアクセスしているので2番目のファイルのコンテンツアドレスを直接指定してもコンテンツにアクセス出来ます。

```bash
root@8995ca7fb137:/work# ipfs cat QmePw8gVcBMb8x6kAep6aMBAX23hCSk6iZW3i9VKkiFhu1
Hello world
root@8995ca7fb137:/work# ipfs cat QmUVTKsrYJpaxUT7dr9FpKq6AoKHhEM7eG1ZHGL56haKLG
Hello IPFS
```

それではフォルダ内にフォルダがある場合どうなるのかを試してみます。

```bash
root@8995ca7fb137:/work# cd test_dir
root@8995ca7fb137:/work/test_dir# ls
hello.txt  hello2.txt
# test_dir内に新しくフォルダとファイルを作成
root@8995ca7fb137:/work/test_dir# mkdir test_dir2
root@8995ca7fb137:/work/test_dir# echo "Hello World" > hello3.txt
root@8995ca7fb137:/work/test_dir# mv hello3.txt ./test_dir2/
root@8995ca7fb137:/work/test_dir# cd test_dir2/
root@8995ca7fb137:/work/test_dir/test_dir2# ls
hello3.txt
# 作業フォルダに戻る
root@8995ca7fb137:/work/test_dir/test_dir2# cd ../..
```
test_dirをリポジトリに登録してみます。

```bash
root@8995ca7fb137:/work# ipfs add -r test_dir/
added QmePw8gVcBMb8x6kAep6aMBAX23hCSk6iZW3i9VKkiFhu1 test_dir/hello.txt
added QmUVTKsrYJpaxUT7dr9FpKq6AoKHhEM7eG1ZHGL56haKLG test_dir/hello2.txt
added QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u test_dir/test_dir2/hello3.txt
added QmWj72UchZvsKfT6nHXpA1ZJE8EwuyERbXCeq9MRWtzS39 test_dir/test_dir2
added QmQtzNn4Mh52Ha7hRefSaUeRP5g6CMhdWJBUUBXM5SH6Sz test_dir
 35 B / 35 B [=====================================================================================================================] 100.00%
```
対象フォルダ内のフォルダとその内部ファイルも再起的に登録されていて基本的な動作は変わらないですね。

アクセス方法も同じようです。

```bash
root@8995ca7fb137:/work# ipfs cat QmQtzNn4Mh52Ha7hRefSaUeRP5g6CMhdWJBUUBXM5SH6Sz/test_dir2/hello3.txt
Hello World
# 内部のディレクトリのアドレスを指定
root@8995ca7fb137:/work# ipfs cat 
QmWj72UchZvsKfT6nHXpA1ZJE8EwuyERbXCeq9MRWtzS39/hello3.txt
Hello World
# 直接コンテンツアドレスを指定
root@8995ca7fb137:/work# ipfs cat QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u
Hello World
```

ここで1点重要なことは、今回test_dir内に新しくフォルダやファイルを追加したため、test_dirのコンテンツアドレスが変わっていることです。
・前回: Qmco3Z21nYrNoV1Tijd8WNSYEpuRWbrJkhTkePu3fU1Zon
・今回: QmQtzNn4Mh52Ha7hRefSaUeRP5g6CMhdWJBUUBXM5SH6Sz
IPFSはコンテンツ自体を登録・参照するので、内容が変わるとアドレスも変わってしますのですね。
ただ内容が変更するたびにアドレスが変わると不便なことも出てきます。
そのためIPFSにも名前解決の仕組みがあるようですが、こちらは次回以降試してみます。

ところで前回登録したtest_dirのアドレスはどうなっているのでしょうか。
アクセスして試してみます。

```bash
root@8995ca7fb137:/work# ipfs cat Qmco3Z21nYrNoV1Tijd8WNSYEpuRWbrJkhTkePu3fU1Zon/hello.txt
Hello world
root@8995ca7fb137:/work# ipfs cat Qmco3Z21nYrNoV1Tijd8WNSYEpuRWbrJkhTkePu3fU1Zon/hello2.txt
Hello IPFS
```
普通にアクセス出来ますね。
コンテンツの内容を更新した際はコンテンツの内容のハッシュ値をもとに新しいアドレスが生成されるけど古いアドレスも残り続けるようですね。


###ネットワークに接続してみる
これまではDockerコンテナ内のローカル環境でIPFS環境を作成しgo-ipfsの動作を試してきました。
ここから実際にIPFSネットワークに接続し、色々試してみようと思います。
(コンテナのポート4001番と5001番がホスト環境の同ポートとマッピングされていることを確認してください)

ローカル環境とIPFSネットワーク環境を同期させるにはipfs daemonコマンドを使用します。

```bash
root@8995ca7fb137:/work# ipfs daemon
Initializing daemon...
go-ipfs version: 0.10.0
Repo version: 11
System version: amd64/linux
Golang version: go1.16.8
2021/10/06 13:13:05 failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 2048 kiB, got: 416 kiB). See https://github.com/lucas-clemente/quic-go/wiki/UDP-Receive-Buffer-Size for details.
Swarm listening on /ip4/127.0.0.1/tcp/4001
Swarm listening on /ip4/127.0.0.1/udp/4001/quic
Swarm listening on /ip4/172.17.0.2/tcp/4001
Swarm listening on /ip4/172.17.0.2/udp/4001/quic
Swarm listening on /p2p-circuit
Swarm announcing /ip4/127.0.0.1/tcp/4001
Swarm announcing /ip4/127.0.0.1/udp/4001/quic
Swarm announcing /ip4/172.17.0.2/tcp/4001
Swarm announcing /ip4/172.17.0.2/udp/4001/quic
API server listening on /ip4/127.0.0.1/tcp/5001
WebUI: http://127.0.0.1:5001/webui
Gateway (readonly) server listening on /ip4/127.0.0.1/tcp/8080
Daemon is ready

```
daemonが起動し、IPFS環境と同期されます。
自分のリポジトリに格納されているコンテンツも同期され他のノードから参照されているので注意してください。

daemonが起動したら、commnd + n等で新しいターミナルを立ち上げ、docker execコマンドで同じコンテナ環境に入ります。
新しいプロセスが起動したらIPFSネットワーク環境で色々試してみます。

まずはipfs swarm peersコマンドで自分のノードと接続されているノード一覧を表示してみます。

```bash
root@8995ca7fb137:/work# ipfs swarm peers
/ip4/1.64.188.205/tcp/41137/p2p/QmUaH6LHcFHJnTgmHfFzrSR1TXtpztihdctxchiUgGdFem
/ip4/102.130.120.119/tcp/4001/p2p/12D3KooWQJtYg86hhpZxNJjpUaQBDASRcN3Mx9i4GX7oGRozvkKj
/ip4/109.191.66.49/tcp/36587/p2p/QmYP3KFLGSSQA256BpXhsr8Gckc9dyNtDuUDYbcMLFq6hh
/ip4/111.197.248.156/udp/47090/quic/p2p/12D3KooWDanYSsmCdMoPnCAYByJ7t1uh3MQj15rdvQNxPCKKjtct
/ip4/111.22.164.176/tcp/34151/p2p/Qme7c5qSEMxi9avnHyu6fTBx2JF5HEBuiVT21HMdqVdk19
~
長いので省略
~
/ip4/86.15.193.194/udp/13491/quic/p2p/12D3KooWLAFdEzd5r3j5w4bq23vLVWWNh8nj4J1fowWbxPM4S7cY
/ip4/89.233.107.203/udp/4001/quic/p2p/QmUxWTcwSTzoQ1rKR7o5vuNLsQa2nWP63b2Jk3syvrubD6
/ip4/93.190.141.28/tcp/4001/p2p/Qmef8fTzAgaVThZe9K3biLwQzeAP7xZF1a8dTA3kpKJ844
/ip4/96.234.121.209/udp/10887/quic/p2p/12D3KooWRrbwLc8cqhRJuUPps4ZQCjZCfcPYKrZdjby2NF3DxToA
/ip4/98.161.184.23/udp/4001/quic/p2p/QmSWem9XzBR6NLV1eKYdXqSSYtdyGztWriBs2r3xKLoCvX
```
結構な数のノードと接続されています。

それでは自分の作成したコンテンツがどれくらい共有されているのか見てみます。
ipfs dht findprovsコマンドは引数で渡されたコンテンツを保持しているノードやゲートウェイを表示しますので、hello3.txt(QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u)を確認してみます。

```bash
root@8995ca7fb137:/work# ipfs dht findprovs QmWATWQ7fVPP2EFGu71UkfnqhYXDYH566qy47CnJDgvs8u                     
12D3KooWAoT3h4z6iEPFbpxkC5p8kVqXgZRp3SKtmHsj6ZATBPHV
12D3KooWBqXURGrk6gtc2Vsdi2DnJDxgpWJopDTCRjV3RADtnfy2
12D3KooWD14e5KGSkdz3R9SWRCGNqd14ZKUwNYsC2J4ou273DRc1
12D3KooWJ4AowBTbeFZhH1CYLQtGeWXh8xxibyERBzgPwyruu5Wq
12D3KooWJMJmwWStP8ysxkaJw5EDarkUxd7DYMnXqcapbqXYL9p5
12D3KooWJfuLbyXr8ifmZfPyBnTML7VxYf6rCbFBz2pB9FCjmZ2t
12D3KooWKrB93pwXDdeyz2WRMwcSBny5ECjA1JasB4GTo4ijUUtf
12D3KooWL2jHiB5ikvE2NUYxHKvP27Wdzt1aDUxsemhGeMYMTsgo
12D3KooWNyeZ6jSaNdHU2BRPFDADg2LWyuxHrASpvRq1T5hGqga2
QmNSYxZAiJHeLdkBg38roksAR9So7Y5eojks1yjEcUtZ7i
QmPgug7VVM8gYz66qhm5HP9CYLH4fMcD97VqfUwKnjaY7s
QmRXP6S7qwSH4vjSrZeJUGT68ww8rQVhoFWU5Kp7UkVkPN
QmSaXJRZVyKJNE1tN6hjFtKWSkmfzWWCEzbbnWmu6LJM7h
QmUEMvxS2e7iDrereVYc5SWPauXPyNwxcy9BXZrC1QTcHE
QmYBEXLhD7CDEBx4uW1QBTZRgUS3xnntgCigv9ahjhehXZ
QmbVWZQhCGrS7DhgLqWbgvdmKN7JueKCREVanfnVpgyq8x
12D3KooWCJAyPa3Ha3CGTgASibgW4FkhuA34yPUUKhfKc1xyFvFK
QmUGCr3msQ5pVFKcxRvK53AChUyUYQHKDqjDKPKcS2YBNK
```
Hello Worldというコンテンツを保持しているノードはこれだけいるんですね。

ではhello2.txtで確認してみます。

```bash
root@8995ca7fb137:/work# ipfs dht findprovs QmUG8xLzecnsb77dZYU24QCeTfvD52kdNf5ugSTXm2nZVw
root@8995ca7fb137:/work# 
```
何も表示されません。
Hello Ipfsというコンテンツを保持しているノードはいないということだと思います。
(参考サイトでは自身のノードは表示されているようです。なぜ僕の環境では表示されていないのかは調査中です。。)

では僕のノードが保持しているhello2.txtコンテンツを外部から参照できるか試してみます。
自分のブラウザからIPFSネットワークスにアクセスしコンテンツを表示させます。
この時使用するのがゲートウェイです。
IPFSは独自のプロトコルで動いているのでブラウザからHTTPではアクセス出来ません。
ゲートウェイはHTTPプロトコルとIPFSプロトコルをつなげる役目を果たします。(まだきちんと理解出来ていないため、このくらいの説明しか出来ません。もし間違えていたら訂正してください)

公式が用意しているゲートウェイは、
https://ipfs.io/ipfs/
です。
このipfs/の後に検索したいコンテンツのアドレスを入力します。

それではブラウザを開き、
https://ipfs.io/ipfs/QmUG8xLzecnsb77dZYU24QCeTfvD52kdNf5ugSTXm2nZVw
にアクセスしてみます。

![0CB884D9-ECF1-4677-A03E-92643061F680_1_201_a.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/505203/2e18d5b4-4533-6749-ed82-3a36b8887d3b.jpeg)

Hello Ipfsが表示されました。

この状態で先程のipfs dht findprovsコマンドを実行してみます。

```bash
root@8995ca7fb137:/work# ipfs dht findprovs QmUG8xLzecnsb77dZYU24QCeTfvD52kdNf5ugSTXm2nZVw
12D3KooWAoT3h4z6iEPFbpxkC5p8kVqXgZRp3SKtmHsj6ZATBPHV
```
今度は表示されました。
これは、ゲートウェイには共有したコンテンツを一定時間保持する機能があるのでhello2.txtを保持しているゲートウェイが表示されていると思われます。(推測です。間違えていたら訂正してください)
いちおう僕のリポジトリに登録されているコンテンツもIPFSネットワーク内で共有できているようです。


#おわりに
今回はipfsを使ってディレクトリの登録・参照やIPFSネットワークへの接続を試してみました。
ただゲートウェイを使用したコンテンツへのアクセスや名前解決の仕組み、クライアント端末からipfsへコンテンツを登録・参照する方法等、まだ理解出来ていない部分も多いので、次回以降このあたりを試してみようと思います。
ご覧いただきありがとうございました。

#参考サイト、文献

* [IPFS入門](https://ipfs-book.decentralized-web.jp/)
IPFSについて詳しく紹介されています。
すごく参考にしているサイトです。

* 「マスタリング イーサリアム」オライリー 
   Andreas M.Antonopoulos, Gavin Wood 著
EthereumやDAppについて詳しい書籍です。