项目意义

投票是现代民主社会的基石,确保选民的声音能够被公正、透明地表达。然而,近年来,投票过程中的各种乱象,如非法投票、选票篡改、技术故障等,严重威胁了民主制度的公信力和有效性。这些问题不仅影响了选举结果,还削弱了公众对民主制度的信任。因此,设计一个基于区块链的投票系统,将有助于提高投票过程的透明度和安全性。区块链技术的去中心化和不可篡改性能够确保每一票的真实性和有效性,减少欺诈行为,从而增强公众对选举的信心。

系统功能

核心功能

该系统将实现以下核心功能:

  1. 国民ID验证

    • 系统将通过国民ID来确认选民的身份,确保只有合法的本国国民可以参与投票。
    • 通过智能合约进行身份验证,确保国民ID的唯一性。
  2. 选举事件管理

    • 系统支持多种选举事件的创建和管理,选民可以在不同的选举中投票。
    • 每个选举事件将生成唯一的标识符,以便于跟踪和管理。
  3. 被选举人身份标识

    • 每位被选举人将被分配一个唯一的身份标识(例如,对其国民ID取余),以便于投票和统计。
  4. 投票时间记录

    • 系统将记录每次投票的时间戳,确保投票的时效性和合法性。
  5. 哈希时间戳生成

    • 投票信息将生成一个哈希时间戳,作为每个投票的独特标识,确保数据的完整性和不可篡改性。
  6. 风险控制措施

    • 虽然实名投票可能存在被追溯的风险,但系统将采取措施保护选民隐私,例如使用匿名投票技术和加密算法,确保选民的投票行为不被追踪。

区块链在该投票系统中的作用

主要作用

区块链技术在该投票系统中的主要作用包括:

  1. 数据安全性

    • 所有投票数据将被记录在区块链上,确保其不可篡改和透明。任何对投票数据的修改都将被记录下来,确保审计的可追溯性。
  2. 去中心化信任

    • 通过去中心化的方式,投票过程不再依赖于单一的中心化机构,减少了对第三方的信任需求。
  3. 数据统计与透明

    • 区块链的公开性使得任何人都可以查看投票结果,增强了选举的透明度,防止选票的隐瞒或篡改。
  4. 匿名性与隐私保护

    • 通过加密技术保护选民的匿名性,确保其投票不被外部干扰或追踪。

保护选民的隐私的改进措施

为了更好地保护选民的隐私,我们可以在投票系统的智能合约中进行以下改进:

  1. 使用匿名投票机制

在传统的投票系统中,投票人的地址与投票记录直接关联,可能导致选民身份的泄露。可以使用环签名(Ring Signatures)或零知识证明(Zero-Knowledge Proofs)等技术来实现匿名投票。

  1. 投票信息加密

在提交投票时,可以对投票内容进行加密。只有在投票统计时,授权的合约才能解密并验证投票的有效性。

  1. 采用哈希链路

将选民的投票哈希与其他信息结合,而不直接存储选民的地址。这可以通过生成一个用户的唯一标识符(如通过哈希算法处理国民ID)来实现,从而确保没有直接链接到选民的身份。

  1. 限制访问权限

在合约中设置适当的访问控制,以确保只有授权的角色(如选举管理员)可以访问敏感信息。

通过以上改进,投票系统能够更好地保护选民的隐私,同时保持系统的透明性和安全性。在设计智能合约时,应始终考虑隐私保护和数据安全,以建立公众对投票系统的信任。

验证选民的资格

为了验证选民的资格,我们可以在智能合约中添加一个功能,确保只有符合特定条件的选民才能参与投票。我们可以引入一个国民ID的注册系统,只有经过注册的国民才能投票。

  1. 增加选民注册功能

我们将增加一个选民注册功能,允许合约管理员添加符合条件的选民。每个选民的资格将通过其国民ID或其他标识符进行验证。

  1. 记录选民资格

我们将使用一个映射来存储已注册选民的资格信息,并在投票时进行验证。

通过引入选民注册机制和资格验证,智能合约能够有效地确保只有符合条件的选民可以参与投票。这种方法增强了系统的安全性和公正性,进一步提升了公众对投票系统的信任。

流程图

基本功能流程图

功能模块图

系统架构图

投票系统完整代码(Solidity)

