Java实现简单区块链

参考地址:Creating Your First Blockchain with Java

准备

开发环境

  1. java1.8~
  2. maven
  3. 任选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);
    }

    // ... 其他代码省略
}

个人想法

如果某人想篡改区块链中已经确定好的区块,那么这个人就必须得赶在其他所有人的前头,把他自己节点上的那条区块链变得更长,他所需要的算力必须比其他人加起来还要多,不然其他人中就会出现至少一个人把未经篡改的区块链变长,继而把他那条篡改过的区块链淘汰。

posted @ 2022-05-22 21:23  bacmive  阅读(808)  评论(0编辑  收藏  举报