Java实现简单区块链
参考地址:Creating Your First Blockchain with Java
准备
开发环境
- java1.8~
- maven
- 任选IDE
区块链概述
顾名思义,区块链就是很多“区块”形成的“链”。
每个“区块”上包含的数据有:
- 它自身的数字指纹(digital fingerprint)
- 上一个区块的数字指纹
- 一些额外信息,如交易信息(tansaction infomation)等等
数字指纹一般是一个哈希值
当前区块的数字指纹计算方式:会根据上一个区块的指纹,以及当前区块的信息来计算
故当前区块的信息发生改变,影响这条链上之后的所有区块的数字指纹
所以,区块链的一般结构为:
编码
区块Block
import java.util.Date;
public class Block {
public String hash;
public String previousHash;
private String data;
private long timeStamp;
public Block(String data,String previousHash ) {
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
}
}
“数字指纹”生成
采用sha256哈希算法生成哈希值(String表示)
import java.security.MessageDigest;
public class StringUtil {
/**
* 对输入input使用sha256算法进行哈希,
* 返回哈希值的16进制字符串
* @param input
* @return
*/
public static String applySha256(String input) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(input.getBytes("UTF-8"));
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]);
if (hex.length() == 1)
hexString.append('0');
hexString.append(hex);
}
return hexString.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
接下来可以修改Block类,增加一个生成数字指纹的方法
在Block类的构造函数中,给变量hash赋初值
public class Block {
// ... 省略成员变量
public Block(String data, String previousHash) {
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
this.hash = calculateHash(); // 给hash赋初值
}
public String calculateHash() {
String calculatedhash = StringUtil.applySha256(
previousHash + // 上一区块的数字指纹
Long.toString(timeStamp) + // 当前区块的时间戳
data); // 当前区块的额外信息
return calculatedhash;
}
}
区块链NoobChain
Noob是“菜鸟,新手”的意思。
新建NoobChain类
先把主函数放入这个类中,进行测试:
创世区块是指区块链的第一个区块
创世区块没有上一个区块,所以把该区块的“上一个区块数字指纹”设为0
有了创世区块,就可以在这条区块链上继续新增区块
public class NoobChain {
// 测试
public static void main(String[] args) {
// 创世区块
Block genesisBlock = new Block("Hi im the first block", "0");
System.out.println("Hash for block 1 : " + genesisBlock.hash);
Block secondBlock = new Block("Yo im the second block", genesisBlock.hash);
System.out.println("Hash for block 2 : " + secondBlock.hash);
Block thirdBlock = new Block("Hey im the third block", secondBlock.hash);
System.out.println("Hash for block 3 : " + thirdBlock.hash);
}
}
输出为:
修改pom.xml文件,添加Gson依赖
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>
修改NoobChain类,用链表ArrayList保存区块
public class NoobChain {
public static ArrayList<Block> blockchain = new ArrayList<Block>();
//测试
public static void main(String[] args) {
// 往区块链上添加区块:
blockchain.add(new Block("Hi im the first block", "0"));
blockchain.add(new Block("Yo im the second block", blockchain.get(blockchain.size() - 1).hash));
blockchain.add(new Block("Hey im the third block", blockchain.get(blockchain.size() - 1).hash));
// 按json格式输出
String blockchainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockchain);
System.out.println(blockchainJson);
}
}
区块链完整性校验
之前提到,在一个区块链上,
如果当前区块的信息发生变化,
后序所有区块的数字指纹都要重新计算,
所以需要校验是否所有区块都满足前后依赖关系。
这里在NoobChain类中设计一个实例函数isChainValid()
public class NoobChain {
// 区块链
public static ArrayList<Block> blockchain = new ArrayList<Block>();
// 区块链完整性校验
public static Boolean isChainValid() {
Block currentBlock;
Block previousBlock;
// 从前往后遍历所有区块
for (int i = 1; i < blockchain.size(); i++) {
currentBlock = blockchain.get(i);
previousBlock = blockchain.get(i - 1);
// 重新计算当前区块的数字指纹(实例变量hash)
if (!currentBlock.hash.equals(currentBlock.calculateHash())) {
System.out.println("Current Hashes not equal");
return false;
}
// 判断当前区块的中上一区块的数字指纹是否正确
if (!previousBlock.hash.equals(currentBlock.previousHash)) {
System.out.println("Previous Hashes not equal");
return false;
}
}
return true;
}
}
“区块链上线”
区块链系统是典型的分布式系统
每个机器(节点)都能运行区块链程序
每个节点都能运行上述NoobChain类中的主函数
在主函数中,主要执行的任务是什么呢?就是往区块链后添加区块
例如,比特币(bitcoin)系统的每个节点是共享区块链的,
而所有节点中的最长区块链会被整个系统接受。
为了防止某个节点随意创建“最长的区块链”,比特币有工作量证明机制(Proof of work),
也就是在“添加区块至区块链”时会消耗很多时间和算力
攻击者的攻击需要有超过整个系统中所有剩余节点的算力之和
“挖矿”
挖矿:简单理解,挖矿等价于“添加区块至区块链”;
由于存在工作量证明机制,添加区块至区块链时会很难;
下面模拟一种工作量证明机制(使得“添加区块至区块链”变难):
在区块Block类中新增一个nonce属性,nonce的作用是:
- 在计算数字指纹(hash实例变量)时也需加上这个nonce值;
- 只有当新建区块的数字指纹以一定数量的0开头,才能把新建区块添加入区块链
public class Block {
public String hash;
public String previousHash;
private String data;
private long timeStamp;
private int nonce;
public Block(String data, String previousHash) {
this.data = data;
this.previousHash = previousHash;
this.timeStamp = new Date().getTime();
this.hash = calculateHash(); // 给hash赋初值
}
public String calculateHash() {
String calculatedhash = StringUtil.applySha256(
previousHash + // 上一区块的数字指纹
Long.toString(timeStamp) + // 当前区块的时间戳
Integer.toString(nonce) + // 随机值
data); // 当前区块的额外信息
return calculatedhash;
}
/**
* 模拟:只有当hash数字指纹 以difficulty个0开头,
* 才能结束while循环,
* 然后去把已经创建好的区块添加至区块链
*
* @param difficulty
*/
public void mineBlock(int difficulty) {
String target = new String(new char[difficulty]).replace('\0', '0');
while (!hash.substring(0, difficulty).equals(target)) {
nonce++;
hash = calculateHash();
}
System.out.println("Block Mined!!! : " + hash);
}
}
更新NoobChain类中的main函数,实现:只有满足工作量证明后,才创建区块
public class NoobChain {
// 区块链
public static ArrayList<Block> blockchain = new ArrayList<Block>();
public static int difficulty = 1;
// 模拟挖矿
public static void main(String[] args) {
// add our blocks to the blockchain ArrayList:
blockchain.add(new Block("Hi im the first block", "0"));
System.out.println("Trying to Mine block 1... ");
// 得完成工作量证明,才能算成功把新建区块1添加区块链
blockchain.get(0).mineBlock(difficulty);
blockchain.add(new Block("Yo im the second block", blockchain.get(blockchain.size() - 1).hash));
System.out.println("Trying to Mine block 2... ");
// 得完成工作量证明,才能算成功把新建区块2添加区块链
blockchain.get(1).mineBlock(difficulty);
blockchain.add(new Block("Hey im the third block", blockchain.get(blockchain.size() - 1).hash));
System.out.println("Trying to Mine block 3... ");
// 得完成工作量证明,才能算成功把新建区块3添加区块链
blockchain.get(2).mineBlock(difficulty);
System.out.println("\nBlockchain is Valid: " + isChainValid()); // 检验区块链的完整性
String blockchainJson = new GsonBuilder().setPrettyPrinting().create().toJson(blockchain);
System.out.println("\nThe block chain: ");
System.out.println(blockchainJson);
}
// ... 其他代码省略
}
个人想法
如果某人想篡改区块链中已经确定好的区块,那么这个人就必须得赶在其他所有人的前头,把他自己节点上的那条区块链变得更长,他所需要的算力必须比其他人加起来还要多,不然其他人中就会出现至少一个人把未经篡改的区块链变长,继而把他那条篡改过的区块链淘汰。