以下是使用 Solidity 编写的投票系统的智能合约代码。该合约可以部署在以太坊虚拟机(EVM)上:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract VotingSystem {
    struct Election {
        uint256 id;
        string name;
        uint256 startTime;
        uint256 endTime;
        bool isActive;
    }

    struct Candidate {
        uint256 id;
        string name;
        uint256 voteCount;
    }

    struct Vote {
        bytes32 voterHash; // 存储选民哈希
        uint256 electionId;
        uint256 candidateId;
        uint256 timestamp;
        bytes32 hashTimestamp;
    }

    mapping(uint256 => Election) public elections;
    mapping(uint256 => mapping(uint256 => Candidate)) public candidates; // electionId => (candidateId => Candidate)
    mapping(uint256 => Vote[]) public votes; // electionId => Votes
    mapping(bytes32 => bool) public hasVoted; // voterHash => voted
    mapping(bytes32 => bool) public registeredVoters; // voterHash => registered

    uint256 public electionCount;
    uint256 public candidateCount;

    event ElectionCreated(uint256 electionId, string name);
    event CandidateAdded(uint256 electionId, uint256 candidateId, string name);
    event Voted(uint256 electionId, uint256 candidateId, bytes32 voterHash, uint256 timestamp, bytes32 hashTimestamp);
    event VoterRegistered(bytes32 voterHash);

    modifier onlyDuringElection(uint256 electionId) {
        require(elections[electionId].isActive, "Election is not active");
        require(block.timestamp >= elections[electionId].startTime && block.timestamp <= elections[electionId].endTime, "Voting is not allowed at this time");
        _;
    }

    modifier onlyRegisteredVoter(bytes32 voterHash) {
        require(registeredVoters[voterHash], "Voter is not registered");
        _;
    }

    function createElection(string memory name, uint256 startTime, uint256 endTime) public {
        require(endTime > startTime, "End time must be after start time");
        electionCount++;
        elections[electionCount] = Election(electionCount, name, startTime, endTime, true);
        emit ElectionCreated(electionCount, name);
    }

    function addCandidate(uint256 electionId, string memory name) public onlyDuringElection(electionId) {
        candidateCount++;
        candidates[electionId][candidateCount] = Candidate(candidateCount, name, 0);
        emit CandidateAdded(electionId, candidateCount, name);
    }

    function registerVoter(bytes32 voterHash) public {
        require(!registeredVoters[voterHash], "Voter is already registered");
        registeredVoters[voterHash] = true;
        emit VoterRegistered(voterHash);
    }

    function vote(uint256 electionId, uint256 candidateId, bytes32 voterHash) public onlyDuringElection(electionId) onlyRegisteredVoter(voterHash) {
        require(!hasVoted[voterHash], "You have already voted");

        hasVoted[voterHash] = true;
        
        uint256 timestamp = block.timestamp;
        bytes32 hashTimestamp = keccak256(abi.encodePacked(voterHash, electionId, candidateId, timestamp));

        votes[electionId].push(Vote(voterHash, electionId, candidateId, timestamp, hashTimestamp));
        candidates[electionId][candidateId].voteCount++;

        emit Voted(electionId, candidateId, voterHash, timestamp, hashTimestamp);
    }

    function getElectionResults(uint256 electionId) public view returns (Candidate[] memory) {
        uint256 candidateCountForElection = 0;
        for (uint256 i = 1; i <= candidateCount; i++) {
            if (candidates[electionId][i].id != 0) {
                candidateCountForElection++;
            }
        }

        Candidate[] memory results = new Candidate[](candidateCountForElection);
        uint256 index = 0;
        for (uint256 i = 1; i <= candidateCount; i++) {
            if (candidates[electionId][i].id != 0) {
                results[index] = candidates[electionId][i];
                index++;
            }
        }
        return results;
    }
}

代码说明

  • Election 结构体:用于存储选举事件的基本信息(ID、名称、开始与结束时间及状态)。
  • Candidate 结构体:存储候选人的信息(ID、名称、投票数量)。
  • Vote 结构体:记录每笔投票的信息(投票人地址、选举ID、候选人ID、时间戳及哈希时间戳)。
  • createElection:创建新的选举事件。
  • addCandidate:在选举中添加候选人。
  • vote:提交投票,并生成相应的哈希时间戳。
  • getElectionResults:获取选举结果的功能,返回候选人及其得票数。

