構建一個簡單的以太坊+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美元。請參閱:
如果我們只能在區塊鏈上儲存幾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 start
,Create-React-App
應自動在http://localhost:3000/
上呈現。
注意:如果你到目前為止尚未使用create-react-app
,則可能必須先在全域性安裝它
-
sudo npm install -g create-react-app
或者npm install -g create-react-app
-
create-react-app eth-ipfs
-
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.js
,ipfs.js
和storehash.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