目录

实现效果预览图

前提条件:安装包

目录结构

简单投票 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 安装ganachesolcweb3,就可以继续项目的下一步了

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