这个设计确保了投票过程的透明性、安全性和不可篡改性,符合现代社会对民主选举的基本要求。

代码上链

部署到Remix-Ethereum IDE

image-20241212201935569

部署到truffle上

image-20241212220723108

image-20241212220816092

image-20241212220825946

image-20241212215243445

image-20241212215305599

image-20241212215727982

image-20241212215745432

Web后台和Web界面

使用指南

使用PyCharm打开代码,运行app.py文件,打开两个terminal。一个terminal执行 truffle compiletruffle migrate --network development,另一个terminal执行 ganache-cli

最终效果展示

image-20241213123557002

image-20241213123525252

image-20241213123634909

image-20241213123733249

相关代码

app.py

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
from flask import Flask, request, jsonify, render_template
from web3 import Web3

app = Flask(__name__)

# 连接到以太坊节点
w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:8545'))

# 智能合约地址和ABI
contract_address = '0x2A8611D40905715d30674E549DdfCDF5b8562500'
contract_abi = [
    # 填入你的合约ABI
	{
		"anonymous": False,
		"inputs": [
			{
				"indexed": False,
				"internalType": "uint256",
				"name": "electionId",
				"type": "uint256"
			},
			{
				"indexed": False,
				"internalType": "uint256",
				"name": "candidateId",
				"type": "uint256"
			},
			{
				"indexed": False,
				"internalType": "string",
				"name": "name",
				"type": "string"
			}
		],
		"name": "CandidateAdded",
		"type": "event"
	},
	{
		"anonymous": False,
		"inputs": [
			{
				"indexed": False,
				"internalType": "uint256",
				"name": "electionId",
				"type": "uint256"
			},
			{
				"indexed": False,
				"internalType": "string",
				"name": "name",
				"type": "string"
			}
		],
		"name": "ElectionCreated",
		"type": "event"
	},
	{
		"anonymous": False,
		"inputs": [
			{
				"indexed": False,
				"internalType": "uint256",
				"name": "electionId",
				"type": "uint256"
			},
			{
				"indexed": False,
				"internalType": "uint256",
				"name": "candidateId",
				"type": "uint256"
			},
			{
				"indexed": False,
				"internalType": "bytes32",
				"name": "voterHash",
				"type": "bytes32"
			},
			{
				"indexed": False,
				"internalType": "uint256",
				"name": "timestamp",
				"type": "uint256"
			},
			{
				"indexed": False,
				"internalType": "bytes32",
				"name": "hashTimestamp",
				"type": "bytes32"
			}
		],
		"name": "Voted",
		"type": "event"
	},
	{
		"anonymous": False,
		"inputs": [
			{
				"indexed": False,
				"internalType": "bytes32",
				"name": "voterHash",
				"type": "bytes32"
			}
		],
		"name": "VoterRegistered",
		"type": "event"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "electionId",
				"type": "uint256"
			},
			{
				"internalType": "string",
				"name": "name",
				"type": "string"
			}
		],
		"name": "addCandidate",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "candidateCount",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"name": "candidates",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "id",
				"type": "uint256"
			},
			{
				"internalType": "string",
				"name": "name",
				"type": "string"
			},
			{
				"internalType": "uint256",
				"name": "voteCount",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "string",
				"name": "name",
				"type": "string"
			},
			{
				"internalType": "uint256",
				"name": "startTime",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "endTime",
				"type": "uint256"
			}
		],
		"name": "createElection",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [],
		"name": "electionCount",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"name": "elections",
		"outputs": [
			{
				"internalType": "uint256",
				"name": "id",
				"type": "uint256"
			},
			{
				"internalType": "string",
				"name": "name",
				"type": "string"
			},
			{
				"internalType": "uint256",
				"name": "startTime",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "endTime",
				"type": "uint256"
			},
			{
				"internalType": "bool",
				"name": "isActive",
				"type": "bool"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "electionId",
				"type": "uint256"
			}
		],
		"name": "getElectionResults",
		"outputs": [
			{
				"components": [
					{
						"internalType": "uint256",
						"name": "id",
						"type": "uint256"
					},
					{
						"internalType": "string",
						"name": "name",
						"type": "string"
					},
					{
						"internalType": "uint256",
						"name": "voteCount",
						"type": "uint256"
					}
				],
				"internalType": "struct VotingSystem.Candidate[]",
				"name": "",
				"type": "tuple[]"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "bytes32",
				"name": "",
				"type": "bytes32"
			}
		],
		"name": "hasVoted",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "bytes32",
				"name": "voterHash",
				"type": "bytes32"
			}
		],
		"name": "registerVoter",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "bytes32",
				"name": "",
				"type": "bytes32"
			}
		],
		"name": "registeredVoters",
		"outputs": [
			{
				"internalType": "bool",
				"name": "",
				"type": "bool"
			}
		],
		"stateMutability": "view",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "electionId",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "candidateId",
				"type": "uint256"
			},
			{
				"internalType": "bytes32",
				"name": "voterHash",
				"type": "bytes32"
			}
		],
		"name": "vote",
		"outputs": [],
		"stateMutability": "nonpayable",
		"type": "function"
	},
	{
		"inputs": [
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "",
				"type": "uint256"
			}
		],
		"name": "votes",
		"outputs": [
			{
				"internalType": "bytes32",
				"name": "voterHash",
				"type": "bytes32"
			},
			{
				"internalType": "uint256",
				"name": "electionId",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "candidateId",
				"type": "uint256"
			},
			{
				"internalType": "uint256",
				"name": "timestamp",
				"type": "uint256"
			},
			{
				"internalType": "bytes32",
				"name": "hashTimestamp",
				"type": "bytes32"
			}
		],
		"stateMutability": "view",
		"type": "function"
	}
]

