1. 程式人生 > >構建一個簡單的以太坊+IPFS+React.js去中心化應用DApp

構建一個簡單的以太坊+IPFS+React.js去中心化應用DApp

我們為什麼要構建這個?在以太坊區塊鏈上儲存大量資料是非常昂貴的。根據以太坊的黃皮書,它是大約20,0000gas,256bit/8位元組(1字)。基於02/28/2018 gas價格為4gwei/gas。請參閱:https://ethgasstation.info瞭解當前價格。

每個交易8個位元組20,000gas*4gwei/gas=80,000gwei

8,000位元組80,000gwei*1000bytes/8=10,000,000gwei/kB=0.01`以太。

0.01以太/kB*1000kB=10以太儲存1Mb,價格為860美元/以太=8600.00美元!在以太坊區塊鏈上儲存1GB檔案需要花費8,600,000.00美元!

儲存以太坊的38頁PDF黃皮書(520Kb)=4472美元。請參閱:

http://eth-converter.com/進行轉換計算。

如果我們只能在區塊鏈上儲存幾Kb的資料,那麼我們仍然需要依靠集中式伺服器來儲存資料。值得慶幸的是,可以使用稱為InterPlanetary files system 星際檔案系統IPFS的去中心化網路上儲存資料的解決方案。請參閱:https://ipfs.io/瞭解更多資訊。在IPFS中查詢檔案時,你要求網路查詢將內容儲存在唯一雜湊後面的節點。來自IPFS自己的網站:

“IPFS和Blockchain完美匹配!你可以使用IPFS處理大量資料,並將不可變的永久IPFS連結放入區塊鏈交易中。這個時間戳和保護你的內容,而不必將資料放在鏈本身上。“

我們構建什麼?

一個簡單的DApp,用於將文件上載到IPFS,然後將IPFS雜湊儲存在以太坊區塊鏈上。一旦IPFS雜湊號被髮送到以太坊區塊鏈,使用者將收到交易收據。我們將使用Create-React-App框架來構建前端。此Dapp適用於在瀏覽器中安裝了MetaMask的任何使用者。

這就是我們完成後DApp的樣子:

如何建立它:

注意:如果你只是想要程式碼,請參閱我的github

在我們開始之前,這些是我做出的假設:

  • 關於使用者的假設:使用者安裝了Metamask以與DApp互動。
  • 關於你/開發人員的假設:你對JavaScript、React.js以及Node.js/NPM有一定的瞭解。重要說明:請確保你運行當前版本的Node和NPM。對於本教程,我正在執行:node v8.9.4和NPM 5.6.0。
  • 安裝MetaMask。如果尚未安裝MetaMask,請訪問https://metamask.io/並按照說明操作。此DApp將假定使用者已安裝MetaMask。
  • 建立一個目錄來儲存我們的DApp。對於本教程,我將其稱為eth-ipfs
  • 使用NPM安裝Create-React-App和其他依賴項。使用NPM並安裝以下內容:
npm i create-react-app
npm install react-bootstrap
npm install fs-extra
npm install ipfs-api
npm install [email protected]^1.0.0-beta.26

輸入eth-ipfs目錄,鍵入npm startCreate-React-App應自動在http://localhost:3000/上呈現。

注意:如果你到目前為止尚未使用create-react-app,則可能必須先在全域性安裝它

    1. sudo npm install -g create-react-app或者npm install -g create-react-app
    1. create-react-app eth-ipfs
    1. cd進入eth-ipfs然後執行npm start

在Rinkeby testnet上使用Remix部署以下Solidity程式碼。請參閱https://remix.ethereum.org。你需要一些Rinkeby測試以太,如果你還沒有Rinkeby faucet的一些免費測試以太話。https://www.rinkeby.io/#faucet

pragma solidity ^0.4.17;
contract Contract {
 string ipfsHash;
 
 function sendHash(string x) public {
   ipfsHash = x;
 }

 function getHash() public view returns (string x) {
   return ipfsHash;
 }
}

儲存部署它的合約地址和應用程式二進位制介面(ABI)。要獲得合約的ABI,請在Remix中轉到你的合約地址:

單擊“Compile”選項卡,然後單擊灰色的“Details”按鈕。

這將開啟“Details”視窗。複製“ABI”,它是一個JSON檔案。

我個人更喜歡將ABI JSON放入格式化程式,例如https://jsonformatter.org,並在我的javascript程式碼中使用之前檢查它是否有效。儲存合約地址和ABI以供日後使用。

在我們的“eth-ipfs/src”目錄中,建立以下檔案: web3.jsipfs.jsstorehash.js。我們的大部分程式碼都在App.js中。

web3.js

我們想使用1.0版本的web3.js,因為與0.20版本不同,1.0允許我們在我們的javascript中使用async並等待而不是promises。目前,MetaMask的預設web3.js提供程式是0.20版本。所以,讓我們確保我們覆蓋Metamask的web3版本0.20的預設版本,並使用我們的1.0。這是程式碼:

//為我們的1.0版本覆蓋metamask v0.2。 
//1.0讓我們使用async和await而不是promises 
import Web3 from ‘web3’;

const web3 = new Web3(window.web3.currentProvider);
export default web3;

storehash.js

為了讓web3.js能夠訪問我們之前部署到以太坊的Rinkeby testnet的合約,你需要以下內容:1)合約地址和2)合約中的ABI。一定要從/src目錄中匯入web3.js檔案。這是程式碼:

import web3 from './web3';
//access our local copy to contract deployed on rinkeby testnet
//use your own contract address
const address = '0xb84b12e953f5bcf01b05f926728e855f2d4a67a9';
//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);

ipfs.js

在本教程中,我們將執行ipfs.infura.io節點以連線到IPFS,而不是在我們自己的計算機上執行IPFS守護程式。在程式碼註釋中,如果將IPFS安裝為全域性依賴項,則還可以選擇執行自己的IPFS守護程式。有關使用其節點的更多資訊,請參閱https://infura.io/。這是程式碼:

//using the infura.io node, otherwise ipfs requires you to run a //daemon on your own computer/server.
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;

App.js

這是App.js中的操作順序:

  • 1.設定狀態變數。
  • 2.捕獲使用者的檔案。
  • 3.將檔案轉換為緩衝區。
  • 4.將緩衝的檔案傳送到IPFS。
  • 5.IPFS返回一個雜湊值。
  • 6.獲取使用者的MetaMask以太坊地址
  • 7.傳送IPFS以便在以太坊上儲存。
  • 8.使用MetaMask,使用者將確認交易到以太坊。
  • 9.以太坊合約將返回一個交易雜湊數。
  • 10.交易雜湊值可用於生成具有諸如所使用的gas量和塊編號之類的資訊的交易收據。
  • 11.IPFS和以太坊資訊將在使用Bootstrap for CSS的表中呈現。注意:我沒有建立一個isLoading型別變數來自動重新呈現blockNumber和gasUsed變數的狀態。因此,現在,你必須再次單擊或實現自己的載入圖示。 描述變數和函式的表,後面是程式碼本身如下:

最後,這是App.js程式碼:

import React, { Component } from ‘react’;
//import logo from ‘./logo.svg’;
import ‘./App.css’;
import web3 from ‘./web3’;
import ipfs from ‘./ipfs’;
import storehash from ‘./storehash’;
class App extends Component {
 
    state = {
      ipfsHash:null,
      buffer:'',
      ethAddress:'',
      blockNumber:'',
      transactionHash:'',
      gasUsed:'',
      txReceipt: ''   
    };
captureFile =(event) => {
        event.stopPropagation()
        event.preventDefault()
        const file = event.target.files[0]
        let reader = new window.FileReader()
        reader.readAsArrayBuffer(file)
        reader.onloadend = () => this.convertToBuffer(reader)    
      };
 convertToBuffer = async(reader) => {
      //file is converted to a buffer for upload to IPFS
        const buffer = await Buffer.from(reader.result);
      //set this buffer -using es6 syntax
        this.setState({buffer});
    };
onClick = async () => {
try{
        this.setState({blockNumber:"waiting.."});
        this.setState({gasUsed:"waiting..."});
//get Transaction Receipt in console on click
//See: https://web3js.readthedocs.io/en/1.0/web3-eth.html#gettransactionreceipt
await web3.eth.getTransactionReceipt(this.state.transactionHash, (err, txReceipt)=>{
          console.log(err,txReceipt);
          this.setState({txReceipt});
        }); //await for getTransactionReceipt
await this.setState({blockNumber: this.state.txReceipt.blockNumber});
        await this.setState({gasUsed: this.state.txReceipt.gasUsed});    
      } //try
    catch(error){
        console.log(error);
      } //catch
  } //onClick
onSubmit = async (event) => {
      event.preventDefault();
     //bring in user's metamask account address
      const accounts = await web3.eth.getAccounts();
     
      console.log('Sending from Metamask account: ' + accounts[0]);
    //obtain contract address from storehash.js
      const ethAddress= await storehash.options.address;
      this.setState({ethAddress});
    //save document to IPFS,return its hash#, and set hash# to state
    //https://github.com/ipfs/interface-ipfs-core/blob/master/SPEC/FILES.md#add 
      await ipfs.add(this.state.buffer, (err, ipfsHash) => {
        console.log(err,ipfsHash);
        //setState by setting ipfsHash to ipfsHash[0].hash 
        this.setState({ ipfsHash:ipfsHash[0].hash });
   // call Ethereum contract method "sendHash" and .send IPFS hash to etheruem contract 
  //return the transaction hash from the ethereum contract
 //see, this https://web3js.readthedocs.io/en/1.0/web3-eth-contract.html#methods-mymethod-send
        
        storehash.methods.sendHash(this.state.ipfsHash).send({
          from: accounts[0] 
        }, (error, transactionHash) => {
          console.log(transactionHash);
          this.setState({transactionHash});
        }); //storehash 
      }) //await ipfs.add 
    }; //onSubmit
render() {
      
      return (
        <div className="App">
          <header className="App-header">
            <h1> Ethereum and IPFS with Create React App</h1>
          </header>
          
          <hr />
<Grid>
          <h3> Choose file to send to IPFS </h3>
          <Form onSubmit={this.onSubmit}>
            <input 
              type = "file"
              onChange = {this.captureFile}
            />
             <Button 
             bsStyle="primary" 
             type="submit"> 
             Send it 
             </Button>
          </Form>
<hr/>
 <Button onClick = {this.onClick}> Get Transaction Receipt </Button>
  <Table bordered responsive>
                <thead>
                  <tr>
                    <th>Tx Receipt Category</th>
                    <th>Values</th>
                  </tr>
                </thead>
               
                <tbody>
                  <tr>
                    <td>IPFS Hash # stored on Eth Contract</td>
                    <td>{this.state.ipfsHash}</td>
                  </tr>
                  <tr>
                    <td>Ethereum Contract Address</td>
                    <td>{this.state.ethAddress}</td>
                  </tr>
                  <tr>
                    <td>Tx Hash # </td>
                    <td>{this.state.transactionHash}</td>
                  </tr>
                  <tr>
                    <td>Block Number # </td>
                    <td>{this.state.blockNumber}</td>
                  </tr>
                  <tr>
                    <td>Gas Used</td>
                    <td>{this.state.gasUsed}</td>
                  </tr>
                
                </tbody>
            </Table>
        </Grid>
     </div>
      );
    } //render
} //App
export default App;

我在src/App.css中添加了一些CSS,使它看起來更容易一些:

/*some css I added*/
input[type=”file”] {
 display: inline-block;
}
.table {
 max-width: 90%;
 margin: 10px;
}
.table th {
 text-align: center;
}
/*end of my css*/

並向src/index.js新增一些匯入:

/*https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-a-stylesheet*/
import ‘bootstrap/dist/css/bootstrap.css’;
import ‘bootstrap/dist/css/bootstrap-theme.css’;

這就對了!你的DApp應該完成。所以你需要做的就是選擇一個檔案,傳送它,並獲得一個交易收據。如果你通過localhost:3000連線到IPFS節點,那麼你應該能夠在其中一個IPFS閘道器上看到你的檔案。https://gateway.ipfs.io/ipfs/+你的IPFS雜湊。

例如: https://gateway.ipfs.io/ipfs/QmYjh5NsDc6LwU3394NbB42WpQbGVsueVSBmod5WACvpte

關於IPFS的一個注意事項是,除非你的檔案被另一個節點接收或者你將其固定,否則IPFS最終將垃圾收集你的檔案。他們的網站上有很多關於此的內容。

======================================================================

分享一些以太坊、EOS、比特幣等區塊鏈相關的互動式線上程式設計實戰教程:

  • java以太坊開發教程,主要是針對java和android程式設計師進行區塊鏈以太坊開發的web3j詳解。
  • python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
  • php以太坊,主要是介紹使用php進行智慧合約開發互動,進行賬號建立、交易、轉賬、代幣開發以及過濾器和交易等內容。
  • 以太坊入門教程,主要介紹智慧合約與dapp應用開發,適合入門。
  • 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
  • C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括賬戶管理、狀態與交易、智慧合約開發與互動、過濾器和交易等。
  • EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、賬戶與錢包、發行代幣、智慧合約開發與部署、使用程式碼與智慧合約互動等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
  • java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Java程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
  • php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈儲存、去中心化共識機制、金鑰與指令碼、交易與UTXO等,同時也詳細講解如何在Php程式碼中整合比特幣支援功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
  • tendermint區塊鏈開發詳解,本課程適合希望使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI介面、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操程式碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。

匯智網原創翻譯,轉載請標明出處。這裡是原文構建一個簡單的以太坊+IPFS+React.js DApp