Dapp 投票 Voting 实现流程

目录

 实现效果预览图

前提条件:安装包

 目录结构

简单投票 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)部署合约 

调用合约方法


 实现效果预览图

Dapp 投票 Voting 实现流程_第1张图片

前提条件:安装包

    包:

    "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_dapp
cd ~/桌面/simple_vote_dapp

npm init
sudo npm install [email protected] [email protected] [email protected]

如果安装成功,运行如下命令,应该能够看到如下的输出

~/桌面/simple_vote_dapp/node_modules/.bin/ganache-cli

Dapp 投票 Voting 实现流程_第2张图片

 新建一个contracts用来存放 sol 合约文件

mkdir ~/桌面/simple_vote_dapp/contracts

 目录结构

Dapp 投票 Voting 实现流程_第3张图片

确保此时同时 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 投票 Voting 实现流程_第4张图片


简单投票 Dapp 设计流程 

solidity 合约

我们设计一个叫做 Voting 的合约,这个合约有以下内容:

  • 一个构造函数,用来初始化一些候选人
  • 一个用来投票的方法(对投票数 + 1)
  • 一个返回候选者所获得的总票数的方法

当你把合约部署到区块链的时候,就会调用构造函数,并只调用一次。与 web 世界里每次部署代码都会覆盖旧代码不同,在区块链上部署的合约是不可改变的,也就是说,如果你更新合约并再次部署,旧的合约仍然会在区块链上存在


创建 Voting.sol 合约

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.4.25 <0.9.0;

contract Voting{
    bytes32[] public candidateList;
    mapping(bytes32 => 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)

Dapp 投票 Voting 实现流程_第5张图片

 查看候选人的所得票数

var aVoter = web3.utils.fromAscii('Alice')
contractInstance.methods.totalVotesFor(aVoter).call({from:accounts[0]}).then(res=>console.log(res))

Dapp 投票 Voting 实现流程_第6张图片


前端页面HTML





    Voting DApp
    



    

Simple Voting DApp

Candidate Vote Count
Alice
Bob
Cary
Vote


前端JS

方法一:获得合约地址和abi 

solcjs --abi --bin Voting.sol

Dapp 投票 Voting 实现流程_第7张图片

 Dapp 投票 Voting 实现流程_第8张图片

 方法二:获得 abi

solc --abi --bin Voting.sol

Dapp 投票 Voting 实现流程_第9张图片

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 < candidateList.length; i++) {
            let name = candidateList[i];
            let aVoter = await web3.utils.fromAscii(name)
            let count = await contractInstance.methods.totalVotesFor(aVoter).call({ from: accounts[0] });
            console.log('count=', count)
            $("#" + candidates[name]).html(count)
        }
    })();


    voteForCandidate = async() => {
        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.0

pragma solidity >=0.4.25 <0.9.0;

contract Voting{
    bytes32[] public candidateList;
    mapping(bytes32 => 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)

Dapp 投票 Voting 实现流程_第10张图片


运行外部网络访问

在 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

Dapp 投票 Voting 实现流程_第11张图片

你可能感兴趣的:(Dapp,区块链,以太坊)