contract = w3.eth.contract(address=contract_address, abi=contract_abi)
@app.route('/')
def home():
    return render_template('index.html')
@app.route('/create_election', methods=['POST'])
def create_election():
    data = request.json
    tx_hash = contract.functions.createElection(data['name'], data['startTime'], data['endTime']).transact({'from': w3.eth.accounts[0]})
    w3.eth.wait_for_transaction_receipt(tx_hash)
    return jsonify({'status': 'Election created!'})

@app.route('/add_candidate', methods=['POST'])
def add_candidate():
    data = request.json
    tx_hash = contract.functions.addCandidate(data['electionId'], data['name']).transact({'from': w3.eth.accounts[0]})
    w3.eth.wait_for_transaction_receipt(tx_hash)
    return jsonify({'status': 'Candidate added!'})

@app.route('/vote', methods=['POST'])
def vote():
    data = request.json
    tx_hash = contract.functions.vote(data['electionId'], data['candidateId'], data['voterHash']).transact({'from': w3.eth.accounts[0]})
    w3.eth.wait_for_transaction_receipt(tx_hash)
    return jsonify({'status': 'Vote cast!'})

@app.route('/get_results/<int:election_id>', methods=['GET'])
def get_results(election_id):
    results = contract.functions.getElectionResults(election_id).call()
    return jsonify(results)

if __name__ == '__main__':
    app.run(debug=True)

index.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Voting System</title>
</head>
<body>
    <h1>Voting System</h1>

    <h2>Create Election</h2>
    <form id="createElectionForm">
        <input type="text" id="electionName" placeholder="Election Name" required>
        <input type="number" id="startTime" placeholder="Start Time" required>
        <input type="number" id="endTime" placeholder="End Time" required>
        <button type="submit">Create Election</button>
    </form>

    <h2>Add Election Candidate</h2>
    <form id="ElectionForm">
        <input type="number" id="electionId" placeholder="Election ID" required>
        <input type="text" id="name" placeholder="Election Name" required>
        <button type="submit">Add Election Candidate</button>
    </form>

    <h2>Vote For Election Candidate</h2>
    <form id="VoteForm">
        <input type="number" id="electionIdForVote" placeholder="Election ID" required>
        <input type="number" id="candidateId" placeholder="Candidate ID" required>
        <input type="text" id="voterHash" placeholder="VoterHash" required>
        <button type="submit">Vote For Election Candidate</button>
    </form>

    <script>
        document.getElementById('createElectionForm').onsubmit = async function(event) {
            event.preventDefault();
            const response = await fetch('/create_election', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                    name: document.getElementById('electionName').value,
                    startTime: parseInt(document.getElementById('startTime').value),
                    endTime: parseInt(document.getElementById('endTime').value)
                })
            });
            const result = await response.json();
            alert(result.status);
        };
    </script>
