参考文章:https://hackernoon.com/learn-blockchains-by-building-one-117428612f46
原文使用python实现,这里使用java
首先,使用springboot作为容器,打开https://start.spring.io/,输入各项参数,生成项目下载,如下图
因为需要通过web接口接收请求,所以这里需要选择spring web starter
导入项目后,在pom中添加json依赖,以便于后继调试
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.28</version>
</dependency>
以下是完整的包类路径
bo包是业务对象,里面有3个对象,分别是chain--链,block--块,transaction--交易,这三者也是区块链中最基础的业务模型
utils包是工具类包,实现hash算法,用于实现挖矿算法的原型,以及时间戳生成
web包是web接口包,提供了服务与外部之间的交互,按原文,实现了挖矿、新交易、展示区块链内容,
接下来展开各个类来详细介绍如何实现
Transaction.java
package com.zibra.chain.bo;
public class Transaction {
/*
* 交易发起人
*/
private String sender;
/*
* 交易接收人
*/
private String recipient;
/*
* 数额
*/
private String amount;
public Transaction(String sender, String recipient, String amount) {
this.sender = sender;
this.recipient = recipient;
this.amount = amount;
}
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 String getAmount() {
return amount;
}
public void setAmount(String amount) {
this.amount = amount;
}
}
交易对象里面包含3个属性,发起人、接收人、金额(比特币个数),用以记录每次交易的细节
Block.java
package com.zibra.chain.bo;
import java.util.ArrayList;
import java.util.List;
/**
* 块
*/
public class Block {
/*
* 索引
*/
private Integer index;
/*
* 创建时间戳
*/
private String timestamp;
/*
* 交易列表
*/
private List<Transaction> transactions = new ArrayList<Transaction>();
/*
* 证明
*/
private int proof;
/*
* 来源hash
*/
private int previous_hash;
public Block() {
}
public Integer getIndex() {
return index;
}
public void setIndex(Integer index) {
this.index = index;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public List<Transaction> getTransactions() {
return transactions;
}
public void setTransactions(List<Transaction> transactions) {
this.transactions = transactions;
}
public int getProof() {
return proof;
}
public void setProof(int proof) {
this.proof = proof;
}
public int getPrevious_hash() {
return previous_hash;
}
public void setPrevious_hash(int previous_hash) {
this.previous_hash = previous_hash;
}
}
块对象也就是区块链中的基础数据单元,形式很像传统关系型数据库中的表,包含了head字段如索引、时间戳、证明、区块链中上一块的hash值,以及截至到下个新块诞生为止的所有交易记录。
块对象是区块链实现不可篡改的核心对象,通过登记上一块的hash值与当前块的proof形成了完整的证据链。
Chain.java
package com.zibra.chain.bo;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import com.zibra.chain.utils.CommonUtil;
/**
* 区块链原型
*/
public class Chain {
/*
* 节点唯一标识
*/
private static String NODE_ID;
/*
* 区块链
*/
public static List<Block> chain = new ArrayList<Block>();
/*
* 当前交易
*/
public static List<Transaction> current_transactions = new ArrayList<Transaction>();
/**
* 初始化
*/
public Chain() {
newBlock(100, 1);
}
/**
* 获取节点ID 后继可以考虑持久化
* @return
*/
public static String getNodeId() {
if (null == NODE_ID) {
NODE_ID = UUID.randomUUID().toString();
}
return NODE_ID;
}
/**
* 创建一个新的块,并添加到链中
*
* @return
*/
public static Block newBlock(int proof, int previous_hash) {
Block block = new Block();
block.setIndex(chain.size() + 1);
block.setTimestamp(CommonUtil.getTimesteap());
block.setPrevious_hash(previous_hash);
block.setProof(proof);
block.setTransactions(new ArrayList<Transaction>(current_transactions));
chain.add(block);
return block;
}
/**
* 添加一笔新的交易到transactions中
*
* @param sender 发送者地址
* @param recipient 接收者地址
* @param amount amount 数量
* @return 包含该交易记录的块的索引
*/
public static int newTransaction(Transaction transaction) {
current_transactions.add(transaction);
return lastBlock().getIndex() + 1;
}
/**
* 生成块的hash
*
* @param block
* @return
*/
public static int hash(Block block) {
return block.hashCode();
}
/**
* 返回链中的最后一个块
*
* @return
*/
public static Block lastBlock() {
return chain.get(chain.size() > 0 ? chain.size() - 1 : 0);
}
/**
* 工作量证明
*
* @param last_proof
*/
public static Integer proofOfWork(int last_proof) {
int proof = 0;
if (validProof(last_proof, proof)) {
proof++;
}
return proof;
}
/**
* 验证算法
*
* @param last_proof
* @param proof
* @return
*/
public static boolean validProof(int last_proof, int proof) {
return CommonUtil.getHash((String.valueOf(last_proof) + String.valueOf(proof))).startsWith("00");
}
}
链对象中包含两个链表,一个记录了所有按先后顺序生成的块,另一个记录了所有交易的列表。
这里还实现了一些区块链核心功能,如创建新块、块的hash值计算、挖矿算法(proof of work)、验证hash值(传说中0越多难度越高的控制器)。
原文中为了阐述原理,略去了为了控制比特币奖励、按时间提高挖矿难度的导致程序可读性下降的逻辑,但如果了解原理,还是很容易在代码上实现的。
关于比特币,大家请自行查看资料。
CommonUtil.java
package com.zibra.chain.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class CommonUtil {
/**
* 获取hash值
* @param source
* @return
*/
public static String getHash(String source) {
return getHash2(source, "MD5");
}
/**
* 获取hash值
* @param source
* @param hashType
* @return
*/
public static String getHash2(String source, String hashType) {
StringBuilder sb = new StringBuilder();
MessageDigest md5;
try {
md5 = MessageDigest.getInstance(hashType);
md5.update(source.getBytes());
for (byte b : md5.digest()) {
sb.append(String.format("%02X", b));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
/**
* 获取时间戳
* @return
*/
public static String getTimesteap() {
return new SimpleDateFormat("yyyy/MM/dd HH:mm:ss").format(new Date());
}
}
工具类中使用java的md5进行hash计算,与时间戳
BlockChainController.java
package com.zibra.chain.web;
import java.util.HashMap;
import java.util.Map;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.zibra.chain.bo.Block;
import com.zibra.chain.bo.Chain;
import com.zibra.chain.bo.Transaction;
/**
* 入口
*/
@RestController
public class BlockChainController {
/**
* 挖矿
*
* @return
*/
@PostMapping(value = "/mine")
public Map<String, Object> mine() {
//运行工作量证明算法,获取下一个proof
Block block = Chain.lastBlock();
int last_proof = block.getProof();
int proof = Chain.proofOfWork(last_proof);
//获取奖励,发送者为"0", 表明是该节点挖出来的新币
Chain.newTransaction(new Transaction("0", Chain.getNodeId(), "1"));
//创建新的区块,并添加到链中
int previous_hash = Chain.hash(block);
block = Chain.newBlock(proof, previous_hash);
//回复挖矿结果
Map<String, Object> result = new HashMap<String, Object>();
result.put("message", "New Block Forged");
result.put("index", block.getIndex());
result.put("transactions", JSON.toJSONString(block.getTransactions(), SerializerFeature.PrettyFormat));
result.put("proof", block.getProof());
result.put("previous_hash", block.getPrevious_hash());
return result;
}
/**
* 添加交易
*
* @param transaction
* @return
*/
@PostMapping(value = "/transactions/new")
public String newTransaction(Transaction transaction) {
if (null == transaction.getSender() || null == transaction.getSender() || null == transaction.getSender()) {
return "error/404";
}
int index = Chain.newTransaction(transaction);
return "添加新交易" + index;
}
/**
* 显示内容
*
* @return
*/
@PostMapping(value = "/chain")
public String chain() {
return JSON.toJSONString(Chain.chain, SerializerFeature.PrettyFormat);
}
}
web入口实现了挖矿,添加交易,查看区块链全体内容三个方法,可以通过postman调用
可以按如下步骤执行,先查看区块链内容,里面只有一个初始块
执行一个挖矿动作,会返回一个新块内容
执行一次新增交易,可以看到提交成功
查看区块链内容
可见交易已经成功添加到块中
以此类推,添加块,交易,就能看到交易信息被记录到各个块中了