IPFS(InterPlanetary File System) Upload

2021. 6. 29. 17:17BlockChain

InterPlanetary File System(IPFS)는 모든 컴퓨터를 연결하고자 하는 분산된 P2P 파일 시스템입니다.
InterPlanetary라는 표현이 사용된 이유는 지구 상의 컴퓨터 뿐만 아니라 
다른 행성의 컴퓨터들까지 모두 연결하겠다는 IPFS팀의 비전이 담겨 있습니다.
IPFS Web는 기존의 HTTP Web의 문제점을 해결하고 보완한 새로운 Web입니다. 
IPFS가 어떻게 기술적으로 구현되었는지 알아보기 전에, IPFS가 어떤 특징을 갖고 있는지 알아보겠습니다.

IPFS의 특징

  • 중앙화된 서버 없이 노드들의 P2P 통신으로 실현한 더 빠르고 안전하고 열린 네트워크 입니다. 대형 서버의 연결이 차단되면 치명적인 결과를 낳는 과거 HTTP Web과는 달리, IPFS에서는 몇몇 노드들이 연결이 끊어지더라도 생태계가 안정적으로 유지됩니다.
  • 고용량의 파일을 빠르고 효율적이게 전달할 수 있으며(BitSwap), 파일들의 중복을 알 수 있기 때문에 저장소도 효율적으로 사용할 수 있습니다(Merkle DAG, contents-addressed)
  • IPFS 상에 업로드된 파일의 이름은 영원히 기록되며, 만약 IPFS 상에서 지키고 싶은 파일은 원하는 만큼 지켜낼 수 있습니다(pinning). 또한 파일의 버전 관리(Git)가 가능합니다.
  • 주류 인터넷에 원활하게 접속할 수 없는 상황이더라도 IPFS의 생태계는 유지됩니다.

How IPFS works?

  • 각각의 파일은 여러 개의 블록으로 이루어져 있으며, 각각의 블록은 해시로 표현된 고유의 이름이 있습니다.
  • IPFS는 모든 파일의 이름을 데이터베이스 속에 저장하며, 동일 파일의 중복을 배제하며, 각 파일의 버전 정보를 트래킹합니다.
  • 각 노드는 본인이 관심있는 파일만 저장소에 보관하며, 인덱싱 정보를 통해 누가 어떤 파일을 저장하고 있는지 알 수 있습니다.
  • 네트워크에서 파일을 찾기 위해서는, 파일명을 조회하고 해당 파일을 갖고 있는 노드를 물어보면 됩니다.
  • IPNS를 통해 모든 파일명은 인간이 읽기 쉬운 형태(DNS와 유사한 개념)로 변환할 수 있습니다.

React에서 IPFS 이미지 업로드 하기

 

폴더 구조

//App.js
import React, { useState } from 'react';
import { Container, Button, Form } from 'react-bootstrap';
import storehash from './storehash';
import web3 from './web3';
import ipfs from './ipfs';

