IPFS Desktop で立ち上げたノードに対して JS でアップロード
IPFS とは
IPFS とは P2P ネットワーク上でハイパーメディアプロトコルで、コンテンツのハッシュがアドレスとして使われるのでアップロード内容が変化したら参照するアドレスにも変更が必要といったものになります。ブロックチェーンのストレージとしての相性が良いようでブロックチェーンの台帳側には IPFS のアドレス(CID)を書き込むことで、その時点のコンテンツの履歴を残せるようになっています。
また、IPFS で使われる p2p のプロトコルはlibp2p
になっていまして IPFS のために作られたもののようです。
今回は IPFS 学習のため IPFS Desktop で立ち上げたノードに対して JS でアップロードしてみました。 IPFS Desktop は以下で取得できます。
IPFS Desktop で実際に立ち上がるノードは go-ipfs
のもので IPFS Desktop はその UI となっています。
GitHub - ipfs/go-ipfs: IPFS implementation in Go
また、自前でノードを立ち上げてネットワーク上でコンテンツを共有ではなくブラウザからのアップロードを試したかったので js-ipfs でipfs-http-client
を利用しています。
GitHub - ipfs/js-ipfs: IPFS implementation in JavaScript
実際に確認したソースは examples にあるhttp-client-bundle-webpack
になります。
js-ipfs/examples/http-client-bundle-webpack at master · ipfs/js-ipfs · GitHub
IPFS Desktop をシングルノードで立ち上げる
動作確認にあたり、今回は他のノードに共有する必要はないので IPFS Desktop 経由でインストールされたgo-ipfs
の設定を行います。
具体的には~/.ipfs/config
ファイルのBootstrap
の設定を以下のように空にします。
"Bootstrap": [ "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt", "/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ", "/ip4/104.131.131.82/udp/4001/quic/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ" ],
↓
"Bootstrap": [],
これで IPFS Desktop の peers を見たら 0 で表示され、他のノードにつながっていないことが確認できるかと思います。
IPFS の基本的な動作確認
基本的な確認としてファイルのアップロードをしてみます。アップロードはipfs add
でアップロードした内容はipfs cat
で確認できます。以下は Windows のコマンドプロンプトで確認しています。
C:\Users\arite\OneDrive\Desktop>type test1.txt "hello world" C:\Users\arite\OneDrive\Desktop>ipfs add test1.txt 16 B / 16 B [================================================================================================] 100.00%added QmXeKtRSz7SVKp8Qh6tXtv6whRF5WXWQPwsqA38houakhZ test1.txt 16 B / 16 B [================================================================================================] 100.00% C:\Users\arite\OneDrive\Desktop> C:\Users\arite\OneDrive\Desktop>ipfs cat QmXeKtRSz7SVKp8Qh6tXtv6whRF5WXWQPwsqA38houakhZ "hello world"
ipfs add
実行時のQmXeKtRSz7SVKp8Qh6tXtv6whRF5WXWQPwsqA38houakhZ
は CID というものでアップロードした内容により一意に決まるハッシュになっています。そのため、同じ内容でファイル名が異なるものをアップロードした倍も CID は同じになります。
C:\Users\arite\OneDrive\Desktop>copy test1.txt test2.txt 1 個のファイルをコピーしました。 C:\Users\arite\OneDrive\Desktop>ipfs add test2.txt 16 B / 16 B [================================================================================================] 100.00%added QmXeKtRSz7SVKp8Qh6tXtv6whRF5WXWQPwsqA38houakhZ test2.txt 16 B / 16 B [================================================================================================] 100.00% C:\Users\arite\OneDrive\Desktop>
また、ipfs cat
では CID を指定して内容が確認できています。
http アクセスの許可
デフォルトのまま立ち上がった IPFS のノードに対してipfs-http-client
でリクエストを投げると CORS などではじかれるので以下のコマンドを実行し設定変更します。設定変更後はノードを再起動します。
ipfs config --json API.HTTPHeaders.Access-Control-Allow-Origin "[\"*\"]" ipfs config --json API.HTTPHeaders.Access-Control-Allow-Credentials "[\"true\"]"
ipfs-http-client での文字列アップロード
今回はjs-ipfs
の examples に含まれるhttp-client-bundle-webpack
の確認を行いまして、対象のソースは以下になります。
'use strict' const React = require('react') const { create: ipfsClient } = require('ipfs-http-client') const stringToUse = 'hello world from webpacked IPFS3' class App extends React.Component { constructor(props) { super(props) this.state = { addr: null, id: null, version: null, protocol_version: null, added_file_hash: null, added_file_contents: null } this.connect = this.connect.bind(this) this.multiaddr = React.createRef() } async connect() { const ipfs = ipfsClient(this.multiaddr.current.value) const id = await ipfs.id() this.setState({ id: id.id, version: id.agentVersion, protocol_version: id.protocolVersion }) const file = await ipfs.add(stringToUse) const hash = file.cid this.setState({ added_file_hash: hash.toString() }) const source = ipfs.cat(hash) let contents = '' const decoder = new TextDecoder('utf-8') for await (const chunk of source) { contents += decoder.decode(chunk, { stream: true }) } contents += decoder.decode() this.setState({ added_file_contents: contents }) } render() { if (this.state.id) { return ( <div style={{ textAlign: 'center' }}> <h1 id="info-header">Everything is working!</h1> <p>Your ID is <strong>{this.state.id}</strong></p> <p>Your IPFS version is <strong>{this.state.version}</strong></p> <p>Your IPFS protocol version is <strong>{this.state.protocol_version}</strong></p> <div> <div> Added a file! <br /> {this.state.added_file_hash} </div> <div> Contents of this file: <br /> {this.state.added_file_contents} </div> </div> </div> ) } return ( <div style={{ textAlign: 'center' }}> <h1 id="connect-header">Enter the multiaddr for an IPFS node HTTP API</h1> <form> <input id="connect-input" type="text" defaultValue="/ip4/127.0.0.1/tcp/5001" ref={this.multiaddr} /> <input id="connect-submit" type="button" value="Connect" onClick={this.connect} /> </form> </div> ) } } module.exports = App
ここでは以下で IPFS の http クライアントを立ち上げていまして、ipfsClient
に API アドレスを渡していまして今回は/ip4/127.0.0.1/tcp/5001
になります。これは~/.ipfs/config
のAddresses.API
で指定したものになります。
const ipfs = ipfsClient(this.multiaddr.current.value)
それから、以下でテキストをアップロードし帰ってきた CID を取得しています。
const file = await ipfs.add(stringToUse) const hash = file.cid this.setState({ added_file_hash: hash.toString() })
そして、CID をもとにコンテンツを取得しています。
const source = ipfs.cat(hash) let contents = '' const decoder = new TextDecoder('utf-8') for await (const chunk of source) { contents += decoder.decode(chunk, { stream: true }) } contents += decoder.decode() this.setState({ added_file_contents: contents })
実際に実行すると以下のようにアップロードおよび取得がうまくいっていることが確認できます。
このときのCIDはQmNiw4occBBHvkQVdtvAB391yv6wZmMnU7QhfcSYqqqAtW
になるのでipfs cat
を使って実際にアップロードされていることが確認できます。
C:\Users\arite\OneDrive\Desktop>ipfs cat QmNiw4occBBHvkQVdtvAB391yv6wZmMnU7QhfcSYqqqAtW hello world from webpacked IPFS
NFT での IPFS 利用について
最初の方でブロックチェーンに IPFS の CID を書き込むとしていましたが、Etherum ネットワークでのトークン発行に使われる OpenZeppelin ではERC721 コントラクトを使っていまして、以下では_safeMint
にアドレスとカウント値を渡してNFTトークンを発行し_setTokenURI
に対してカウント値とmetadataURI(IPFSゲートウェイURL + CID)を渡してCID情報をコントラクトに書き込んでいます。
docs.ipfs.io