目录
实现效果预览图
前提条件:安装包
目录结构
简单投票 Dapp 设计流程
solidity 合约
创建 Voting.sol 合约
编译合约
(1)导入 solc 和 fs
(2)读取合约内容
(3)编译合约代码
(4)取abi 、byteCode
部署合约
(1)创建Web3实例
(2)创建Contract对象
(3)部署合约
完整代码
调用合约方法
前端页面HTML
前端JS
方法一:获得合约地址和abi
方法二:获得 abi
编写 server.js
执行 server.js
(没走通版本)简单投票 Dapp 设计流程
!!!!!!!!!
这部分我遇到问题,没有走通,换版本重新写了一个,仅有借鉴意义
solidity 合约
创建 Voting.sol 合约
编译合约
(1)导入 solc 和 fs
(2)读取合约内容
(3)编译合约代码
(4)取abi 和 byteCode
部署合约
(1)创建Web3实例
(2)创建Contract对象
(3)部署合约
调用合约方法
实现效果预览图
前提条件:安装包
包:
“ganache-cli”: “^6.1.8”,
“solc”: “^0.4.25” // 之前用”solc”:”^0.7.3″,二者编译有好多不同,改了好多个问题,最后卡住没办法版本了
“web3”: “^1.7.0”系统:
“ubuntu-20.04.4-desktop-amd64”
下面是基于 Linux 的安装指南。这里要求我们预先安装 nodejs 和 npm,再用 npm 安装ganache、solc、web3,就可以继续项目的下一步了
simple_vote_dapp文件夹 中安装以下环境
mkdir ~/桌面/simple_vote_dappcd ~/桌面/simple_vote_dappnpm initsudo npm install ganache-cli@6.1.8 web3@1.7.0 solc@0.4.25
如果安装成功,运行如下命令,应该能够看到如下的输出
~/桌面/simple_vote_dapp/node_modules/.bin/ganache-cli
新建一个contracts用来存放 sol 合约文件
mkdir ~/桌面/simple_vote_dapp/contracts
目录结构
确保此时同时 ganache 已经在另一个窗口中运行
另起一个终端中,并在simple_vote_dapp目录下
运行 node 进入 node 控制台,初始化 web3 对象,并向区块链查询获取所有的账户。
node
var Web3 = require('web3')var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));var accounts;web3.eth.getAccounts().then((res)=>{accounts = res;console.log(accounts)});
简单投票 Dapp 设计流程
solidity 合约
我们设计一个叫做 Voting 的合约,这个合约有以下内容:
- 一个构造函数,用来初始化一些候选人
- 一个用来投票的方法(对投票数 + 1)
- 一个返回候选者所获得的总票数的方法
当你把合约部署到区块链的时候,就会调用构造函数,并只调用一次。与 web 世界里每次部署代码都会覆盖旧代码不同,在区块链上部署的合约是不可改变的,也就是说,如果你更新合约并再次部署,旧的合约仍然会在区块链上存在
创建 Voting.sol 合约
// SPDX-License-Identifier: GPL-3.0pragma solidity >=0.4.25 uint8) public votesReceived; constructor(bytes32[] memory candidateListName){ candidateList = candidateListName; } function validateCandidate(bytes32 candidateName) internal view returns(bool){ for (uint8 i = 0; i < candidateList.length; i++){ if(candidateName == candidateList[i]){ return true; } } return false; } function vote(bytes32 candidateName) public{ require(validateCandidate(candidateName)); votesReceived[candidateName] += 1; } function totalVotesFor(bytes32 candidateName) public view returns(uint8){ require(validateCandidate(candidateName)); return votesReceived[candidateName]; }}
为了编译合约,先从 Voting.sol 中加载代码并绑定到一个 string 类型的变量
编译合约
参考链接:Web3部署智能合约_zhongliwen1981的专栏-CSDN博客_web3部署智能合约一、web3介绍web3是一个专门与以太坊交互的node.js库。我们先回顾一下使用remix部署合约的步骤:第一步:编写合约。第二步:编译合约(之前我们设置了自动编译)。第三步:部署合约,部署成功后返回合约地址。第四步:调用合约。remix底层就是使用了web3实现了编译、部署、调用合约的功能。那么web3是如何实现这些功能呢?看完这篇文章就一清二楚了!!!二、web…https://blog.csdn.net/zhongliwen1981/article/details/89926975
在 node 控制台下
(1)导入 solc 和 fs
var solc = require('solc')var fs = require('fs')
(2)读取合约内容
var contractContent = fs.readFileSync('./contracts/Voting.sol', 'utf-8').toString()
(3)编译合约代码
var contractCompiled = solc.compile(contractContent);
(4)取abi 、byteCode
var abi = contractCompiled['contracts'][':Voting']['interface']var byteCode = contractCompiled['contracts'][':Voting']['bytecode']
部署合约
(1)创建Web3实例
var Web3 = require('web3')var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
(2)创建Contract对象
var contract = new web3.eth.Contract(JSON.parse(abi));
(3)部署合约
var accounts;web3.eth.getAccounts().then((res)=>{accounts = res;console.log(accounts)});// 合约拥有者账户var account = accounts[0];var gasLimit = 3000000;// 参数需要两个数组是因为 deploy 参数 arguments 本身接收的就是一个数组,用以接收多个数据,所以最外层是一个数组,里面的数组是我们 Voting合约构造函数定义的参数为 bytes32[]// 因为构造函数参数定义为 bytes32[],所以我们需要传入 bytes32,而不能直接传入字符串var argument = [[web3.utils.fromAscii('Alice'),web3.utils.fromAscii('Bob'),web3.utils.fromAscii('Cary')]]// 或// var argument = [["0x416c696365000000000000000000000000000000000000000000000000000000","0x426f620000000000000000000000000000000000000000000000000000000000","0x4361727900000000000000000000000000000000000000000000000000000000"]]contract.deploy({ data:byteCode,arguments:argument}).send({ from:account, gas:gasLimit,}).then(instance => {contractInstance = instance; console.log("contract address:", instance.options.address)})
记下合约地址contract address,后面修改 fontend-Voting.js 中的contract address 为你自己的
完整代码
var solc = require('solc')var fs = require('fs')var Web3 = require('web3')var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));var contractContent = fs.readFileSync('./contracts/Voting.sol', 'utf-8').toString()var contractCompiled = solc.compile(contractContent);var abi = contractCompiled['contracts'][':Voting']['interface']var byteCode = contractCompiled['contracts'][':Voting']['bytecode']var contract = new web3.eth.Contract(JSON.parse(abi));var accounts;web3.eth.getAccounts().then((res)=>{accounts = res;console.log(accounts)});var account = accounts[0];var gasLimit = 3000000;var argument = [[web3.utils.fromAscii('Alice'),web3.utils.fromAscii('Bob'),web3.utils.fromAscii('Cary')]]// 或// var argument = [["0x416c696365000000000000000000000000000000000000000000000000000000","0x426f620000000000000000000000000000000000000000000000000000000000","0x4361727900000000000000000000000000000000000000000000000000000000"]]contract.deploy({ data:byteCode,arguments:argument}).send({ from:account, gas:gasLimit,}).then(instance => {contractInstance = instance; console.log("contract address:", instance.options.address)})
调用合约方法
调用合约中的 vote 方法,投票给Alice
var voteTo = web3.utils.fromAscii('Alice')contractInstance.methods.vote(voteTo).send({from:accounts[0]}).then(console.log)
查看候选人的所得票数
var aVoter = web3.utils.fromAscii('Alice')contractInstance.methods.totalVotesFor(aVoter).call({from:accounts[0]}).then(res=>console.log(res))
前端页面HTML
Voting DApp Simple Voting DApp
Candidate Vote Count Alice Bob Cary
Vote
前端JS
方法一:获得合约地址和abi
solcjs --abi --bin Voting.sol
方法二:获得 abi
solc --abi --bin Voting.sol
let voteForCandidate;let initial = async() => { // 不需要先 var Web3 = require('web3'), 因为已经网络引入了 var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); var abi = JSON.parse('[{"constant":true,"inputs":[{"name":"candidateName","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"getLengthList","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"tlength","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"candidateName","type":"bytes32"}],"name":"vote","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"candidateListName","type":"bytes32[]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]') var contractAddress = '使用你自己的,上面步骤中有输出'; var contractInstance = await new web3.eth.Contract(abi, contractAddress); var accounts = await web3.eth.getAccounts(); console.log(accounts) // 对应关系 var candidates = { "Alice": "candidate-1", "Bob": "candidate-2", "Cary": "candidate-3" }; (async() => { var candidateList = Object.keys(candidates); // 拿出 candidates 中的 key 值 for (let i = 0; i { var candidateName = $("#candidate").val() try { var voteTo = await web3.utils.fromAscii(candidateName) contractInstance.methods.vote(voteTo).send({ from: accounts[0] }, async(err, res) => { if (err) { console.log("Error: ", err); } else { let id = candidates[candidateName]; var count = await contractInstance.methods.totalVotesFor(voteTo).call({ from: accounts[0] }); console.log(count) $("#" + id).html(count); } }) } catch (err) { console.log(err) } }}$(document).ready(function() { initial();});
编写 server.js
在 fontend 目录下创建 server.js
var http = require('http');var fs = require('fs');var url = require('url'); // 创建服务器let server = http.createServer( function (request, response) { // 解析请求,包括文件名 var pathname = url.parse(request.url).pathname; // 输出请求的文件名 console.log("Request for " + pathname + " received."); // 从文件系统中读取请求的文件内容 fs.readFile(pathname.substr(1), function (err, data) { if (err) { console.log(err); // HTTP 状态码: 404 : NOT FOUND // Content Type: text/html response.writeHead(404, {'Content-Type': 'text/html'}); }else{ // HTTP 状态码: 200 : OK // Content Type: text/html response.writeHead(200, {'Content-Type': 'text/html'}); // 响应文件内容 response.write(data.toString()); } // 发送响应数据 response.end(); }); }) server.listen(8888, '0.0.0.0', () => {console.log('Server running at http://0.0.0.0:8888/');})
执行 server.js
调用 server.js ,前提ganache-cli已经在后台启动,且 fontend-Voting.js 中的合约地址 contract address 是上面操作中新创建的
node server.js
之后就可以在浏览器中打开前端页面,测试使用了
http://127.0.0.1:8888/fontend-Voting.html
(没走通版本)简单投票 Dapp 设计流程
!!!!!!!!!
这部分我遇到问题,没有走通,换版本重新写了一个,仅有借鉴意义
包:
“ganache-cli”: “^6.1.8”,
“solc”: “^0.7.3” // 走不通,换^0.4.25了
“web3”: “^1.7.0”系统:
“ubuntu-20.04.4-desktop-amd64”
solidity 合约
我们设计一个叫做 Voting 的合约,这个合约有以下内容:
- 一个构造函数,用来初始化一些候选人
- 一个用来投票的方法(对投票数 + 1)
- 一个返回候选者所获得的总票数的方法
当你把合约部署到区块链的时候,就会调用构造函数,并只调用一次。与 web 世界里每次部署代码都会覆盖旧代码不同,在区块链上部署的合约是不可改变的,也就是说,如果你更新合约并再次部署,旧的合约仍然会在区块链上存在
创建 Voting.sol 合约
// SPDX-License-Identifier: GPL-3.0pragma solidity >=0.4.25 uint8) public votesReceived; constructor(bytes32[] memory candidateListName){ candidateList = candidateListName; } function validateCandidate(bytes32 candidateName) internal view returns(bool){ for (uint8 i = 0; i < candidateList.length; i++){ if(candidateName == candidateList[i]){ return true; } } return false; } function vote(bytes32 candidateName) public{ require(validateCandidate(candidateName)); votesReceived[candidateName] += 1; } function totalVotesFor(bytes32 candidateName) public view returns(uint8){ require(validateCandidate(candidateName)); return votesReceived[candidateName]; }}
为了编译合约,先从 Voting.sol 中加载代码并绑定到一个 string 类型的变量
编译合约
参考链接:Web3部署智能合约_zhongliwen1981的专栏-CSDN博客_web3部署智能合约一、web3介绍web3是一个专门与以太坊交互的node.js库。我们先回顾一下使用remix部署合约的步骤:第一步:编写合约。第二步:编译合约(之前我们设置了自动编译)。第三步:部署合约,部署成功后返回合约地址。第四步:调用合约。remix底层就是使用了web3实现了编译、部署、调用合约的功能。那么web3是如何实现这些功能呢?看完这篇文章就一清二楚了!!!二、web…https://blog.csdn.net/zhongliwen1981/article/details/89926975
在 node 控制台下
(1)导入 solc 和 fs
var solc = require('solc')var fs = require('fs')
(2)读取合约内容
var contractContent = fs.readFileSync('./contracts/Voting.sol', 'utf-8').toString()
(3)编译合约代码
合约内容转化为 JSON 格式
var input = { language: 'Solidity', sources: { contract: { content: contractContent } }, settings: { outputSelection: { '*': { '*': '*' } } }}var contractCompiled = JSON.parse(solc.compile(JSON.stringify(input)));
(4)取abi 和 byteCode
var contractCompliedContent = contractCompiled.contracts.contract.Voting;var abi = contractCompliedContent.abi;var byteCode = contractCompliedContent.evm.bytecode.object;
部署合约
(1)创建Web3实例
var Web3 = require('web3')var web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
(2)创建Contract对象
var contract = new web3.eth.Contract(abi);
(3)部署合约
var accounts;web3.eth.getAccounts().then((res)=>{accounts = res;console.log(accounts)});// 合约拥有者的帐号var account = accounts[0];var gasLimit = 3000000;// 参数需要两个数组是因为 deploy 参数 arguments 本身接收的就是一个数组,用以接收多个数据,所以最外层是一个数组,里面的数组是我们 Voting合约构造函数定义的参数为 bytes32[]// 因为构造函数参数定义为 bytes32[],所以我们需要传入 bytes32,而不能直接传入字符串var argument = [[web3.utils.fromAscii('Alice'),web3.utils.fromAscii('Bob'),web3.utils.fromAscii('Cary')]]// 或// var argument = [["0x7465737400000000000000000000000000000000000000000000000000000000","0x7465737400000000000000000000000000000000000000000000000000000000"]]var contractInstance;contract.deploy({ data:byteCode,arguments:argument}).send({ from:account, gas:gasLimit,}).then(instance => { contractInstance = instance; console.log("contract address:", instance.options.address)})
调用合约方法
之后我调用这 vote合约方法走不通了,尝试了好多方法都不行
一直报错误 VM Exception while processing transaction: invalid opcode,如果有好心人知道,望告知
换成0.4.25版本 编译之后就可以了,猜测是因为上面 input 那步骤,因为换了0.4.25版本之后没有用到 input 步骤
具体原因暂时不知
contractInstance.methods.vote("0x416c6963650000000000000000000000").send({from:accounts[0]}).then(console.log)
运行外部网络访问
在 fontend文件夹下,创建 server.js 文件
var http = require('http');var fs = require('fs');var url = require('url'); // 创建服务器let server = http.createServer( function (request, response) { // 解析请求,包括文件名 var pathname = url.parse(request.url).pathname; // 输出请求的文件名 console.log("Request for " + pathname + " received."); // 从文件系统中读取请求的文件内容 fs.readFile(pathname.substr(1), function (err, data) { if (err) { console.log(err); // HTTP 状态码: 404 : NOT FOUND // Content Type: text/html response.writeHead(404, {'Content-Type': 'text/html'}); }else{ // HTTP 状态码: 200 : OK // Content Type: text/html response.writeHead(200, {'Content-Type': 'text/html'}); // 响应文件内容 response.write(data.toString()); } // 发送响应数据 response.end(); }); }) server.listen(8890, '0.0.0.0', () => {console.log('Server running at http://0.0.0.0:8890/');})
将 server.js 放入云服务器上,执行以下命令
node server.js