</body>
</html>

VotingSystem.sol

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

contract VotingSystem {
    struct Election {
        uint256 id;
        string name;
        uint256 startTime;
        uint256 endTime;
        bool isActive;
    }

    struct Candidate {
        uint256 id;
        string name;
        uint256 voteCount;
    }

    struct Vote {
        bytes32 voterHash; // 存储选民哈希
        uint256 electionId;
        uint256 candidateId;
        uint256 timestamp;
        bytes32 hashTimestamp;
    }

    mapping(uint256 => Election) public elections;
    mapping(uint256 => mapping(uint256 => Candidate)) public candidates; // electionId => (candidateId => Candidate)
    mapping(uint256 => Vote[]) public votes; // electionId => Votes
    mapping(bytes32 => bool) public hasVoted; // voterHash => voted
    mapping(bytes32 => bool) public registeredVoters; // voterHash => registered

    uint256 public electionCount;
    uint256 public candidateCount;

    event ElectionCreated(uint256 electionId, string name);
    event CandidateAdded(uint256 electionId, uint256 candidateId, string name);
    event Voted(uint256 electionId, uint256 candidateId, bytes32 voterHash, uint256 timestamp, bytes32 hashTimestamp);
    event VoterRegistered(bytes32 voterHash);

    modifier onlyDuringElection(uint256 electionId) {
        require(elections[electionId].isActive, "Election is not active");
        require(block.timestamp >= elections[electionId].startTime && block.timestamp <= elections[electionId].endTime, "Voting is not allowed at this time");
        _;
    }

    modifier onlyRegisteredVoter(bytes32 voterHash) {
        require(registeredVoters[voterHash], "Voter is not registered");
        _;
    }

    function createElection(string memory name, uint256 startTime, uint256 endTime) public {
        require(endTime > startTime, "End time must be after start time");
        electionCount++;
        elections[electionCount] = Election(electionCount, name, startTime, endTime, true);
        emit ElectionCreated(electionCount, name);
    }

    function addCandidate(uint256 electionId, string memory name) public onlyDuringElection(electionId) {
        candidateCount++;
        candidates[electionId][candidateCount] = Candidate(candidateCount, name, 0);
        emit CandidateAdded(electionId, candidateCount, name);
    }

    function registerVoter(bytes32 voterHash) public {
        require(!registeredVoters[voterHash], "Voter is already registered");
        registeredVoters[voterHash] = true;
        emit VoterRegistered(voterHash);
    }

    function vote(uint256 electionId, uint256 candidateId, bytes32 voterHash) public onlyDuringElection(electionId) onlyRegisteredVoter(voterHash) {
        require(!hasVoted[voterHash], "You have already voted");

        hasVoted[voterHash] = true;

        uint256 timestamp = block.timestamp;
        bytes32 hashTimestamp = keccak256(abi.encodePacked(voterHash, electionId, candidateId, timestamp));

        votes[electionId].push(Vote(voterHash, electionId, candidateId, timestamp, hashTimestamp));
        candidates[electionId][candidateId].voteCount++;

        emit Voted(electionId, candidateId, voterHash, timestamp, hashTimestamp);
    }

    function getElectionResults(uint256 electionId) public view returns (Candidate[] memory) {
        uint256 candidateCountForElection = 0;
        for (uint256 i = 1; i <= candidateCount; i++) {
            if (candidates[electionId][i].id != 0) {
                candidateCountForElection++;
            }
        }

        Candidate[] memory results = new Candidate[](candidateCountForElection);
        uint256 index = 0;
        for (uint256 i = 1; i <= candidateCount; i++) {
            if (candidates[electionId][i].id != 0) {
                results[index] = candidates[electionId][i];
                index++;
            }
        }
        return results;
    }
}

