以太坊DApp开发入门:构建简易投票应用

·

本文将引导你从零开始,构建一个基于以太坊的简易投票DApp。通过智能合约实现投票逻辑,并利用Web3.js与前端页面进行交互,适合以太坊开发初学者入门实践。

环境配置与工具安装

在开始开发前,需要安装以下基础开发环境与工具:

使用npm全局安装所需工具:

npm install -g web3 solc ganache-cli

启动本地测试环境

首先启动本地测试区块链网络:

ganache-cli

启动后,系统会自动生成10个测试账户,每个账户预分配100个测试以太币(ETH)。记下提供的HTTP Provider地址(通常为http://localhost:8545),后续将用于连接区块链网络。

编写与编译智能合约

创建投票合约

使用Solidity语言编写投票智能合约,保存为Voting.sol文件:

pragma solidity ^0.4.11;

contract Voting {
    mapping (bytes32 => uint8) public votesReceived;
    bytes32[] public candidateList;

    function Voting(bytes32[] candidateNames) {
        candidateList = candidateNames;
    }

    function totalVotesFor(bytes32 candidate) returns (uint8) {
        if (validCandidate(candidate) == false) throw;
        return votesReceived[candidate];
    }

    function voteForCandidate(bytes32 candidate) {
        if (validCandidate(candidate) == false) throw;
        votesReceived[candidate] += 1;
    }

    function validCandidate(bytes32 candidate) returns (bool) {
        for(uint i = 0; i < candidateList.length; i++) {
            if (candidateList[i] == candidate) {
                return true;
            }
        }
        return false;
    }
}

编译智能合约

打开Node.js命令行,编译刚编写的合约:

Web3 = require('web3')
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

solc = require('solc')
fs = require('fs')

code = fs.readFileSync('Voting.sol').toString()
compiledCode = solc.compile(code)

部署智能合约到区块链

准备部署信息

获取合约ABI接口定义和字节码:

abiDefinition = JSON.parse(compiledCode.contracts[':Voting'].interface)
byteCode = compiledCode.contracts[':Voting'].bytecode

部署合约实例

创建合约对象并部署到区块链:

VotingContract = web3.eth.contract(abiDefinition)
deployedContract = VotingContract.new(
    ['Rama','Nick','Jose'],
    {data: byteCode, from: web3.eth.accounts[0], gas: 4700000}
)

contractInstance = VotingContract.at(deployedContract.address)

记下返回的合约地址,后续前端交互需要使用。

与智能合约交互

命令行测试

在部署完成后,可通过命令行直接与合约交互:

// 查询候选人得票数
contractInstance.totalVotesFor.call('Rama')

// 为候选人投票
contractInstance.voteForCandidate('Rama', {from: web3.eth.accounts[0]})

// 再次查询得票数
contractInstance.totalVotesFor.call('Rama').toLocaleString()

构建前端界面

创建HTML页面(index.html)提供用户界面:

<!DOCTYPE html>
<html>
<head>
    <title>投票DApp</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/web3.min.js"></script>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
    <h1>SSC投票应用</h1>
    <table>
        <tr><th>候选人</th><th>得票数</th></tr>
        <tr><td>Rama</td><td id="candidate-1">0</td></tr>
        <tr><td>Nick</td><td id="candidate-2">0</td></tr>
        <tr><td>Jose</td><td id="candidate-3">0</td></tr>
    </table>
    <input type="text" id="candidate" placeholder="输入候选人名字"/>
    <button onclick="voteForCandidate()">投票</button>
    <script src="index.js"></script>
</body>
</html>

创建JavaScript文件(index.js)处理前端逻辑:

web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));

// 合约ABI接口
abi = JSON.parse('[{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"totalVotesFor","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"validCandidate","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"bytes32"}],"name":"votesReceived","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"x","type":"bytes32"}],"name":"bytes32ToString","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"candidateList","outputs":[{"name":"","type":"bytes32"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"candidate","type":"bytes32"}],"name":"voteForCandidate","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"contractOwner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"inputs":[{"name":"candidateNames","type":"bytes32[]"}],"payable":false,"type":"constructor"}]')

VotingContract = web3.eth.contract(abi);

// 使用你的合约地址替换下面的地址
contractInstance = VotingContract.at('0x4131a0f92d36932d3ec3b7a0581546f2e662ad0b');

candidates = {"Rama": "candidate-1", "Nick": "candidate-2", "Jose": "candidate-3"}

function voteForCandidate() {
    candidateName = $("#candidate").val();
    contractInstance.voteForCandidate(candidateName, {from: web3.eth.accounts[0]}, function() {
        let div_id = candidates[candidateName];
        $("#" + div_id).html(contractInstance.totalVotesFor.call(candidateName).toString());
    });
}

$(document).ready(function() {
    candidateNames = Object.keys(candidates);
    for (var i = 0; i < candidateNames.length; i++) {
        let name = candidateNames[i];
        let val = contractInstance.totalVotesFor.call(name).toString()
        $("#" + candidates[name]).html(val);
    }
});

👉 查看实时开发工具与资源

运行与测试DApp

  1. 确保ganache-cli正在运行
  2. 在浏览器中打开index.html文件
  3. 在输入框中输入候选人姓名(Rama、Nick或Jose)
  4. 点击"投票"按钮,观察票数变化
  5. 可通过MetaMask等钱包查看交易状态和余额变化

常见问题

什么是智能合约?

智能合约是运行在区块链上的自执行代码,包含预设规则和协议。一旦部署到区块链,就无法更改,确保了交易的透明性和不可篡改性。

为什么需要本地测试环境?

本地测试环境如Ganache允许开发者在不消耗真实加密货币的情况下测试DApp功能,提供了快速迭代和调试的安全沙盒环境。

Web3.js的主要作用是什么?

Web3.js是以太坊的JavaScript库,提供了与以太坊区块链交互的接口。它允许前端应用读取区块链数据、发送交易并与智能合约进行交互。

如何将DApp部署到主网?

将DApp部署到主网需要以下步骤:1) 使用Truffle或Hardhat等框架编译和优化合约;2) 配置Infura等节点服务;3) 使用MetaMask连接主网;4) 支付Gas费部署合约;5) 更新前端配置指向主网合约地址。

投票DApp有哪些实际应用场景?

投票DApp可用于社区治理、DAO组织决策、民意调查、股东投票等场景,提供透明、可验证且不可篡改的投票机制,确保投票过程的公正性和结果的可信度。

如何改进这个基础投票DApp?

可改进的方向包括:添加投票者身份验证、实施投票权重机制、设置投票时间限制、增加匿名投票功能、优化Gas费用效率以及添加结果可视化图表等高级功能。

通过本教程,你已经掌握了构建基础以太坊DApp的核心流程。继续探索智能合约开发和前端集成,可以创建更复杂且功能丰富的去中心化应用。