构建一个以太坊+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 web3@^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