You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
600 lines
22 KiB
600 lines
22 KiB
// 引入所需的库和模块
|
|
const express = require('express');
|
|
const app = express();
|
|
const path = require('path');
|
|
const { Level } = require('level');
|
|
const fs = require("fs");
|
|
const pino = require('pino');
|
|
const Web3 = require('web3');
|
|
const mysql = require('mysql'); // 使用mysql2模块
|
|
|
|
// 获取合约ABI和字节码
|
|
const VotingSystemContract = require('./build/contracts/VotingSystem.json');
|
|
const contractABI = VotingSystemContract.abi;
|
|
const contractBytecode = VotingSystemContract.bytecode;
|
|
|
|
// 打开或创建leveldb数据库
|
|
const db = new Level('ethereum', { valueEncoding: 'json' })
|
|
// 连接到以太坊网络
|
|
const web3 = new Web3('http://localhost:7545');
|
|
|
|
// 设置静态文件目录
|
|
app.use(express.static(path.join(__dirname, 'public')));
|
|
|
|
// 流日志
|
|
const stream = fs.createWriteStream("./log.txt", { flags: 'a' });
|
|
const logger = pino(stream);
|
|
|
|
// 使用 express.urlencoded() 中间件解析表单数据
|
|
app.use(express.urlencoded({ extended: true }));
|
|
|
|
// 使用 express.json() 中间件解析 JSON 数据
|
|
app.use(express.json());
|
|
|
|
// 创建MySQL连接池
|
|
const pool = mysql.createPool({
|
|
host: 'localhost',
|
|
user: 'root',
|
|
password: 'panzhixin',
|
|
database: 'blockvote'
|
|
});
|
|
|
|
// 存储区块信息到数据库
|
|
async function saveBlockData(blockData) {
|
|
try {
|
|
const blockDataString = JSON.stringify(blockData);
|
|
await db.put(blockData.blockHash, blockDataString); // 存储转换后的字符串到数据库
|
|
} catch (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
throw error; // 抛出异常以便调用者捕获并处理
|
|
}
|
|
}
|
|
|
|
// 根据区块哈希检索区块信息
|
|
async function getBlockData(blockHash) {
|
|
try {
|
|
const data = await db.get(blockHash);
|
|
const parsedData = JSON.parse(data); // 解析为 JSON 格式
|
|
return parsedData; // 返回完整的区块数据对象
|
|
} catch (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
return null; // 或者返回空对象 {}
|
|
}
|
|
}
|
|
|
|
// 将MySQL连接池添加到Express应用程序的本地变量中
|
|
app.locals.pool = pool;
|
|
|
|
// 插入数据到 ballots 表
|
|
function insertDataIntoBallots(creatorAddress, contractAddress, voteTitle, deadline) {
|
|
// 构建插入语句
|
|
const sql = `INSERT INTO ballots (creator_address, contract_address, vote_title, deadline) VALUES (?, ?, ?, ?)`;
|
|
|
|
// 使用连接池执行插入操作
|
|
pool.query(sql, [creatorAddress, contractAddress, voteTitle, deadline], (error, results, fields) => {
|
|
if (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
|
|
// 插入id和hash值
|
|
function insertIntoEthereum(blockID, hashValue) {
|
|
const sql = 'INSERT INTO blockdata (blockID, hashValue) VALUES (?, ?)';
|
|
pool.query(sql, [blockID, hashValue], (error, result, fields) => {
|
|
if (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
return;
|
|
}
|
|
})
|
|
}
|
|
|
|
// 从数据库中获取 ballots 数据并返回给前端
|
|
function getBallotsData(callback) {
|
|
// 构建查询语句
|
|
const sql = `SELECT * FROM blockdata ORDER BY blockID DESC`;
|
|
|
|
// 使用连接池获取连接
|
|
pool.getConnection((error, connection) => {
|
|
if (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
callback(error, null);
|
|
return;
|
|
}
|
|
|
|
// 执行查询操作
|
|
connection.query(sql, (error, results, fields) => {
|
|
// 释放连接
|
|
connection.release();
|
|
|
|
if (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
callback(error, null);
|
|
return;
|
|
}
|
|
|
|
// 查询成功,将结果返回给回调函数
|
|
callback(null, results);
|
|
});
|
|
});
|
|
}
|
|
|
|
// 插入历史合约数据的函数
|
|
function insertHistoryContract(contractAddress, voterAddress, voteTitle, deadline, userChoice) {
|
|
return new Promise((resolve, reject) => {
|
|
pool.getConnection((err, connection) => {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
const query = 'INSERT INTO history_contracts (contract_address, voter_address, vote_title, deadline, user_choice, created_at) VALUES (?, ?, ?, ?, ?, NOW())';
|
|
connection.query(query, [contractAddress, voterAddress, voteTitle, deadline, userChoice], (error, results) => {
|
|
connection.release();
|
|
if (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
resolve(results);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
// 在应用程序关闭时关闭数据库连接
|
|
process.on('SIGINT', () => {
|
|
pool.end((err) => {
|
|
if (err) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
process.exit(1);
|
|
}
|
|
console.log('程序关闭,成功关闭数据库连接');
|
|
logger.info('program broken (CTRL + C)');
|
|
process.exit(0);
|
|
});
|
|
});
|
|
|
|
async function saveBlockDataToDatabase(fromAddress, toAddress) {
|
|
try {
|
|
// 在发生交易后调用该函数
|
|
const block = await web3.eth.getBlock('latest');
|
|
const blockData = {
|
|
blockId: block.number,
|
|
timestamp: block.timestamp,
|
|
blockHash: block.hash,
|
|
parentHash: block.parentHash,
|
|
difficulty: block.difficulty,
|
|
miner: block.miner,
|
|
stateRoot: block.stateRoot,
|
|
transactionsRoot: block.transactionsRoot,
|
|
receiptsRoot: block.receiptsRoot,
|
|
txHash: block.transactions,
|
|
gasUsed: block.gasUsed,
|
|
gasLimit: block.gasLimit,
|
|
fromAddress: fromAddress,
|
|
toAddress: toAddress,
|
|
uncles: block.uncles
|
|
};
|
|
insertIntoEthereum(block.number, block.hash);
|
|
await saveBlockData(blockData);
|
|
} catch (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
}
|
|
}
|
|
|
|
// 创建 POST 路由处理前端提交的表单数据
|
|
app.post('/createVote', async (req, res) => {
|
|
// 从请求体中提取表单数据
|
|
const formData = req.body;
|
|
const metaMaskUser = req.body.metaMaskUser;
|
|
// 单独保存表单数据的各个字段
|
|
const voteTitle = formData.voteTitle;
|
|
const numOptions = formData.numOptions;
|
|
const options = [];
|
|
for (let i = 1; i <= numOptions; i++) {
|
|
options.push(formData['option' + i]);
|
|
}
|
|
|
|
// 获取截止时间的时间戳(毫秒)
|
|
const deadlineTimestamp = new Date(formData.deadline).getTime();
|
|
try {
|
|
// 部署新的合约
|
|
let newContractInstance = await new web3.eth.Contract(contractABI)
|
|
.deploy({
|
|
data: contractBytecode,
|
|
arguments: [voteTitle, options, deadlineTimestamp] // 传递合约到构造函数中,但是由于本地字节码是源码的,需要再次调用合约函数
|
|
})
|
|
.send({
|
|
from: metaMaskUser, // 使用全局变量中存储的调用者地址来部署合约
|
|
gas: 1500000, // 指定 gas 上限
|
|
gasPrice: '30000000000' // 指定 gas 价格
|
|
});
|
|
|
|
// 存入日志文件中
|
|
logger.info({
|
|
message: "Successfully to create a new contract",
|
|
contractAddress: newContractInstance.options.address,
|
|
createdBy: metaMaskUser,
|
|
voteTitle,
|
|
deadlineTimestamp,
|
|
options
|
|
});
|
|
|
|
res.json({ success: true, contractAddress: newContractInstance.options.address });
|
|
// 在成功部署合约后调用该函数,将合约信息插入到数据库中
|
|
insertDataIntoBallots(metaMaskUser, newContractInstance.options.address, voteTitle, formData.deadline);
|
|
initContract(voteTitle, options, deadlineTimestamp, metaMaskUser, newContractInstance);
|
|
// 部署合约成功后获取区块和交易信息
|
|
// 获取最新区块的信息
|
|
saveBlockDataToDatabase(metaMaskUser, newContractInstance.options.address);
|
|
} catch (error) {
|
|
// 记录出错时的日志信息
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ error: 'Failed to deploy contract' });
|
|
}
|
|
});
|
|
|
|
// 使用合约实例调用Solidity合约中的函数
|
|
async function initContract(voteTitle, options, deadlineTimestamp, metaMaskUser, newContractInstance) {
|
|
try {
|
|
// 调用Solidity合约中的setTitle函数
|
|
await newContractInstance.methods.setTitle(voteTitle).send({
|
|
from: metaMaskUser, // 从这个地址发送交易
|
|
gas: 3000000 // 设置gas限制
|
|
});
|
|
saveBlockDataToDatabase(metaMaskUser, newContractInstance.options.address);
|
|
// 调用Solidity合约中的setOptions函数
|
|
await newContractInstance.methods.setOptions(options).send({
|
|
from: metaMaskUser, // 从这个地址发送交易
|
|
gas: 3000000 // 设置gas限制
|
|
});
|
|
saveBlockDataToDatabase(metaMaskUser, newContractInstance.options.address);
|
|
// 调用Solidity合约中的setDeadline函数
|
|
await newContractInstance.methods.setDeadline(deadlineTimestamp).send({
|
|
from: metaMaskUser, // 从这个地址发送交易
|
|
gas: 3000000 // 设置gas限制
|
|
});
|
|
saveBlockDataToDatabase(metaMaskUser, newContractInstance.options.address);
|
|
// 设置投票状态
|
|
await newContractInstance.methods.setIsOpen(true).send({
|
|
from: metaMaskUser, // 从这个地址发送交易
|
|
gas: 3000000 // 设置gas限制
|
|
});
|
|
saveBlockDataToDatabase(metaMaskUser, newContractInstance.options.address);
|
|
} catch (error) {
|
|
// 记录错误日志
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
}
|
|
}
|
|
|
|
app.get('/ethereum', (req, res) => {
|
|
res.sendFile(path.join(__dirname, 'public', 'ethereum.html'));
|
|
});
|
|
|
|
// 查询整个网络上的区块
|
|
app.get('/allBlocks', async (req, res) => {
|
|
try {
|
|
// 从数据库获取 ballots 数据
|
|
getBallotsData((error, results) => {
|
|
if (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ success: false, error: 'Failed to retrieve data from MySQL' });
|
|
return;
|
|
}
|
|
|
|
// 数据提取成功,将结果发送给客户端
|
|
res.status(200).json({ success: true, data: results });
|
|
});
|
|
} catch (error) {
|
|
// 如果发生错误,则向客户端发送错误响应
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ success: false, error: 'Failed to retrieve data from Leveldb' });
|
|
}
|
|
});
|
|
|
|
app.get('/getBallotInfo', async (req, res) => {
|
|
try {
|
|
const contractAddress = req.query.contractAddress;
|
|
// 创建合约实例
|
|
const contractInstance = new web3.eth.Contract(contractABI, contractAddress);
|
|
|
|
// 调用合约实例的方法获取投票项目信息
|
|
const options = await contractInstance.methods.getOptions().call();
|
|
const title = await contractInstance.methods.getBallotTitle().call();
|
|
const deadlineTimestamp = await contractInstance.methods.getDeadline().call();
|
|
|
|
// 将时间戳转换为格式化的日期
|
|
const deadlineDate = new Date(Number(deadlineTimestamp));
|
|
const formattedDeadline = deadlineDate.toLocaleString();
|
|
|
|
// 返回获取到的投票项目信息,包括格式化后的截止日期
|
|
res.json({ options, title, deadline: formattedDeadline });
|
|
} catch (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ error: 'Failed to get ballot info' });
|
|
}
|
|
});
|
|
|
|
// 获取当前用户创建的智能合约
|
|
app.post('/getContracts', (req, res) => {
|
|
const userPublicKey = req.body.publicKey;
|
|
const sql = `SELECT * FROM ballots WHERE creator_address = ? AND deleted = false ORDER BY deadline DESC`;
|
|
pool.query(sql, [userPublicKey], (error, results) => {
|
|
if (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ error: 'Failed to fetch contracts' });
|
|
return;
|
|
}
|
|
res.json({ contracts: results });
|
|
});
|
|
});
|
|
|
|
// 查询某个区块的详细信息
|
|
app.post('/blockDetails', async (req, res) => {
|
|
try {
|
|
const { hash } = req.body; // 获取请求中的哈希值
|
|
// 获取区块详细信息
|
|
const blockData = await getBlockData(hash);
|
|
// 将区块详细信息返回给前端
|
|
res.status(200).json({ success: true, data: blockData });
|
|
} catch (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ success: false, error: 'Failed to fetch block details' });
|
|
}
|
|
});
|
|
|
|
app.post('/deleteContract', (req, res) => {
|
|
const contractAddress = req.body.contractAddress;
|
|
const publicKey = req.body.publicKey;
|
|
// 检查用户是否有权限删除合约,这里可以根据实际需求进行权限验证
|
|
|
|
// 更新数据库中对应合约的 deleted 字段为真
|
|
const queryString = 'UPDATE ballots SET deleted = true WHERE contract_address = ? AND creator_address = ?';
|
|
pool.query(queryString, [contractAddress, publicKey], (err, result) => {
|
|
if (err) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ success: false, message: '合约删除失败' });
|
|
return;
|
|
}
|
|
res.json({ success: true, message: '合约删除成功' });
|
|
});
|
|
});
|
|
|
|
// 获取当前用户参加过的投票项目
|
|
app.post('/getHistoryContracts', (req, res) => {
|
|
const userPublicKey = req.body.publicKey;
|
|
const sql = `SELECT * FROM history_contracts WHERE voter_address = ? ORDER BY deadline DESC`;
|
|
pool.query(sql, [userPublicKey], (error, results) => {
|
|
if (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ error: 'Failed to fetch contracts' });
|
|
return;
|
|
}
|
|
res.json({ contracts: results });
|
|
});
|
|
});
|
|
|
|
// 进行投票
|
|
app.post('/vote', async (req, res) => {
|
|
try {
|
|
const contractAddress = req.query.contractAddress;
|
|
const selectedOption = decodeURIComponent(req.query.selectedOption);
|
|
const publicKey = req.query.publicKey;
|
|
const contractInstance = new web3.eth.Contract(contractABI, contractAddress);
|
|
// 调用合约实例的方法获取投票项目信息
|
|
const deadlineTimestamp = await contractInstance.methods.getDeadline().call();
|
|
const voteTitle = await contractInstance.methods.getBallotTitle().call();
|
|
// 获取当前时间戳
|
|
const currentTimestamp = Math.floor(Date.now());
|
|
// 如果当前时间晚于投票截止日期,则投票已经截至
|
|
if (currentTimestamp >= deadlineTimestamp) {
|
|
res.json({ success: false, message: '投票已经截止' });
|
|
return;
|
|
}
|
|
// 已经投过票
|
|
const hasVoted = await contractInstance.methods.hasVotedForBallot(contractAddress).call();
|
|
if(hasVoted) {
|
|
res.json({ success: false, message: '您已经投过票了' });
|
|
return;
|
|
}
|
|
// 调用合约的投票函数
|
|
await contractInstance.methods.castVote(selectedOption).send({
|
|
from: publicKey, // 从这个地址发送交易
|
|
gas: 3000000 // 设置gas限制
|
|
});
|
|
saveBlockDataToDatabase(publicKey, contractInstance.options.address);
|
|
const deadlineData = new Date(Number(deadlineTimestamp));
|
|
await insertHistoryContract(contractAddress, publicKey, voteTitle, deadlineData, selectedOption);
|
|
// 发送响应
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ success: false, error: '投票失败' });
|
|
}
|
|
});
|
|
|
|
// 搜索合约的路由处理程序
|
|
app.post('/searchContracts', async (req, res) => {
|
|
try {
|
|
const keyword = req.body.keyword;
|
|
const userPublicKey = req.body.userPublicKey;
|
|
|
|
// 构建 SQL 查询语句
|
|
let query;
|
|
let queryParams;
|
|
if (/^0x[a-fA-F0-9]{40}$/.test(keyword)) {
|
|
// 如果关键字是合约地址,则查询指定地址的合约信息
|
|
query = 'SELECT * FROM ballots WHERE contract_address = ? AND creator_address = ?';
|
|
queryParams = [keyword, userPublicKey];
|
|
} else {
|
|
// 如果关键字不是合约地址,则执行模糊查询
|
|
query = 'SELECT * FROM ballots WHERE (contract_address LIKE ? OR vote_title LIKE ?) AND creator_address = ?';
|
|
const searchTerm = '%' + keyword + '%';
|
|
queryParams = [searchTerm, searchTerm, userPublicKey];
|
|
}
|
|
|
|
// 执行数据库查询
|
|
pool.query(query, queryParams, (error, results, fields) => {
|
|
if (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ message: '内部服务器错误' });
|
|
return;
|
|
}
|
|
if (results.length === 0) {
|
|
res.status(404).json({ message: '未找到匹配的合约或项目' });
|
|
return;
|
|
}
|
|
// 返回查询结果
|
|
res.json({ contracts: results });
|
|
});
|
|
} catch (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ message: '内部服务器错误' });
|
|
}
|
|
});
|
|
|
|
// 搜索历史合约的路由处理程序
|
|
app.post('/searchHistoryContracts', async (req, res) => {
|
|
try {
|
|
const keyword = req.body.keyword;
|
|
const userPublicKey = req.body.userPublicKey;
|
|
|
|
// 构建 SQL 查询语句
|
|
let query;
|
|
let queryParams;
|
|
if (/^0x[a-fA-F0-9]{40}$/.test(keyword)) {
|
|
// 如果关键字是合约地址,则查询指定地址的合约信息
|
|
query = 'SELECT * FROM history_contracts WHERE contract_address = ? AND voter_address = ?';
|
|
queryParams = [keyword, userPublicKey];
|
|
} else {
|
|
// 如果关键字不是合约地址,则执行模糊查询
|
|
query = 'SELECT * FROM history_contracts WHERE (contract_address LIKE ? OR vote_title LIKE ?) AND voter_address = ?';
|
|
const searchTerm = '%' + keyword + '%';
|
|
queryParams = [searchTerm, searchTerm, userPublicKey];
|
|
}
|
|
|
|
// 执行数据库查询
|
|
pool.query(query, queryParams, (error, results, fields) => {
|
|
if (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ message: '内部服务器错误' });
|
|
return;
|
|
}
|
|
if (results.length === 0) {
|
|
res.status(404).json({ message: '未找到匹配的合约或项目' });
|
|
return;
|
|
}
|
|
// 返回查询结果
|
|
res.json({ contracts: results });
|
|
});
|
|
} catch (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ message: '内部服务器错误' });
|
|
}
|
|
});
|
|
|
|
// 路由处理函数,根据合约地址返回合约详情数据
|
|
app.post('/getContractDetails', async (req, res) => {
|
|
try {
|
|
const contractAddress = req.body.contractAddress; // 从请求体中获取合约地址
|
|
// 创建合约实例
|
|
const contractInstance = new web3.eth.Contract(contractABI, contractAddress);
|
|
|
|
// 调用合约实例的方法获取投票项目信息
|
|
const options = await contractInstance.methods.getOptions().call();
|
|
const title = await contractInstance.methods.getBallotTitle().call();
|
|
const deadlineTimestamp = await contractInstance.methods.getDeadline().call();
|
|
const isOpen = await contractInstance.methods.getIsOpen().call();
|
|
// 获取每个候选项的得票数
|
|
const voteCounts = await contractInstance.methods.getVoteCounts().call();
|
|
// 将时间戳转换为格式化的日期
|
|
const deadlineDate = new Date(Number(deadlineTimestamp));
|
|
const formattedDeadline = deadlineDate.toLocaleString();
|
|
// 返回获取到的投票项目信息,包括格式化后的截止日期和每个候选项的得票数
|
|
res.json({ options, title, deadline: formattedDeadline, voteCounts });
|
|
} catch (error) {
|
|
logger.error({
|
|
errorMessage: error.message,
|
|
stackTrace: error.stack
|
|
});
|
|
res.status(500).json({ error: 'Failed to get ballot info' });
|
|
}
|
|
});
|
|
|
|
// 启动服务器
|
|
const PORT = process.env.PORT || 3000;
|
|
app.listen(PORT, () => {
|
|
logger.info('begin working!');
|
|
logger.info({
|
|
message: `Server is running on port ${PORT}`,
|
|
});
|
|
console.log(`Server is running on port ${PORT}`);
|
|
});
|