Appearance
L2-16: IPFS Node(IPFSノード構築)
1. 問題
ほとんどのNFTプロジェクトの「メタデータストレージ」は分散型を謳っていますが、実際にはInfura IPFSゲートウェイやPinataなどの中央集権的なピニングサービスに大きく依存しています。あなたのNFT画像の「永続的ストレージ」は実際には「ある会社があなたのためにデータをピンすることを約束している」に過ぎません — その会社がサービスを停止したり、価格を変更したり、ダウンした場合、あなたのNFTメタデータはアクセス不能になります。データがIPFSネットワークのどこかにまだ存在するとしても(他の誰かがピンしていれば)、到達性は保証されません。
自分自身のIPFSノードを構築することで、NFTデータストレージを完全に管理できます。コンテンツはあなたのノードによって提供され(サードパーティのゲートウェイではなく)、ピンポリシーはあなたが決定し(どのコンテンツを永続的に保持する必要があるか)、コミュニティ向けのパブリックIPFSゲートウェイサービスを提供することもできます。これは「他人のインフラに依存する」から「自分のインフラを貢献する」への転換です。
本チャレンジ(scripts/ipfs-node.md 参照)では、IPFSノードをゼロから構築する全プロセスを完了します — インストール、初期設定、コンテンツピン管理、ガベージコレクションポリシー、Solidity NFTコントラクトとの統合を含みます。
2. 理由
IPFS(InterPlanetary File System、惑星間ファイルシステム)は、ロケーションアドレッシング(Location Addressing)ではなくコンテンツアドレッシング(Content Addressing)を使用するピアツーピアの分散ファイルシステムです。HTTPでは、URL(https://example.com/image.png など)を通じてファイルにアクセスします — URLは「ファイルがどこにあるか」を教えます。IPFSでは、CID(Content Identifier、QmXxXxX... など)を通じてファイルにアクセスします — CIDは「ファイルが何であるか」を教えます。なぜならCIDはファイル内容のハッシュ値だからです。
コンテンツアドレッシングは2つの重要な特性を意味します:
- 不変性:同じファイル内容は常に同じCIDを生成します。ファイルの1バイトを変更すると完全に新しいCIDが生成されます。これによりNFTメタデータの完全性が保証されます — 受信したデータが本当に元のバージョンであることを検証できます。
- 分散性:誰でも特定のCIDを持つコンテンツを「提供」(serve)できます — あなたのノード、友人のノード、パブリックゲートウェイ — DHT(分散ハッシュテーブル)を通じてそのコンテンツを保持するノードを見つけられさえすれば可能です。
自分自身のIPFSノードを運用することは、「消費者」から「提供者」への転換です。あなたはIPFSネットワークのコンテンツを消費するだけでなく、あなたのノードもネットワークにストレージと帯域幅を貢献します。あなたのノードが特定のCIDのコンテンツを保持している場合、ネットワーク内の他のノードからのリクエストに応答します — あなたはIPFSインフラストラクチャの一部になります。
NFTの文脈では、IPFSノードのセルフホスティングは以下のことを意味します:あなたのNFTメタデータはPinataのサーバーがオンラインかどうかに依存せず、Infuraゲートウェイがレート制限しているかどうかに依存せず、いかなるサードパーティサービスにも依存しません — あなたのノードがオンラインである限り、あなたのNFTデータは到達可能です。
3. 解決策
IPFS Kuboのインストールと初期化
bash
# ====== 1. IPFS Kubo(旧 go-ipfs)のインストール ======
# Linux
wget https://dist.ipfs.tech/kubo/latest/kubo_linux-amd64.tar.gz
tar -xvzf kubo_linux-amd64.tar.gz
cd kubo
sudo bash install.sh
# macOS
brew install ipfs
# ====== 2. ノードの初期化 ======
ipfs init
# ~/.ipfs/ ディレクトリを生成、以下を含む:
# config — ノード設定ファイル
# datastore — データストレージ(デフォルト:flatfs)
# keystore — ノード鍵とIPNS鍵
# blocks — コンテンツブロックストレージ
ipfs init が生成する config ファイルには、ノードの完全な設定が含まれます:
Addresses:ノードがリッスンするネットワークアドレス(API、Gateway、Swarm)Bootstrap:初期接続先のブートストラップノードリストDatastore:ストレージ設定(StorageMax上限、GCポリシー)Identity:ノードのPeerIDと秘密鍵
ノードの設定
bash
# ====== 3. ストレージ上限の調整(デフォルトはわずか10 GB)======
ipfs config Datastore.StorageMax 100GB
# ====== 4. ガベージコレクションの設定(1時間ごとに未ピンコンテンツを自動クリーンアップ)======
ipfs config Datastore.GCPeriod 1h
# ====== 5. パブリックゲートウェイの有効化(オプション — 誰でもあなたのノードを通じてIPFSコンテンツにアクセス可能)======
ipfs config --json Addresses.Gateway '"/ip4/0.0.0.0/tcp/8080"'
# ====== 6. 完全な設定の表示 ======
ipfs config show
デーモンの起動
bash
# 方法1:フォアグラウンド実行
ipfs daemon
# 方法2:systemdサービス(本番環境推奨)
sudo tee /etc/systemd/system/ipfs.service << 'EOF'
[Unit]
Description=IPFS Daemon
After=network.target
[Service]
Type=simple
User=ubuntu
ExecStart=/usr/local/bin/ipfs daemon
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable ipfs
sudo systemctl start ipfs
sudo systemctl status ipfs
コンテンツの追加とピン
bash
# ====== 7. ファイルの追加 ======
echo "ETH Tech Tree NFT Metadata" > metadata.json
ipfs add metadata.json
# 出力: added QmXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx metadata.json
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# CID(コンテンツハッシュ)— これがファイルの永続的アドレス
# ====== 8. コンテンツのピン(GCによる削除を防止)======
ipfs pin add QmXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx
# ====== 9. ディレクトリ全体の追加(NFTコレクション)======
ipfs add -r my-nft-collection/
# 出力: added QmYyYyYy... my-nft-collection
# ディレクトリ自体にもCIDがある(ディレクトリ内の全ファイルのCIDから計算)
# ====== 10. ピン済みコンテンツの表示 ======
ipfs pin ls # 全ピンを一覧(ローカル)
ipfs pin ls --type=recursive # 再帰ピンのみ表示
# ====== 11. ローカルゲートウェイ経由でのアクセス ======
curl http://localhost:8080/ipfs/QmXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXx
# 戻り値: "ETH Tech Tree NFT Metadata"
Solidity NFTコントラクトとの統合
NFTコントラクトのデプロイ時に、tokenURI がIPFS URIを返します:
solidity
// コントラクト内で ipfs:// プロトコルプレフィックスを使用
string memory uri = string(abi.encodePacked("ipfs://", cid));
// またはHTTPゲートウェイをフォールバックとして使用(マーケットプレイス/ウォレット互換性が高い)
string memory gatewayUri = string(abi.encodePacked(
"https://ipfs.io/ipfs/", cid
));
// または自分のノードゲートウェイを使用(パブリック向けノードがある場合)
string memory selfHostedUri = string(abi.encodePacked(
"https://my-ipfs-node.example.com/ipfs/", cid
));
IPNS:不変コンテンツへの可変ポインタ
IPFSコンテンツは不変であるため(ファイル変更 = 新CID)、最新バージョンを「指し示す」方法が必要です。IPNS(InterPlanetary Name System)は可変の名前システムを提供します:
bash
# ====== 12. IPNS鍵ペアの作成(NFTプロジェクト用)======
ipfs key gen my-nft-project
# 出力: k51qzi5uqu5...
# ====== 13. IPNSレコードの公開 ======
ipfs name publish --key=my-nft-project QmNewVersionCID
# これで /ipns/k51qzi5uqu5... → QmNewVersionCID を指す
# ====== 14. IPNSポインタの更新(NFTメタデータ更新後)======
ipfs add updated-metadata.json # 新CIDを取得
ipfs name publish --key=my-nft-project QmUpdatedCID
# コントラクトでIPNSを使用:
# string memory uri = string(abi.encodePacked("ipns://k51qzi5uqu5..."));
IPNSの主要特性:各 publish 更新後、ipns://key 経由でアクセスするすべてのユーザーが自動的に最新の指し示すCIDを取得します — スマートコントラクト内のURIを更新する必要はありません。
NFTプロジェクト完全ワークフロー
1. NFTメタデータJSONを生成(名前、説明、属性)
2. NFT画像を生成(SVGまたはPNG)
3. ipfs add image.png → 画像CIDを取得
4. metadata.json内で "image": "ipfs://imageCID" を参照
5. ipfs add metadata.json → メタデータCIDを取得
6. スマートコントラクトをデプロイ、tokenURI() は "ipfs://metadataCID" を返す
7. IPFSノード上で全コンテンツをピン(画像 + メタデータ)
8. オプション:コレクションルートディレクトリを指すIPNS名を作成
9. オプション:Pinata/web3.storageで冗長ピン(二重保険)
10. オプション:Filecoinで分散型長期ストレージを利用
4. 遭遇した落とし穴
- 「永続的ストレージ」の神話:IPFSはFilecoinではありません — コンテンツの永続性を保証する組み込みの経済的インセンティブメカニズムはありません。あなたがピンしたコンテンツは、それをピンしているノード上にのみ存在します — あなたのノードがオフラインになり、他のノードが同じコンテンツをピンしていなければ、コンテンツはネットワークから消えます
- CID不変性の罠:NFTメタデータを(1バイトでも)変更すると完全に新しいCIDになります — しかし、すでにデプロイされたコントラクトの
tokenURIは古いCIDを返します。IPNSまたはアップグレード可能なコントラクトなしでは、メタデータを更新できません - ガベージコレクションによる誤削除:ピンされていないコンテンツはGCによって自動的にクリーンアップされます —
ipfs addしたがipfs pin addしなかった場合、1時間後にはノード上に存在しない可能性があります - パブリックネットワーク到達性の悪さ:新しいノードはDHTネットワークでピアを発見するのに時間がかかります — NAT背後(家庭用ルーター)のノードは、他のノードに発見されるまでに数日かかることがあります
- CIDバージョンの混乱:IPFSには2つのCID形式があります — CIDv0(
Qmで始まる、Base58)とCIDv1(bで始まる、Base32) — 異なるツールやライブラリが異なる形式をデフォルトで使用 - パブリックゲートウェイはサービスを保証しない:
https://ipfs.io/ipfs/CIDは中央集権的なゲートウェイです — ipfs.ioはレート制限、ダウンタイム、またはサービス条件の変更を行う可能性があります
5. 落とし穴の原因
「IPFSは永続的ストレージである」という誤解は、IPFSの基盤的説明 — 「分散ファイルシステム」— に起因します。実際には、IPFSが提供するのはコンテンツアドレッシングとピアツーピア転送のみです — 永続性には追加のインセンティブ層(Filecoin)または能動的ピニングが必要です。アナロジー:IPFSはBitTorrent(ピアツーピア転送)、Filecoinはシード維持サービス(永続化のための経済的インセンティブ)です。ピニングなしでは、あなたのコンテンツはシードのないtorrentのようなものです — 技術的には存在しますが、到達不能です。
CIDの不変性はコンテンツアドレッシングの必然的結果です:CID = hash(content)。コンテンツを変更するとハッシュが変わり、CIDも必然的に変わります。これはバグではなく機能です — コンテンツの完全性を保証します(CIDでリクエストしたコンテンツがまさに期待するコンテンツであり、改ざん不可能であること)。しかし、この「不変性」はメタデータの更新が必要な場合に不利になります — リダイレクトのために追加の可変層(IPNS、ENS、またはアップグレード可能なコントラクトの _baseURI)が必要です。
NAT背後ノードの接続性の悪さは、IPFSがピア発見に分散DHT(Kademlia DHT)に依存しているためです。DHTは双方向です — 他のノードがあなたのノードに直接接続できる必要があります。ルーターでポート転送(TCP 4001、デフォルト)が有効になっていない場合、発見したノードに接続することはできますが、他のノードはあなたに能動的に接続できません — これはあなたのノードがDHTルックアップ結果に表示されず、あなたが提供するCIDが他のノードから到達不能であることを意味します。
6. 落とし穴の解決方法
多層冗長ピニング戦略 — 自分のノードだけに依存しない:
bash
# 自分のノード(メインピン)
ipfs pin add QmCID
# Pinata(バックアップピニングサービス)
# https://pinata.cloud — 無料1 GB、有料プランでさらに多く
curl -X POST "https://api.pinata.cloud/pinning/pinByHash" \
-H "Authorization: Bearer $PINATA_JWT" \
-d '{"hashToPin": "QmCID"}'
# web3.storage(Filecoinバックアップ)
# https://web3.storage — 無料5 GB、Filecoinへの自動バックアップ
IPNSを使用した可変メタデータ:
bash
# プロジェクト初期化時にIPNS鍵を生成
ipfs key gen my-nft
# コントラクトデプロイ時に ipfs:// ではなく ipns:// を使用
# tokenURI = "ipns://k51qzi5uqu5..."
# EthereumライブラリはIPNSに特別な処理が必要、通常はHTTPゲートウェイプロキシを使用
# 実用的なアプローチ:アップグレード可能な _baseURI を使用
# NFTコントラクト内:
string public baseURI; // ownerが変更可能
function setBaseURI(string memory _newBaseURI) external onlyOwner {
baseURI = _newBaseURI; // 新しいIPFS CIDを指す
}
GCが重要なデータを削除しないようにする:コンテンツ追加後すぐにピン:
bash
# ワンステップで追加とピン(シェル関数を使用)
add-and-pin() {
local cid=$(ipfs add -Q "$1")
ipfs pin add "$cid"
echo "Added and pinned: $cid"
}
add-and-pin metadata.json
-Q パラメータは ipfs add がCIDのみを出力するようにし(ファイル名なし)、チェーン操作に便利です。
NAT問題の解決:
bash
# 1. ルーターでポート4001(TCP+UDP)をノードに転送
# 2. パブリックアドレスを使用するようIPFSを設定
ipfs config --json Addresses.Swarm '[
"/ip4/0.0.0.0/tcp/4001",
"/ip6/::/tcp/4001"
]'
# 3. 手動でパブリックIPを広告(UPnPが利用不可の場合)
ipfs config --json Routing.AcceleratedDHTClient true
# 4. 接続性を検証
ipfs swarm peers # 接続済みピア数を表示
ipfs id # 自分のPeerIDとアドレスを表示
CIDバージョン互換性について、一貫性を維持:NFTシナリオではCIDv0(Qm...)が推奨されます。最も幅広い互換性があるためです — ほとんどのウォレットやマーケットプレイスツールはCIDv0をデフォルトで使用します。CIDv1の機能(マルチハッシュアルゴリズム、マルチコーデック)が必要な場合は明示的に変換:
bash
# CIDv0をCIDv1に変換
ipfs cid base32 QmXxXxXx...
# 出力: bafybei...
7. 技術的要点
| 要点 | 説明 |
|---|---|
| コンテンツアドレッシング (CID) | CID = hash(content) — 同じコンテンツ = 同じアドレス |
| CIDv0 vs CIDv1 | CIDv0: Qm...(Base58、sha256)、CIDv1: b...(Base32、複数ハッシュ対応) |
| Pin vs GC | Pin = 手動で永続保持をマーク;未ピンコンテンツはGCサイクル(デフォルト1時間)で削除 |
| IPNS | 可変名前システム → 不変コンテンツを指す固定識別子(/ipns/key) |
| パブリックゲートウェイ | https://ipfs.io/ipfs/CID — 中央集権的ゲートウェイ、ローカルノード不要 |
| ローカルゲートウェイ | http://localhost:8080/ipfs/CID — 自分のノード、ゼロ依存 |
| 冗長ピニング | Pinata + web3.storage + 自分のノード = 三重保険 |
| DHT発見 | Kademlia DHTでピア発見とコンテンツルーティング |
| Filecoin長期ストレージ | IPFSのデータ永続性インセンティブ層(Filecoin取引はオンチェーンに記録) |
| Kubo (go-ipfs) | IPFSのGo参照実装、最も成熟し最も広く使用されている |