基于Java语言以及目前市面上流行的Springboot+Mybatis等框架开发的区块链投票系统,可用于企业集团、政府机构、组织联盟内的投票平台,系统分为三层架构,分别是自研区块链作为底层、投票系统作为业务层、前端操作界面作为视图层,功能包括完整的前后端权限管理模块、投票应用功能和自研区块链系统,扩展性强。系统整体采用springboot框架开发,简单易懂,包含完整的投票功能、权限管理功能、区块链模型、区块链钱包、转账和交易、UTXO模型、数据的加密解密、挖矿、区块数据的数据库持久化,以及基于netty框架的P2P分布式网络。
随着社会的进步,经济的快速发展,投票调查、意见采集在社会生活中起了越来越重要的作用。网上投票系统以高效率、节省资源、易传播等特性替代了线下投票活动。但是由于网上投票系统的数据存储在中心化服务器中,存在许多弊端。例如:
(1)用户的投票信息有被泄漏的风险;
(2)选民投票以后,普通的选民无法验证投票结果是否正确;
(3)投票数据、结果可能会被恶意篡改等
而如果投票系统利用区块链技术的去中心化、信息不可篡改、公开透明等特性进行系统建设的话,那么就可以轻松构建一套公平、公正、公开透明的投票系统。
本系统旨在通过利用区块链中的转账交易替代投票过程,以解决现有网上投票系统存在的问题。系统为每个用户和投票选项生成一个区块链账号,并且拥有代表投票凭证的token,投票过程也是投票人员把自己拥有的投票token转移给投票选项的虚拟区块链账号,最后,系统统计各投票选项拥有的token数量,把它记为投票选项所得票数,公示投票结果,同时投票结果保存在区块链网络各个节点中,无法篡改。
系统分为三层组织结构,分别是自研区块链作为底层、投票系统作为业务层、前端界面作为视图层,方便系统后台业务灵活调用底层区块链,完成投票功能的实现以及业务逻辑的上链操作。
技术 | 名称 | 官网 | 备注 |
---|---|---|---|
springboot | springboot框架 | ||
Apache Shiro | 权限框架 | ||
MyBatis Generator | 代码生成 | ||
PageHelper | MyBatis物理分页插件 | ||
hikari | 数据库连接池 | ||
Thymeleaf | 模板引擎 | ||
Log4J | 日志组件 | ||
Swagger2 | 接口测试框架 | ||
Maven | 项目构建管理 | ||
Netty | websocket消息通知 | ||
kaptcha | google验证码 | ||
devtools | 热部署 | ||
GSON | 谷歌json | ||
druid | 阿里连接池 |
技术 | 名称 | 官网 | 备注 |
---|---|---|---|
jQuery | 函式库 | ||
bootstrap | 前端页面框架 | ||
Font-awesome | 字体图标 | ||
jquery.validate | jquery验证插件 | ||
ladda.min.js | 按钮加载js | ||
bootstrap-table | 表格组件 | ||
layer.js | 弹窗组件 | ||
jquery.blockUI.js | 遮蔽层组件 | ||
bootstrap-table-export.js | 前台导出组件 | ||
bootstrap-treeview | 树结构组件 | ||
bootstrap-colorpicker | 颜色组件 | ||
dropzone | 文件上传 | ||
bootstrap-wysihtml5 | 富文本 | ||
bootstrap-switch | 开关按钮 |
JDK8:
http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html
Maven:
http://maven.apache.org/download.cgi
Redis:
https://pan.baidu.com/s/1z1_OdNVbtgyEjiktqgB83g 密码:kdfq
1. 首先先把下载的压缩包解压到一个文件夹中,例如C:\Program Files\Redis-x64-3.2.100
2. 打开cmd指令窗口,进入刚才解压的文件路径
3. 执行命令:redis-server --service-install redis.windows.conf
4. 随后在桌面,右键我的电脑→选择管理→进入服务和应用程序→服务 右键启动服务
5. 切换回cmd窗口,执行redis-cli启动客户端,连接服务进行测试
6. 执行命令:set a 123 然后再执行get a ,这时输出123
/**
* 区块
*
* @author Jared [email protected]
*/
public class Block implements Serializable {
/**
* 区块产生的时间戳
*/
private long timestamp;
/**
* 区块高度
* 冗余字段,这个值可以由区块链系统推算出来
*/
private long height;
/**
* 上一个区块的哈希
*/
private String previousBlockHash;
/**
* 区块里的交易
*/
private List<Transaction> transactions;
/**
* 默克尔树根
* 由transactions生成。
*/
private String merkleTreeRoot;
/**
* 随机数
*/
private String nonce;
/**
* 区块哈希:由timestamp、previousBlockHash、nonce、merkleRoot共同作用使用Hash算法生成。
*/
private String hash;
/**
* 挖矿难度
*/
private String difficulty;
/**
* 区块中的交易总笔数
*/
private long transactionCount;
/**
* 区块中第一笔交易的序列号。
*/
private long startTransactionIndexInBlockchain;
/**
* 省略set get方法
*/
}
/**
* 交易
*
* @author Jared [email protected]
*/
public class Transaction implements Serializable {
/**
* 交易哈希
*/
private String transactionHash;
/**
* 交易类型
*/
private TransactionType transactionType;
/**
* 交易输入
*/
private List<TransactionInput> inputs;
/**
* 交易输出
*/
private List<TransactionOutput> outputs;
/**
* 交易在区块中的序列号
*/
private long transactionIndexInBlock;
/**
* 交易在区块链中的序列号
*/
private long transactionIndexInBlockchain;
/**
* 交易所在区块的区块高度
*/
private long blockHeight;
}
public BuildTransactionResponse buildTransactionDTO(List<String> payerPrivateKeyList,String payerChangeAddress,List<Recipient> recipientList) {
//需要转移的token总的数量
long outputValues = 0;
if(recipientList != null){
for(Recipient recipient : recipientList){
outputValues += recipient.getValue();
}
}
//创建交易输出
List<TransactionOutputDTO> transactionOutputDtoList = new ArrayList<>();
//创建交易输出(不包含找零交易输出)
List<BuildTransactionResponse.InnerTransactionOutput> innerTransactionOutputList = new ArrayList<>();
if(recipientList != null){
for(Recipient recipient : recipientList){
TransactionOutputDTO transactionOutputDTO = new TransactionOutputDTO();
transactionOutputDTO.setValue(recipient.getValue());
OutputScript outputScript = StackBasedVirtualMachine.createPayToPublicKeyHashOutputScript(recipient.getAddress());
transactionOutputDTO.setOutputScriptDTO(Model2DtoTool.outputScript2OutputScriptDTO(outputScript));
transactionOutputDtoList.add(transactionOutputDTO);
BuildTransactionResponse.InnerTransactionOutput innerTransactionOutput = new BuildTransactionResponse.InnerTransactionOutput();
innerTransactionOutput.setAddress(recipient.getAddress());
innerTransactionOutput.setValue(recipient.getValue());
innerTransactionOutput.setOutputScript(outputScript);
innerTransactionOutputList.add(innerTransactionOutput);
}
}
//获取足够的token数量
//交易输入列表
List<TransactionOutput> inputs = new ArrayList<>();
List<String> inputPrivateKeyList = new ArrayList<>();
//交易输入总token数量
long inputValues = 0;
long feeValues = 0;
boolean haveEnoughMoneyToPay = false;
for(String privateKey : payerPrivateKeyList){
if(haveEnoughMoneyToPay){
break;
}
String address = AccountUtil.accountFromPrivateKey(privateKey).getAddress();
long from = 0;
long size = 100;
while (true){
if(haveEnoughMoneyToPay){
break;
}
//查询当前投票方地址下token数量
List<TransactionOutput> utxoList = blockchainDataBase.queryUnspendTransactionOutputListByAddress(address,from,size);
if(utxoList == null || utxoList.isEmpty()){
break;
}
for(TransactionOutput transactionOutput:utxoList){
inputValues += transactionOutput.getValue();
//交易输入
inputs.add(transactionOutput);
inputPrivateKeyList.add(privateKey);
if(inputValues >= outputValues){
haveEnoughMoneyToPay = true;
break;
}
}
from += size;
}
}
if(!haveEnoughMoneyToPay){
BuildTransactionResponse buildTransactionResponse = new BuildTransactionResponse();
buildTransactionResponse.setBuildTransactionSuccess(false);
buildTransactionResponse.setMessage("账户没有足够的token进行转移");
return buildTransactionResponse;
}
//构建交易输入
List<TransactionInputDTO> transactionInputDtoList = new ArrayList<>();
for(TransactionOutput input:inputs){
UnspendTransactionOutputDTO unspendTransactionOutputDto = Model2DtoTool.transactionOutput2UnspendTransactionOutputDto(input);
TransactionInputDTO transactionInputDTO = new TransactionInputDTO();
transactionInputDTO.setUnspendTransactionOutputDTO(unspendTransactionOutputDto);
transactionInputDtoList.add(transactionInputDTO);
}
//构建交易
TransactionDTO transactionDTO = new TransactionDTO();
transactionDTO.setTransactionInputDtoList(transactionInputDtoList);
transactionDTO.setTransactionOutputDtoList(transactionOutputDtoList);
//找零
long change = inputValues - outputValues - feeValues;
BuildTransactionResponse.InnerTransactionOutput payerChange = null;
if(change > 0){
TransactionOutputDTO transactionOutputDTO = new TransactionOutputDTO();
transactionOutputDTO.setValue(change);
OutputScript outputScript = StackBasedVirtualMachine.createPayToPublicKeyHashOutputScript(payerChangeAddress);
transactionOutputDTO.setOutputScriptDTO(Model2DtoTool.outputScript2OutputScriptDTO(outputScript));
transactionOutputDtoList.add(transactionOutputDTO);
payerChange = new BuildTransactionResponse.InnerTransactionOutput();
payerChange.setAddress(payerChangeAddress);
payerChange.setValue(change);
payerChange.setOutputScript(outputScript);
}
//签名
for(int i=0;i<transactionInputDtoList.size();i++){
String privateKey = inputPrivateKeyList.get(i);
String publicKey = AccountUtil.accountFromPrivateKey(privateKey).getPublicKey();
TransactionInputDTO transactionInputDTO = transactionInputDtoList.get(i);
String signature = Model2DtoTool.signature(transactionDTO,privateKey);
InputScript inputScript = StackBasedVirtualMachine.createPayToPublicKeyHashInputScript(signature, publicKey);
transactionInputDTO.setInputScriptDTO(Model2DtoTool.inputScript2InputScriptDTO(inputScript));
}
BuildTransactionResponse buildTransactionResponse = new BuildTransactionResponse();
buildTransactionResponse.setBuildTransactionSuccess(true);
buildTransactionResponse.setMessage("构建交易成功");
buildTransactionResponse.setTransactionHash(TransactionTool.calculateTransactionHash(transactionDTO));
buildTransactionResponse.setFee(feeValues);
buildTransactionResponse.setPayerChange(payerChange);
buildTransactionResponse.setTransactionInputList(inputs);
buildTransactionResponse.setTransactionOutputList(innerTransactionOutputList);
buildTransactionResponse.setTransactionDTO(transactionDTO);
return buildTransactionResponse;
}
如果本地电脑算力太差,可以更改算法的逻辑,只要区块的哈希值前四位都为0即认为满足计算的结果:
public boolean isReachConsensus(BlockchainDatabase blockchainDataBase, Block block) {
String difficulty = block.getDifficulty();
if(StringUtil.isNullOrEmpty(difficulty)){
difficulty = calculateDifficult(blockchainDataBase,block);
block.setDifficulty(difficulty);
}
//区块Hash
String hash = block.getHash();
if(hash == null){
hash = BlockTool.calculateBlockHash(block);
}
System.out.println("正在挖矿中,区块hash:"+hash);
return new BigInteger(difficulty,16).compareTo(new BigInteger(hash,16)) > 0;
//return hash.startsWith("0000");
}
public String calculateDifficult(BlockchainDatabase blockchainDataBase, Block block) {
long intervalBlockCount = GlobalSetting.MinerConstant.INTERVAL_TIME / GlobalSetting.MinerConstant.BLOCK_TIME;
String targetDifficult;
long blockHeight = block.getHeight();
if(blockHeight == 1){
targetDifficult = GlobalSetting.GenesisBlock.DIFFICULTY;
return targetDifficult;
}
Block lastBlock = blockchainDataBase.queryBlockByBlockHeight(blockHeight-1);
long lastBlockHeight = lastBlock.getHeight();
if (lastBlockHeight % intervalBlockCount != 0){
targetDifficult = lastBlock.getDifficulty();
return targetDifficult;
}
//此时,最后一个区块是上一个周期的最后一个区块。
Block intervalLastBlock = lastBlock;
Block intervalFirstBlock = blockchainDataBase.queryBlockByBlockHeight(lastBlockHeight-intervalBlockCount+1);
long actualTimespan = intervalLastBlock.getTimestamp() - intervalFirstBlock.getTimestamp();
if (actualTimespan < GlobalSetting.MinerConstant.INTERVAL_TIME /4){
actualTimespan = GlobalSetting.MinerConstant.INTERVAL_TIME /4;
}
if (actualTimespan > GlobalSetting.MinerConstant.INTERVAL_TIME *4){
actualTimespan = GlobalSetting.MinerConstant.INTERVAL_TIME *4;
}
BigInteger bigIntegerTargetDifficult = new BigInteger(intervalLastBlock.getDifficulty(),16).multiply(new BigInteger(String.valueOf(actualTimespan))).divide(new BigInteger(String.valueOf(GlobalSetting.MinerConstant.INTERVAL_TIME)));
return bigIntegerTargetDifficult.toString(16);
}
用来管理区块链平台拥有的区块链账号,并进行增加账号、删除账号、查询账号等操作:
public abstract class Wallet {
public abstract List<Account> queryAllAccount();
public abstract Account createAccount();
public abstract void addAccount(Account account);
public abstract void deleteAccountByAddress(String address);
}
(1)Java区块链技术开发实战入门