前几天心血来潮,用Java模拟实现了一个区块链小工具,该工具是单机版的,没有实现联网功能,属于是单节点的工具,但是对于区块链的核心wk逻辑功能都实现了,如:wk、钱包、转账、记账等功能。界面实现用到了Java的swing包。
下面先看下运行的效果:
主界面:
转账:
下面来看看代码:
首先是界面:
package com.yuanlrc.app;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
import javax.swing.border.TitledBorder;
import com.yuanlrc.model.Block;
import com.yuanlrc.model.Transaction;
import com.yuanlrc.thread.MinBlockThread;
import com.yuanlrc.util.CryptoUtil;
public class App extends JFrame {
/**
*
*/
private static final long serialVersionUID = 1L;
private JPanel contentPane;
private JTextField recieverAddressTextField;
private JTextArea showRstTextArea;
private String address;//当前钱包地址
private List blockChain;//区块链
private MinBlockThread minBlockThread;
private List txs = new ArrayList<>();
public JProgressBar currentStatusProgressBar;
private JTextField amountTextField;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
App frame = new App();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public App() {
try {
UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedLookAndFeelException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
address = UUID.randomUUID().toString();
setTitle("【\u733F\u5E01\u6316\u77FF\u5DE5\u5177】钱包地址:" + address +"【当前余额:0】" );
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 717, 571);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
JPanel panel = new JPanel();
panel.setBorder(new TitledBorder(null, "\u524D\u7F6E\u52A8\u4F5C", TitledBorder.LEADING, TitledBorder.TOP, null, null));
JPanel panel_1 = new JPanel();
panel_1.setBorder(new TitledBorder(null, "\u8F6C\u8D26\u64CD\u4F5C", TitledBorder.LEADING, TitledBorder.TOP, null, null));
JPanel panel_2 = new JPanel();
panel_2.setBorder(new TitledBorder(null, "\u6316\u77FF\u7ED3\u679C", TitledBorder.LEADING, TitledBorder.TOP, null, null));
JPanel panel_3 = new JPanel();
panel_3.setBorder(new TitledBorder(null, "\u5F53\u524D\u72B6\u6001", TitledBorder.LEADING, TitledBorder.TOP, null, null));
GroupLayout gl_contentPane = new GroupLayout(contentPane);
gl_contentPane.setHorizontalGroup(
gl_contentPane.createParallelGroup(Alignment.LEADING)
.addGroup(Alignment.TRAILING, gl_contentPane.createSequentialGroup()
.addGap(18)
.addGroup(gl_contentPane.createParallelGroup(Alignment.LEADING, false)
.addComponent(panel_2, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(panel_3, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(panel_1, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
.addComponent(panel, GroupLayout.DEFAULT_SIZE, 651, Short.MAX_VALUE))
.addGap(22))
);
gl_contentPane.setVerticalGroup(
gl_contentPane.createParallelGroup(Alignment.LEADING)
.addGroup(gl_contentPane.createSequentialGroup()
.addContainerGap()
.addComponent(panel, GroupLayout.PREFERRED_SIZE, 77, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(panel_1, GroupLayout.PREFERRED_SIZE, 73, GroupLayout.PREFERRED_SIZE)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(panel_3, GroupLayout.DEFAULT_SIZE, 41, Short.MAX_VALUE)
.addPreferredGap(ComponentPlacement.UNRELATED)
.addComponent(panel_2, GroupLayout.PREFERRED_SIZE, 300, GroupLayout.PREFERRED_SIZE))
);
currentStatusProgressBar = new JProgressBar();
currentStatusProgressBar.setForeground(Color.GREEN);
currentStatusProgressBar.setBackground(UIManager.getColor("window"));
GroupLayout gl_panel_3 = new GroupLayout(panel_3);
gl_panel_3.setHorizontalGroup(
gl_panel_3.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_3.createSequentialGroup()
.addContainerGap()
.addComponent(currentStatusProgressBar, GroupLayout.DEFAULT_SIZE, 619, Short.MAX_VALUE)
.addContainerGap())
);
gl_panel_3.setVerticalGroup(
gl_panel_3.createParallelGroup(Alignment.LEADING)
.addComponent(currentStatusProgressBar, GroupLayout.DEFAULT_SIZE, 24, Short.MAX_VALUE)
);
panel_3.setLayout(gl_panel_3);
JScrollPane scrollPane = new JScrollPane();
GroupLayout gl_panel_2 = new GroupLayout(panel_2);
gl_panel_2.setHorizontalGroup(
gl_panel_2.createParallelGroup(Alignment.LEADING)
.addComponent(scrollPane, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, 639, Short.MAX_VALUE)
);
gl_panel_2.setVerticalGroup(
gl_panel_2.createParallelGroup(Alignment.LEADING)
.addComponent(scrollPane, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, 276, Short.MAX_VALUE)
);
showRstTextArea = new JTextArea();
showRstTextArea.setForeground(Color.GREEN);
showRstTextArea.setFont(UIManager.getFont("TitledBorder.font"));
showRstTextArea.setBackground(Color.BLACK);
scrollPane.setViewportView(showRstTextArea);
panel_2.setLayout(gl_panel_2);
JLabel label = new JLabel("\u8F6C\u8D26\u5730\u5740");
recieverAddressTextField = new JTextField();
recieverAddressTextField.setColumns(10);
JButton submitTransButton = new JButton("\u786E\u5B9A\u8F6C\u8D26");
submitTransButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
//转账
String recieverAddress = recieverAddressTextField.getText();
if(recieverAddress == null || "".equals(recieverAddress)){
JOptionPane.showMessageDialog(App.this, "请输入转账地址!");
return;
}
String amount = amountTextField.getText();
if(amount == null || "".equals(amount)){
JOptionPane.showMessageDialog(App.this, "请输入转账金额!");
return;
}
try {
double a = Double.valueOf(amount);
double walletBalance = getWalletBalance(blockChain, address);
if(walletBalance < a){
JOptionPane.showMessageDialog(App.this, "您的余额不足!");
return;
}
int showConfirmDialog = JOptionPane.showConfirmDialog(App.this, "确定转账?");
if(showConfirmDialog == JOptionPane.OK_OPTION){
Transaction tx = new Transaction(CryptoUtil.UUID(), address, recieverAddress, a);
txs.add(tx);
String title = getTitle();
title = title.substring(0,title.lastIndexOf("【"));
walletBalance = getWalletBalance(blockChain, address);
title += "【当前余额:" + walletBalance + "】";
setTitle(title);
JOptionPane.showMessageDialog(App.this, "转账成功!");
showRstTextArea.setText(showRstTextArea.getText() + "\n成功转账:" +a+ "个猿币,对方账户【"+recieverAddress+"】,当前猿币余额:" + walletBalance);
}
} catch (Exception e) {
JOptionPane.showMessageDialog(App.this, "请输入正确的数字类型!");
return;
}
}
});
JLabel label_1 = new JLabel("\u8F6C\u8D26\u91D1\u989D");
amountTextField = new JTextField();
amountTextField.setColumns(10);
GroupLayout gl_panel_1 = new GroupLayout(panel_1);
gl_panel_1.setHorizontalGroup(
gl_panel_1.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_1.createSequentialGroup()
.addContainerGap()
.addComponent(label)
.addGap(18)
.addComponent(recieverAddressTextField, GroupLayout.DEFAULT_SIZE, 192, Short.MAX_VALUE)
.addGap(18)
.addComponent(label_1)
.addGap(27)
.addComponent(amountTextField, GroupLayout.PREFERRED_SIZE, 122, GroupLayout.PREFERRED_SIZE)
.addGap(46)
.addComponent(submitTransButton)
.addGap(29))
);
gl_panel_1.setVerticalGroup(
gl_panel_1.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel_1.createSequentialGroup()
.addContainerGap()
.addGroup(gl_panel_1.createParallelGroup(Alignment.BASELINE)
.addComponent(recieverAddressTextField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addComponent(label)
.addComponent(submitTransButton)
.addComponent(amountTextField, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
.addComponent(label_1))
.addContainerGap(16, Short.MAX_VALUE))
);
panel_1.setLayout(gl_panel_1);
JButton createInitBlockChainButton = new JButton("\u521B\u5EFA\u521D\u59CB\u533A\u5757\u94FE");
createInitBlockChainButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
//初始化区块链
if(blockChain != null){
JOptionPane.showMessageDialog(App.this, "请勿重复初始化!");
return;
}
blockChain = new ArrayList();
showRstTextArea.setText("初始化完成!");
}
});
createInitBlockChainButton.setFont(new Font("黑体", Font.PLAIN, 14));
JButton createFirstBlockButton = new JButton("\u751F\u6210\u521B\u4E16\u533A\u5757");
createFirstBlockButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
//生成创世区块
if(blockChain == null){
JOptionPane.showMessageDialog(App.this, "请先初始化创建区块链!");
return;
}
if(blockChain.size() > 0){
JOptionPane.showMessageDialog(App.this, "创世区块已生成,请勿重复创建!");
return;
}
String hash = UUID.randomUUID().toString();
Block block = new Block(1, System.currentTimeMillis(), txs, 0, hash, hash);
showRstTextArea.setText(showRstTextArea.getText() + "\n创世区块已建立!");
blockChain.add(block);
showRstTextArea.setText(showRstTextArea.getText() + "\n创世区块已加入到区块链!");
}
});
createFirstBlockButton.setFont(new Font("黑体", Font.PLAIN, 14));
JButton startMineCoinButton = new JButton("\u5F00\u59CB\u6316\u733F\u5E01");
startMineCoinButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
//开始挖矿
if("停止挖矿".equals(startMineCoinButton.getText())){
stopMinBlock();
startMineCoinButton.setText("开始挖矿");
return;
}
if(blockChain == null){
JOptionPane.showMessageDialog(App.this, "请先初始化创建区块链!");
return;
}
if(blockChain.size() == 0){
JOptionPane.showMessageDialog(App.this, "请先生成创世区块!");
return;
}
startMineBlock();
startMineCoinButton.setText("停止挖矿");
}
});
startMineCoinButton.setFont(new Font("黑体", Font.PLAIN, 14));
GroupLayout gl_panel = new GroupLayout(panel);
gl_panel.setHorizontalGroup(
gl_panel.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel.createSequentialGroup()
.addGap(45)
.addComponent(createInitBlockChainButton)
.addPreferredGap(ComponentPlacement.RELATED, 91, Short.MAX_VALUE)
.addComponent(createFirstBlockButton)
.addGap(83)
.addComponent(startMineCoinButton)
.addGap(69))
);
gl_panel.setVerticalGroup(
gl_panel.createParallelGroup(Alignment.LEADING)
.addGroup(gl_panel.createSequentialGroup()
.addGroup(gl_panel.createParallelGroup(Alignment.BASELINE)
.addComponent(startMineCoinButton, GroupLayout.PREFERRED_SIZE, 40, GroupLayout.PREFERRED_SIZE)
.addComponent(createInitBlockChainButton, GroupLayout.PREFERRED_SIZE, 39, GroupLayout.PREFERRED_SIZE)
.addComponent(createFirstBlockButton, GroupLayout.PREFERRED_SIZE, 41, GroupLayout.PREFERRED_SIZE))
.addContainerGap(3, Short.MAX_VALUE))
);
panel.setLayout(gl_panel);
contentPane.setLayout(gl_contentPane);
}
protected void stopMinBlock() {
// TODO Auto-generated method stub
minBlockThread.interrupt();
currentStatusProgressBar.setValue(100);
}
/**
* 挖矿
* @param blockchain 整个区块链
* @param txs 需记账交易记录,包含
* @return
*/
private void startMineBlock() {
minBlockThread = new MinBlockThread(showRstTextArea, blockChain, txs, this, address);
minBlockThread.start();
currentStatusProgressBar.setValue(0);
}
/**
* 查询余额
* @param blockchain
* @param address
* @return
*/
public double getWalletBalance(List blockchain, String address) {
double balance = 0;
for (Block block : blockchain) {
List transactions = block.getTransactions();
for (Transaction transaction : transactions) {
if (address.equals(transaction.getRecipient())) {
balance += transaction.getAmount();
}
if (address.equals(transaction.getSender())) {
balance -= transaction.getAmount();
}
}
break;
}
return balance;
}
}
实体类:
package com.yuanlrc.model;
import java.util.List;
/**
* 区块
* @author llq
*
*/
public class Block {
/**
* 区块索引号
*/
private int index;
/**
* 当前区块的hash值,区块唯一标识
*/
private String hash;
/**
* 生成区块的时间戳
*/
private long timestamp;
/**
* 当前区块的交易集合
*/
private List transactions;
/**
* 工作量证明,计算正确hash值的次数
*/
private int nonce;
/**
* 前一个区块的hash值
*/
private String previousHash;
public Block(int index, long timestamp, List transactions, int nonce, String previousHash, String hash) {
super();
this.index = index;
this.timestamp = timestamp;
this.transactions = transactions;
this.nonce = nonce;
this.previousHash = previousHash;
this.hash = hash;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getHash() {
return hash;
}
public void setHash(String hash) {
this.hash = hash;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
public List getTransactions() {
return transactions;
}
public void setTransactions(List transactions) {
this.transactions = transactions;
}
public int getNonce() {
return nonce;
}
public void setNonce(int nonce) {
this.nonce = nonce;
}
public String getPreviousHash() {
return previousHash;
}
public void setPreviousHash(String previousHash) {
this.previousHash = previousHash;
}
}
package com.yuanlrc.model;
/**
* 交易集合
* @author llq
*
*/
public class Transaction {
/**
* 交易唯一标识
*/
private String id;
/**
* 交易发送方
*/
private String sender;
/**
* 交易接收方
*/
private String recipient;
/**
* 交易金额
*/
private double amount;
public Transaction(String id, String sender, String recipient, double amount) {
super();
this.id = id;
this.sender = sender;
this.recipient = recipient;
this.amount = amount;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getRecipient() {
return recipient;
}
public void setRecipient(String recipient) {
this.recipient = recipient;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Transaction other = (Transaction) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
加密类:
package com.yuanlrc.util;
import java.security.MessageDigest;
import java.util.UUID;
/**
* 加密类
*
* @author aaron.rao
*
*/
public class CryptoUtil {
private CryptoUtil() {
}
public static String SHA256(String str) {
MessageDigest messageDigest;
String encodeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str.getBytes("UTF-8"));
encodeStr = byte2Hex(messageDigest.digest());
} catch (Exception e) {
System.out.println("getSHA256 is error" + e.getMessage());
}
return encodeStr;
}
public static String UUID() {
return UUID.randomUUID().toString().replaceAll("\\-", "");
}
private static String byte2Hex(byte[] bytes) {
StringBuilder builder = new StringBuilder();
String temp;
for (int i = 0; i < bytes.length; i++) {
temp = Integer.toHexString(bytes[i] & 0xFF);
if (temp.length() == 1) {
builder.append("0");
}
builder.append(temp);
}
return builder.toString();
}
}
解谜类:
package com.yuanlrc.util;
import java.security.Key;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
public class DESUtil {
/**
* 偏移变量,固定占8位字节
*/
private final static String IV_PARAMETER = "12345678";
/**
* 密钥算法
*/
private static final String ALGORITHM = "DES";
/**
* 加密/解密算法-工作模式-填充模式
*/
private static final String CIPHER_ALGORITHM = "DES/CBC/PKCS5Padding";
/**
* 默认编码
*/
private static final String CHARSET = "UTF-8";
/**
* 计算加密key
* @param password
* @return
* @throws Exception
*/
private static Key generateKey(String password) throws Exception {
DESKeySpec dks = new DESKeySpec(password.getBytes(CHARSET));
SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
return keyFactory.generateSecret(dks);
}
/**
* 计算加密后的值
* @param key
* @param data
* @return
*/
public static String encrypt(String key, String data) {
if (key== null || key.length() < 8) {
throw new RuntimeException("加密失败,key不能小于8位");
}
if (data == null)
return null;
try {
Key secretKey = generateKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET));
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
byte[] bytes = cipher.doFinal(data.getBytes(CHARSET));
//JDK1.8及以上可直接使用Base64,JDK1.7及以下可以使用BASE64Encoder
//Android平台可以使用android.util.Base64
return new String(Base64.getEncoder().encode(bytes));
} catch (Exception e) {
e.printStackTrace();
return data;
}
}
/**
* DES解密字符串
*
* @param key 解密密码,长度不能够小于8位
* @param data 待解密字符串
* @return 解密后内容
*/
public static String decrypt(String key, String data) {
if (key== null || key.length() < 8) {
throw new RuntimeException("解密失败,key不能小于8位");
}
if (data == null)
return null;
try {
Key secretKey = generateKey(key);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
IvParameterSpec iv = new IvParameterSpec(IV_PARAMETER.getBytes(CHARSET));
cipher.init(Cipher.DECRYPT_MODE, secretKey, iv);
return new String(cipher.doFinal(Base64.getDecoder().decode(data.getBytes(CHARSET))), CHARSET);
} catch (Exception e) {
e.printStackTrace();
return data;
}
}
public static void main(String[] args){
System.out.println(encrypt("muyi_ylrc", System.currentTimeMillis()+""));
System.out.println(decrypt("muyi_ylrc",encrypt("muyi_ylrc", System.currentTimeMillis()+"")));
System.out.println(encrypt("muyi_ylrc", "159083378454884859#17347843162"));
System.out.println(decrypt("muyi_ylrc","snSZdXC3NRZgoPaigiNyvm0A+Piwa09LDwqhvFZwalU="));
}
}
点击查看