truffle-config.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
module.exports = {
  /**
   * Networks define how you connect to your ethereum client and let you set the
   * defaults web3 uses to send transactions. If you don't specify one truffle
   * will spin up a managed Ganache instance for you on port 9545 when you
   * run `develop` or `test`. You can ask a truffle command to use a specific
   * network from the command line, e.g
   *
   * $ truffle test --network <network-name>
   */

  networks: {
    // Useful for testing. The `development` name is special - truffle uses it by default
    // if it's defined here and no other network is specified at the command line.
    // You should run a client (like ganache, geth, or parity) in a separate terminal
    // tab if you use this network and you must also set the `host`, `port` and `network_id`
    // options below to some value.
    //
    development: {
     host: "127.0.0.1",     // Localhost (default: none)
     port: 8545,            // Standard Ethereum port (default: none)
     network_id: "*",       // Any network (default: none)
    },
    //
    // An additional network, but with some advanced options…
    // advanced: {
    //   port: 8777,             // Custom port
    //   network_id: 1342,       // Custom network
    //   gas: 8500000,           // Gas sent with each transaction (default: ~6700000)
    //   gasPrice: 20000000000,  // 20 gwei (in wei) (default: 100 gwei)
    //   from: <address>,        // Account to send transactions from (default: accounts[0])
    //   websocket: true         // Enable EventEmitter interface for web3 (default: false)
    // },
    //
    // Useful for deploying to a public network.
    // Note: It's important to wrap the provider as a function to ensure truffle uses a new provider every time.
    // goerli: {
    //   provider: () => new HDWalletProvider(MNEMONIC, `https://goerli.infura.io/v3/${PROJECT_ID}`),
    //   network_id: 5,       // Goerli's id
    //   confirmations: 2,    // # of confirmations to wait between deployments. (default: 0)
    //   timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
    //   skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
    // },
    //
    // Useful for private networks
    // private: {
    //   provider: () => new HDWalletProvider(MNEMONIC, `https://network.io`),
    //   network_id: 2111,   // This network is yours, in the cloud.
    //   production: true    // Treats this network as if it was a public net. (default: false)
    // }
  },

  // Set default mocha options here, use special reporters, etc.
  mocha: {
    // timeout: 100000
  },

  // Configure your compilers
  compilers: {
    solc: {
      version: "0.8.19",      // Fetch exact version from solc-bin (default: truffle's version)
      // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
      // settings: {          // See the solidity docs for advice about optimization and evmVersion
      //  optimizer: {
      //    enabled: false,
      //    runs: 200
      //  },
      //  evmVersion: "byzantium"
      // }
    }
  },

  // Truffle DB is currently disabled by default; to enable it, change enabled:
  // false to enabled: true. The default storage location can also be
  // overridden by specifying the adapter settings, as shown in the commented code below.
  //
  // NOTE: It is not possible to migrate your contracts to truffle DB and you should
  // make a backup of your artifacts to a safe location before enabling this feature.
  //
  // After you backed up your artifacts you can utilize db by running migrate as follows:
  // $ truffle migrate --reset --compile-all
  //
  // db: {
  //   enabled: false,
  //   host: "127.0.0.1",
  //   adapter: {
  //     name: "indexeddb",
  //     settings: {
  //       directory: ".db"
  //     }
  //   }
  // }
};

truffleDeploy.js

1
2
3
4
5
const VotingSystem = artifacts.require("VotingSystem");

module.exports = async function(deployer) {
    await deployer.deploy(VotingSystem);
};

2_deploy_contracts.js

1
2
3
4
5
const VotingSystem = artifacts.require("VotingSystem");

module.exports = async function(deployer) {
    await deployer.deploy(VotingSystem);
};

安装必要工具

Required Tools and Libraries for Developing a dApp with Python

  • web3.py
  • Solidity Compiler (solc)
  • Ganache
  • Truffle Suite
  • MetaMask
  • IDE
  • Node.js and npm:

相应命令

1
2
3
4
5
6
7
npm install -g solc

npm install -g truffle

npm install -g ganache-cli

pip install web3 py-solc-x

基本部署流程和命令

  1. 执行ganache-cli以获得账户、私有密钥和钱包,truffle init初始化以部署在本地

  2. VotingSystem.sol放到生成的contracts目录下,把2_deploy_contracts.js文件放到生成的migrations目录下。

  3. 在项目根目录下执行truffle compile来编译solidity文件。

  4. 执行truffle migrate --network development来部署智能合约,记录下合约地址。

  5. 通过Remix Ethereum IDE平台编译VotingSystem.sol并获得该智能合约对应的ABI

  6. 把合约地址和ABI填入app.py文件,以实现智能合约和外界环境通过Web3建立联系

  7. 执行app.py文件启动整个项目

注意:编译器版本需要设定为8.19.0,更高的版本号会报错。