整个教程最终完整代码:https://download.csdn.net/download/u011680118/10649069
一、更新Election.sol智能合约
本教程的最后一步是在投票发生时触发事件,这能帮助我们动态的更新前台界面,更新后的智能合约如下:
pragma solidity ^0.4.2;
contract Election {
//候选者结构体
struct Candidate {
uint id;
string name;
uint voteCount;
}
//候选者id到结构体的映射
mapping(uint => Candidate) public candidates;
//投票者地址到是否投票的映射
mapping(address => bool) public voters;
//总共多少候选者
uint public candidatesCount;
//定义投票事件
event votedEvent(uint indexed _candidateId);
//构造函数
constructor() public {
addCandidate("Candidate 1");
addCandidate("Candidate 2");
}
//添加候选者
function addCandidate(string _name) private {
candidatesCount ++;
candidates[candidatesCount] = Candidate(candidatesCount,_name,0);
} //投票函数
function vote(uint _candidateId) public {
//要求投票者从没投过票
require(!voters[msg.sender]);
//msg.sender是调用这个函数的账户
//要求候选的Id合法
require(_candidateId > 0 && _candidateId <= candidatesCount);
//确定投票
voters[msg.sender] = true;
//更新候选者票数
candidates[_candidateId].voteCount ++;
//触发投票事件,原教程没有emit,我编译会出错
emit votedEvent(_candidateId);
}
}
更新后的测试文件election.js如下:
var Election = artifacts.require("./Election.sol");
contract("Election", function(accounts) {
var electionInstance;
//初始化有两个候选者
it("initializes with two candidates", function() {
return Election.deployed().then(function(instance) {
return instance.candidatesCount();
}).then(function(count) {
assert.equal(count, 2);
});
});
//候选者初始化信息是否正确
it("it initializes the candidates with the correct values", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.candidates(1);
}).then(function(candidate) {
assert.equal(candidate[0], 1, "contains the correct id");
assert.equal(candidate[1], "Candidate 1", "contains the correct name");
assert.equal(candidate[2], 0, "contains the correct votes count");
return electionInstance.candidates(2);
}).then(function(candidate) {
assert.equal(candidate[0], 2, "contains the correct id");
assert.equal(candidate[1], "Candidate 2", "contains the correct name");
assert.equal(candidate[2], 0, "contains the correct votes count");
})
});
//测试是否允许投票者进行投票
it("allows a voter to cast a vote", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
candidateId = 1;
return electionInstance.vote(candidateId, {
from: accounts[0]
});
}).then(function(receipt) {
return electionInstance.voters(accounts[0]);
}).then(function(voted) {
assert(voted, "the voter was marked as voted");
return electionInstance.candidates(candidateId);
}).then(function(candidate) {
var voteCount = candidate[2];
assert.equal(voteCount, 1, "increments the candidate's vote count");
})
});
//测试对于非合法候选者进行投票
it("throws an exception for invalid candidates", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.vote(99, {
from: accounts[1]
})
}).then(assert.fail).catch(function(error) {
assert(error.message.indexOf('revert') >= 0, "error message must contain revert");
return electionInstance.candidates(1);
}).then(function(candidate1) {
var voteCount = candidate1[2];
assert.equal(voteCount, 1, "candidate 1 did not receive any votes");
return electionInstance.candidates(2);
}).then(function(candidate2) {
var voteCount = candidate2[2];
assert.equal(voteCount, 0, "candidate 2 did not receive any votes");
});
});
//测试能否重复投票
it("throws an exception for double voting", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
candidateId = 2;
electionInstance.vote(candidateId, {
from: accounts[1]
});
return electionInstance.candidates(candidateId);
}).then(function(candidate) {
var voteCount = candidate[2];
assert.equal(voteCount, 1, "accepts first vote");
// Try to vote again
return electionInstance.vote(candidateId, {
from: accounts[1]
});
}).then(assert.fail).catch(function(error) {
assert(error.message.indexOf('revert') >= 0, "error message must contain revert");
return electionInstance.candidates(1);
}).then(function(candidate1) {
var voteCount = candidate1[2];
assert.equal(voteCount, 1, "candidate 1 did not receive any votes");
return electionInstance.candidates(2);
}).then(function(candidate2) {
var voteCount = candidate2[2];
assert.equal(voteCount, 1, "candidate 2 did not receive any votes");
});
});
//测试是否触发投票事件
it("allows a voter to cast a vote", function() {
return Election.deployed().then(function(instance) {
electionInstance = instance;
candidateId = 1;
return electionInstance.vote(candidateId, {
from: accounts[0]
});
}).then(function(receipt) {
assert.equal(receipt.logs.length, 1, "an event was triggered");
assert.equal(receipt.logs[0].event, "votedEvent", "the event type is correct");
assert.equal(receipt.logs[0].args._candidateId.toNumber(), candidateId, "the candidate id is correct");
return electionInstance.voters(accounts[0]);
}).then(function(voted) {
assert(voted, "the voter was marked as voted");
return electionInstance.candidates(candidateId);
}).then(function(candidate) {
var voteCount = candidate[2];
assert.equal(voteCount, 1, "increments the candidate's vote count");
})
});
});
测试代码监控vote函数返回的交易receipt以保证其有日志信息,这些日志包括了被触发的事件信息,我们检查事件类型是否正确,参数是否正确。
现在开始更新前台代码,以监听投票事件,并且同时更新页面,我们在app.js中添加一个"listenForEvents"函数,如下所示:
listenForEvents: function() {
App.contracts.Election.deployed().then(function(instance) {
instance.votedEvent({}, {
fromBlock: 0,
toBlock: 'latest'
}).watch(function(error, event) {
console.log("event triggered", event)
// Reload when a new vote is recorded
App.render();
});
});
}
这个函数通过调用"votedEvent"函数以监听投票事件,然后传入一些参数数据保证监听区块链上的所有事件,然后监听的时候打印事件信息, 发生投票事件后进行页面更新,从loading界面到正常界面,同时显示得票数,并隐藏投票功能。对于初始化函数进行更新,如下:
initContract: function() {
$.getJSON("Election.json", function(election) {
// Instantiate a new truffle contract from the artifact
App.contracts.Election = TruffleContract(election);
// Connect provider to interact with contract
App.contracts.Election.setProvider(App.web3Provider);
App.listenForEvents();
return App.render();
});
}
完整的app.js代码如下:
App = {
web3Provider: null,
contracts: {},
account: '0x0',
hasVoted: false,init: function() {
return App.initWeb3();
},initWeb3: function() {
// TODO: refactor conditional
if (typeof web3 !== 'undefined') {
// If a web3 instance is already provided by Meta Mask.
App.web3Provider = web3.currentProvider;
web3 = new Web3(web3.currentProvider);
} else {
// Specify default instance if no web3 instance provided
App.web3Provider = new Web3.providers.HttpProvider('http://localhost:7545');
web3 = new Web3(App.web3Provider);
}
return App.initContract();
},initContract: function() {
$.getJSON("Election.json", function(election) {
// Instantiate a new truffle contract from the artifact
App.contracts.Election = TruffleContract(election);
// Connect provider to interact with contract
App.contracts.Election.setProvider(App.web3Provider);
App.listenForEvents();
return App.render();
});
},// Listen for events emitted from the contract
listenForEvents: function() {
App.contracts.Election.deployed().then(function(instance) {
// Restart Chrome if you are unable to receive this event
// This is a known issue with Metamask
// https://github.com/MetaMask/metamask-extension/issues/2393
instance.votedEvent({}, {
fromBlock: 0,
toBlock: 'latest'
}).watch(function(error, event) {
console.log("event triggered", event)
// Reload when a new vote is recorded
App.render();
});
});
},render: function() {
var electionInstance;
var loader = $("#loader");
var content = $("#content");
loader.show();
content.hide();
// Load account data
web3.eth.getCoinbase(function(err, account) {
if (err === null) {
App.account = account;
$("#accountAddress").html("Your Account: " + account);
}
});
// Load contract data
App.contracts.Election.deployed().then(function(instance) {
electionInstance = instance;
return electionInstance.candidatesCount();
}).then(function(candidatesCount) {
var candidatesResults = $("#candidatesResults");
candidatesResults.empty();
var candidatesSelect = $('#candidatesSelect');
candidatesSelect.empty();
for (var i = 1;
i <= candidatesCount;
i++) {
electionInstance.candidates(i).then(function(candidate) {
var id = candidate[0];
var name = candidate[1];
var voteCount = candidate[2];
// Render candidate Result
var candidateTemplate = "" + id + " " + name + " " + voteCount + " "
candidatesResults.append(candidateTemplate);
// Render candidate ballot option
var candidateOption = "
二、重新部署智能合约和运行
注意:重启ganache,之前部署的智能合约都会丢失,需要重新部署,会从头生成区块
输入truffle migrate重新部署时报错:Error: Attempting to run transaction which calls a contract function, but recipient address 0x3e90490273dd38550d590194877d2162a8d0932f is not a contract address
解决办法,就是将程序根目录(election目录)下的build文件夹删除,然后重新truffle migrate即可
文章图片
【以太坊dApp开发教程(如何一步步构造一个全栈式去中心化应用)(五)监听事件】因为我是重启ganache后重新部署的,所以区块也重新生成,第3个区块包含了新部署合约的地址信息
文章图片
输入npm run dev 观察页面,重新登录之前注册的metamask账户,在投票时发现confirm交易后会报错:
Error: the tx doesn't have the correct nonce
重新选择metamask的网络,输入http://localhost:7545,再用之前的账户投票,仍然不成功,后来想到可能是因为之前已经投过票了
导入一个新账户,再投票,就能交易成功,且能自动更新,不用手动刷新了。如果页面没反应,请重启chrome浏览器
文章图片
观察ganache发现生成了一个新区块,其中的发送地址为上述投票的账户地址,接受地址是我们的合约地址。
文章图片
我在metamask上注册的账户因为以太币为0而不能投票,我用这个新导入的账户给其转入50以太币,转账的交易在ganache中也可以看到,从发送地址到接受账户地址,交易金额为50以太币:
文章图片
然后用535开头的这个账户进行投票,可以投票成功
文章图片
投票的交易也在ganache中记录,发送账户为535开头的地址,接受地址为合约地址:
文章图片
但是投票之后用别的账户登录,再刷新界面时,显示不正常:
文章图片
看到项目源码下的评论,可以解决这个问题:https://github.com/dappuniversity/election/issues/2
把app.js中的监听函数改一下,从最新的区块监听,如果从第一个区块开始监听,会有旧的事件,再更新页面就会出现重复
listenForEvents: function() {
App.contracts.Election.deployed().then(function(instance) {
// Restart Chrome if you are unable to receive this event
// This is a known issue with Metamask
// https://github.com/MetaMask/metamask-extension/issues/2393
instance.votedEvent({}, {
fromBlock: 'latest',//原来是0,会导致候选者列表重复出现,改成latest就正常了
toBlock: 'latest'
}).watch(function(error, event) {
console.log("event triggered", event)
// Reload when a new vote is recorded
App.render();
});
});
},
重新部署合约,对于之前用过的账户,进行投票时会报错,显示transaction failed:the tx doesn't have the correct nonce之类的信息,解决方法是打开metamask的账户,点击设置,然后reset账户,清空之前的transaction信息,再投票即可。
解决方法来源:https://consensys.zendesk.com/hc/en-us/articles/360004177531-Resetting-an-Account-New-UI-
至此,教程结束
推荐阅读
- 推动NFT走出监管困境,BSN推出支持NFT基础设施网络
- 腾讯|SaaS的收入模型有哪些(终于有人讲明白了)
- 就业方向上什么才是最重要的(--- 来自程序猿的迷茫。(C++?Java?or算法?))
- 区块链中加密货币的含义
- 波场万倍潜力币HYL23号21:09分 正式上线JustSwap
- 《瀚兰房地产开发区块链应用及案例分享》BSN培训精华回顾
- 对联盟链而言,跨链协议为什么重要()
- 区块链能够应用在哪些行业
- BSN区块链服务网络中密钥托管模式和公钥上传模式有啥区别()
- 币圈人物传|币圈大佬今何在 唯有一诺正当时