const App = () => {

  const [input, setInput] = useState({
    'description': '',
    'external_url': '',
    'image': '',
    'name': '',
    'attribute': [],
  });

  const [info, setInfo] = useState({
    ipfsHash: null,
    buffer: '',
    ethAddress: '',
    blockNumber: '',
    transactionHash: '',
    gasUsed: '',
    txReceipt: ''
  })

  const captureFile = (event) => {
    event.stopPropagation();
    event.preventDefault();
    const file = event.target.files[0];
    let reader = new window.FileReader();
    reader.readAsArrayBuffer(file);
    reader.onloadend = () => convertToBuffer(reader);
  };

  const convertToBuffer = async (reader) => {
    const buffer = await Buffer.from(reader.result);
    setInfo({ buffer });
  };

  const onClick = async () => {
    let table = document.getElementById('TransactionTable');

    try {
      setInfo({ blockNumber: "waiting.." });
      setInfo({ gasUsed: "waiting..." });

      await web3.eth.getTransactionReceipt(info.transactionHash, (err, txReceipt) => {
        console.log(err, txReceipt);
        setInfo({ txReceipt });
        setInfo({ blockNumber: txReceipt.blockNumber });
        setInfo({ gasUsed: txReceipt.gasUsed });
        let row = `            
          <tr>
            <td>Tx Hash</td>
            <td>${txReceipt.transactionHash}</td>
          </tr>

          <tr>
            <td>Block Number</td>
            <td>${txReceipt.blockNumber}</td>
          </tr>

          <tr>
            <td>사용 된 가스</td>
            <td>${txReceipt.gasUsed}</td>
          </tr>`
        table.innerHTML += row;
      });
    }
    catch (error) {
      console.log(error);
    }
  }

  const onSubmit = async (event) => {
    let table = document.getElementById('TransactionTable');
    event.preventDefault();
    const accounts = await web3.eth.getAccounts();
    console.log('Sending from Metamask account: ' + accounts[0]);
    const ethAddress = await storehash.options.address;

    await ipfs.add(info.buffer, (err, ipfsHash) => {
      console.log(err, ipfsHash);
      setInfo({ ipfsHash: ipfsHash[0].hash });

      storehash.methods.sendHash(ipfsHash[0].hash).send({
        from: accounts[0]
      }, (error, transactionHash) => {
        setInfo({ transactionHash: transactionHash });
        let row = `
        <tr>
          <td>Eth 계약에 저장된 IPFS 해시 번호</td>
          <td>${ipfsHash[0].hash}</td>
        </tr>
        <tr>
          <td>Ethereum 계약 주소</td>
          <td>${ethAddress}</td>
        </tr>
        `
        table.innerHTML += row;
      });
    });
  };

  const onChangeValue = async () => {
    let desc = document.getElementById('description').value;
    let img = document.getElementById('image').value;
    let name = document.getElementById('name').value;

    setInput({
      'description': desc,
      'external_url': '',
      'image': img,
      'name': name,
      'attribute': [],
    });

    jsonSubmit();
  }

  const jsonSubmit = () => {
    ipfs.files.add(Buffer.from(JSON.stringify(input)))
      .then(res => {
        const hash = res[0].hash
        console.log('added data hash:', hash)
        return ipfs.files.cat(hash)
      })
      .then(output => {
        console.log('retrieved data:', JSON.parse(output))
      })
  }

  return (
    <div className="App">
      <p>------------------------------------------</p>
      <Container >
        <h3> Choose file to send to IPFS </h3>
        <Form onSubmit={onSubmit}>
          <input
            type="file"
            onChange={captureFile}
          />
          <Button
            bsStyle="primary"
            type="submit">
            보내기
          </Button>
        </Form>
        <p>------------------------------------------</p>
        <Button onClick={onClick}>거래 영수증 받기</Button>

        <table>
          <thead>
            <tr>
              <th>Tx Receipt Category</th>
              <th>Values</th>
            </tr>
          </thead>
          <tbody id="TransactionTable">
          </tbody>
        </table>
        <div>
          <p>------------------------------------------</p>
          <p>NFT 세부정보 담기</p>
          <input type="text" placeholder="description" id="description" /><br />
          <input type="text" placeholder="image url" id="image" /><br />
          <input type="text" placeholder="name" id="name" /><br />
          <button onClick={onChangeValue}>제출하기</button>
        </div>
      </Container >
    </div>
  );
}

export default App;
//ipfs.js
//using the infura.io node, otherwise ipfs requires you to run a daemon on your own computer/server. See IPFS.io docs
const IPFS = require('ipfs-api');
const ipfs = new IPFS({ host: 'ipfs.infura.io', port: 5001, protocol: 'https' });

//run with local daemon
// const ipfsApi = require('ipfs-api');
// const ipfs = new ipfsApi('localhost', '5001', {protocol: 'http'});

export default ipfs; 
//storehash.js
import web3 from './web3';

//access our local copy to contract deployed on rinkeby testnet
//use your own contract address
const address = '0xbE1875D6A8974E124A0e07c66D6f39c539a93c84';
//use the ABI from your contract
const abi = [
    {
      "constant": true,
      "inputs": [],
      "name": "getHash",
      "outputs": [
        {
          "name": "x",
          "type": "string"
        }
      ],
      "payable": false,
      "stateMutability": "view",
      "type": "function"
    },
    {
      "constant": false,
      "inputs": [
        {
          "name": "x",
          "type": "string"
        }
      ],
      "name": "sendHash",
      "outputs": [],
      "payable": false,
      "stateMutability": "nonpayable",
      "type": "function"
    }
]
export default new web3.eth.Contract(abi, address);
//web3.js
// //overrides metamask v0.2 for our 1.0 version.  
//1.0 lets us use async and await instead of promises

import Web3 from 'web3';
//overrides metamask v0.2 for our v 1.0
const web3 = new Web3(window.web3.currentProvider);

export default web3;

Front 예제 화면
트랜잭션 이후 결과 화면

트랜잭션이 끝나고 IPFS에 add한 JSON 형태의 파일을 추출해 결과값을 출력한 화면입니다.

'BlockChain' 카테고리의 다른 글

[BlockChain] Klaytn Code Verify  (0) 2022.04.27
[BlockChain] 메타마스크 서명과 검증  (0) 2021.09.08
ERC721 - NFT 만들기  (2) 2021.06.24
나만의 토큰 만들기  (0) 2021.06.03
이더리움 VM, 주소의 구성과 역할 이해  (0) 2021.06.03