区块链就是一串或者是一系列区块的集合,类似于链表的概念,每个区块都指向于后面一个区块,然后顺序的连接在一起。在区块链中的每一个区块都存放了很多很有价值的信息,主要包括三个部分:自己的数字签名,上一个区块的数字签名,还有一切需要加密的数据(这些数据在比特币中就相当于是交易的信息,它是加密货币的本质)。每个数字签名不但证明了自己是特有的一个区块,而且指向了前一个区块的来源,让所有的区块在链条中可以串起来,而数据就是一些特定的信息,你可以按照业务逻辑来保存业务数据。

单机区块链实现-LMLPHP

这里的hash指的就是数字签名

所以每一个区块不仅包含前一个区块的hash值,同时包含自身的一个hash值,自身的hash值是通过之前的hash值和数据data通过hash计算出来的。如果前一个区块的数据一旦被篡改了,那么前一个区块的hash值也会同样发生变化(因为数据也被计算在内),这样也就导致了所有后续的区块中的hash值。所以计算和比对hash值会让我们检查到当前的区块链是否是有效的,也就避免了数据被恶意篡改的可能性,因为篡改数据就会改变hash值并破坏整个区块链。

/**
 * 区块
 */
@ToString
@Getter
public class Block {
    //数字签名
    private String hash;
    //上一个区块的数字签名
    private String preHash;
    //保存的数据
    private String data;
    //时间戳
    private long timeStamp;
    //工作量证明
    private int nonce;

    public Block(String data,String preHash) {
        this.data = data;
        this.preHash = preHash;
        timeStamp = new Date().getTime();
        hash = calculateHash();
    }

    /**
     * 计算数字签名
     * @return
     */
    public String calculateHash() {
        return StringUntil.applySha256(preHash + timeStamp + nonce + data);
    }

    /**
     * 挖矿
     * @param difficuity 挖矿难度
     */
    public void mineBlock(int difficuity) {
        String target = new String(new char[difficuity]).replace('\0','0');
        while (!hash.substring(0,difficuity).equals(target)) {
            nonce++;
            hash = calculateHash();
        }
        System.out.println("Block Mind!!!: " + hash);
    }
}
public class StringUntil {
    /**
     * Sha256离散
     * @param input
     * @return
     */
    public static String applySha256(String input){
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            //Applies sha256 to our input,
            byte[] hash = digest.digest(input.getBytes("UTF-8"));
            StringBuffer hexString = new StringBuffer(); // This will contain hash as hexidecimal
            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);
        }
    }
}
public class BlockChain {
    public static List<Block> blockChain = new ArrayList<>();
    public static int difficulty = 5;

    /**
     * 判断整条区块链是否有效
     * @return
     */
    public static boolean isChainValid() {
        Block currentBlock;
        Block prevBlock;
        String hashTarget = new String(new char[difficulty]).replace('\0','0');

        for (int i = 1; i < blockChain.size(); i++) {
            currentBlock = blockChain.get(i);
            prevBlock = blockChain.get(i - 1);
            if (!currentBlock.getHash().equals(currentBlock.calculateHash())) {
                System.out.println("当前区块哈希值不匹配");
                return false;
            }
            if (!prevBlock.getHash().equals(currentBlock.getPreHash())) {
                System.out.println("前一个区块哈希值不匹配");
                return false;
            }
            if (!currentBlock.getHash().substring(0,difficulty).equals(hashTarget)) {
                System.out.println("当前区块有异常");
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        //生成首区块
        Block genesisBlock = new Block("first","0");
        blockChain.add(genesisBlock);
        //挖矿
        blockChain.get(0).mineBlock(difficulty);
        System.out.println(genesisBlock);
        //第二个区块
        Block secBlock = new Block("second",genesisBlock.getHash());
        blockChain.add(secBlock);
        blockChain.get(1).mineBlock(difficulty);
        System.out.println(secBlock);
        //第三个区块
        Block thirdBlock = new Block("third",secBlock.getHash());
        blockChain.add(thirdBlock);
        blockChain.get(2).mineBlock(difficulty);
        System.out.println(thirdBlock);
        System.out.println("区块链有效性: " + isChainValid());
        System.out.println(JSONObject.toJSONString(blockChain));
    }
}

运行结果

Block Mind!!!: 000000dcabbe4bbeccbedc1efc25a886c598652e1efac0dfe1a3d74d9bc9f858
Block(hash=000000dcabbe4bbeccbedc1efc25a886c598652e1efac0dfe1a3d74d9bc9f858, preHash=0, data=first, timeStamp=1608653349150, nonce=1196438)
Block Mind!!!: 00000f6e06be2d09e9e8ba232d84ebe0de75b8e395feaa0f3ddb01377b55c72d
Block(hash=00000f6e06be2d09e9e8ba232d84ebe0de75b8e395feaa0f3ddb01377b55c72d, preHash=000000dcabbe4bbeccbedc1efc25a886c598652e1efac0dfe1a3d74d9bc9f858, data=second, timeStamp=1608653351114, nonce=1312709)
Block Mind!!!: 0000004bdf0b2af602460d79cdd247ac1b57fb651eb68ae68198b514de490569
Block(hash=0000004bdf0b2af602460d79cdd247ac1b57fb651eb68ae68198b514de490569, preHash=00000f6e06be2d09e9e8ba232d84ebe0de75b8e395feaa0f3ddb01377b55c72d, data=third, timeStamp=1608653353097, nonce=1552650)
区块链有效性: true
[{"data":"first","hash":"000000dcabbe4bbeccbedc1efc25a886c598652e1efac0dfe1a3d74d9bc9f858","nonce":1196438,"preHash":"0","timeStamp":1608653349150},{"data":"second","hash":"00000f6e06be2d09e9e8ba232d84ebe0de75b8e395feaa0f3ddb01377b55c72d","nonce":1312709,"preHash":"000000dcabbe4bbeccbedc1efc25a886c598652e1efac0dfe1a3d74d9bc9f858","timeStamp":1608653351114},{"data":"third","hash":"0000004bdf0b2af602460d79cdd247ac1b57fb651eb68ae68198b514de490569","nonce":1552650,"preHash":"00000f6e06be2d09e9e8ba232d84ebe0de75b8e395feaa0f3ddb01377b55c72d","timeStamp":1608653353097}]

使用RocksDB区块链数据持久化

新增依赖

<dependency>
   <groupId>org.rocksdb</groupId>
   <artifactId>rocksdbjni</artifactId>
   <version>6.6.4</version>
</dependency>
<dependency>
   <groupId>com.esotericsoftware</groupId>
   <artifactId>kryo-shaded</artifactId>
   <version>4.0.2</version>
</dependency>

序列化工具

public class SerializeUtils {
    /**
     * 序列化字符串
     * @param o
     * @return
     */
    public static byte[] serializeStr(String o) {
        Kryo kryo = new Kryo();
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        Output output = new Output(stream);
        kryo.writeObject(output, o);
        output.close();
        return stream.toByteArray();
    }

    /**
     * 反序列化字符串
     * @param data
     * @return
     */
    public static String deserializeStr(byte[] data) {
        Input input = null;
        try {
            Kryo kryo = new Kryo();
            input = new Input(new ByteArrayInputStream(data));
            return kryo.readObject(input,String.class);
        }
        finally {
            input.close();
        }
    }

    /**
     * 序列化Map
     * @param o
     * @return
     */
    public static byte[] serializeMap(HashMap o) {
        Kryo kryo = new Kryo();
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        Output output = new Output(stream);
        kryo.writeObject(output, o);
        output.close();
        return stream.toByteArray();
    }

    /**
     * 反序列化Map
     * @param data
     * @return
     */
    public static HashMap deserializeMap(byte[] data) {
        Input input = null;
        try {
            Kryo kryo = new Kryo();
            input = new Input(new ByteArrayInputStream(data));
            return kryo.readObject(input,HashMap.class);
        }
        finally {
            input.close();
        }
    }

    /**
     * 序列化Block
     * @param o
     * @return
     */
    public static byte[] serializeBlock(Block o) {
        Kryo kryo = new Kryo();
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        Output output = new Output(stream);
        kryo.writeObject(output, o);
        output.close();
        return stream.toByteArray();
    }

    /**
     * 反序列化Block
     * @param data
     * @return
     */
    public static Block deserializeBlock(byte[] data) {
        Input input = null;
        try {
            Kryo kryo = new Kryo();
            input = new Input(new ByteArrayInputStream(data));
            return kryo.readObject(input,Block.class);
        }
        finally {
            input.close();
        }
    }
}

RocksDB持久化工具

public class RocksDBUntils {
    static {
        RocksDB.loadLibrary();
    }
    private static final String path = "/Users/admin/Downloads/blocks";
    //区块桶键名,在RocksDB中只有这一个键
    //值为内存的Map
    private static final String BLOCKS_BUCKET_KEY = "blocks";
    //最新区块键,在内存blocksBucket映射中获取最新区块用
    private static final String LAST_BLOCK_KEY = "l";
    private static RocksDB db;
    //区块桶映射,保存两种
    //一是最新区块的数字签名
    //二是区块链中所有的区块
    private Map<String,byte[]> blocksBucket;
    private volatile static RocksDBUntils instance;


    public static RocksDBUntils getInstance() throws RocksDBException {
        synchronized (RocksDBUntils.class) {
            if (instance == null) {
                instance = new RocksDBUntils();
            }
        }
        return instance;
    }

    private RocksDBUntils() throws RocksDBException {
        openDB();
        initBlockBucket();
    }

    private void openDB() throws RocksDBException {
        Options options = new Options();
        options.setCreateIfMissing(true);
        db = RocksDB.open(options,path);
    }

    @SuppressWarnings("unchecked")
    private void initBlockBucket() throws RocksDBException {
        byte[] blockBucketKey = SerializeUtils.serializeStr(BLOCKS_BUCKET_KEY);
        byte[] blockBucketBytes = db.get(blockBucketKey);
        if (blockBucketBytes != null) {
            blocksBucket = SerializeUtils.deserializeMap(blockBucketBytes);
        }else {
            blocksBucket = new HashMap<>();
            db.put(blockBucketKey,SerializeUtils.serializeMap((HashMap) blocksBucket));
        }
    }

    /**
     * 保存最新区块的数字签名
     * @param tipBlockHash
     */
    public void putLastBlockHash(String tipBlockHash) throws RocksDBException {
        blocksBucket.put(LAST_BLOCK_KEY,SerializeUtils.serializeStr(tipBlockHash));
        db.put(SerializeUtils.serializeStr(BLOCKS_BUCKET_KEY),SerializeUtils.serializeMap((HashMap) blocksBucket));

    }

    /**
     * 查询最新区块的数字签名
     * @return
     */
    public String getLastBlockHash() {
        byte[] lastBlockHashBytes = blocksBucket.get(LAST_BLOCK_KEY);
        if (lastBlockHashBytes != null) {
            return SerializeUtils.deserializeStr(lastBlockHashBytes);
        }
        return "";
    }

    /**
     * 保存区块
     * @param block
     * @throws RocksDBException
     */
    public void putBlock(Block block) throws RocksDBException {
        blocksBucket.put(block.getHash(),SerializeUtils.serializeBlock(block));
        db.put(SerializeUtils.serializeStr(BLOCKS_BUCKET_KEY),SerializeUtils.serializeMap((HashMap) blocksBucket));
    }

    /**
     * 查询区块
     * @param blockHash
     * @return
     */
    public Block getBlock(String blockHash) {
        return SerializeUtils.deserializeBlock(blocksBucket.get(blockHash));
    }

    public void closeDB() {
        db.close();
    }
}

由于Kryo序列化对象必须有无参构造器,给Block增加无参构造器

/**
 * 区块
 */
@ToString
@Getter
@NoArgsConstructor
public class Block {
    //数字签名
    private String hash;
    //上一个区块的数字签名
    private String preHash;
    //保存的数据
    private String data;
    //时间戳
    private long timeStamp;
    //工作量证明
    private int nonce;

    public Block(String data,String preHash) {
        this.data = data;
        this.preHash = preHash;
        timeStamp = new Date().getTime();
        hash = calculateHash();
    }

    /**
     * 计算数字签名
     * @return
     */
    public String calculateHash() {
        String all = preHash + timeStamp + nonce + data;
        return StringUntil.applySha256(all);
    }

    /**
     * 挖矿
     * @param difficuity 挖矿难度
     */
    public void mineBlock(int difficuity) {
        String target = new String(new char[difficuity]).replace('\0','0');
        while (!hash.substring(0,difficuity).equals(target)) {
            nonce++;
            hash = calculateHash();
        }
        System.out.println("Block Mind!!!: " + hash);
    }
}

调整区块链代码

@AllArgsConstructor
public class BlockChain implements Iterator<Block> {
    private static int difficulty = 5;
    //最新区块数字签名
    private String lastBlockHash;

    /**
     * 创建区块链
     * @return
     * @throws RocksDBException
     */
    public static BlockChain newBlockchain() throws RocksDBException {
        String lastBlockHash = RocksDBUntils.getInstance().getLastBlockHash();
        if (StringUtils.isEmpty(lastBlockHash)) {
            Block genesisBlock = new Block("first","0");
            lastBlockHash = genesisBlock.getHash();
            RocksDBUntils.getInstance().putBlock(genesisBlock);
            RocksDBUntils.getInstance().putLastBlockHash(lastBlockHash);
        }
        return new BlockChain(lastBlockHash);
    }

    /**
     * 添加区块
     * @param data
     * @throws RocksDBException
     */
    public void addBlock(String data) throws RocksDBException {
        String lastBlockHash = RocksDBUntils.getInstance().getLastBlockHash();
        if (StringUtils.isEmpty(lastBlockHash)) {
            throw new RuntimeException("区块链添加区块错误");
        }
        addBlock(new Block(data,lastBlockHash));
    }

    /**
     * 添加区块
     * @param block
     * @throws RocksDBException
     */
    public void addBlock(Block block) throws RocksDBException {
        RocksDBUntils.getInstance().putLastBlockHash(block.getHash());
        RocksDBUntils.getInstance().putBlock(block);
        lastBlockHash = block.getHash();
    }

    @Override
    public boolean hasNext() {
        if (StringUtils.isEmpty(lastBlockHash) || lastBlockHash.length() == 1) {
            return false;
        }
        try {
            Block lastBlock = RocksDBUntils.getInstance().getBlock(lastBlockHash);
            if (lastBlock == null) {
                return false;
            }
            if (lastBlock.getPreHash().length() == 1) {
                return true;
            }
            return RocksDBUntils.getInstance().getBlock(lastBlock.getPreHash()) != null;
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public Block next() {
        try {
            Block currentBlock = RocksDBUntils.getInstance().getBlock(lastBlockHash);
            if (currentBlock != null) {
                lastBlockHash = currentBlock.getPreHash();
                return currentBlock;
            }
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) throws RocksDBException {
        BlockChain blockChain = BlockChain.newBlockchain();
        blockChain.addBlock("second");
        blockChain.addBlock("third");
        while (blockChain.hasNext()) {
            Block block = blockChain.next();
            if (block != null) {
                block.mineBlock(difficulty);
                System.out.println(block);
            }
        }
    }
}

运行结果

Block Mind!!!: 000008c3b1723df7f6f5a5078b58e321e63450cab1924f3e1ca61f1b624c7d9b
Block(hash=000008c3b1723df7f6f5a5078b58e321e63450cab1924f3e1ca61f1b624c7d9b, preHash=4668eff67280bbaedb44ec34bb8ea68ab050feca1d12122a4b165a7efc822b5d, data=third, timeStamp=1612050079256, nonce=1245798)
Block Mind!!!: 00000401eecc84a0af717c3b3156bfa1d466e02d1c7f19513ef0cea7402261c0
Block(hash=00000401eecc84a0af717c3b3156bfa1d466e02d1c7f19513ef0cea7402261c0, preHash=567e90bc9316153b9a7125664a11d37d7a511a79f816ada940c3ac417069adbb, data=second, timeStamp=1612050079255, nonce=337767)
Block Mind!!!: 00000d2f12c9120e73ce3a93422a6e56aeb2d9edf8c326b243a956da5c82d6d6
Block(hash=00000d2f12c9120e73ce3a93422a6e56aeb2d9edf8c326b243a956da5c82d6d6, preHash=0, data=first, timeStamp=1612050079241, nonce=960202)

区块链数据的RocksDB文件

单机区块链实现-LMLPHP

从持久化中恢复

将添加区块的代码注释掉,我们来看看结果

@AllArgsConstructor
public class BlockChain implements Iterator<Block> {
    private static int difficulty = 5;
    //最新区块数字签名
    private String lastBlockHash;

    /**
     * 创建区块链
     * @return
     * @throws RocksDBException
     */
    public static BlockChain newBlockchain() throws RocksDBException {
        String lastBlockHash = RocksDBUntils.getInstance().getLastBlockHash();
        if (StringUtils.isEmpty(lastBlockHash)) {
            Block genesisBlock = new Block("first","0");
            lastBlockHash = genesisBlock.getHash();
            RocksDBUntils.getInstance().putBlock(genesisBlock);
            RocksDBUntils.getInstance().putLastBlockHash(lastBlockHash);
        }
        return new BlockChain(lastBlockHash);
    }

    /**
     * 添加区块
     * @param data
     * @throws RocksDBException
     */
    public void addBlock(String data) throws RocksDBException {
        String lastBlockHash = RocksDBUntils.getInstance().getLastBlockHash();
        if (StringUtils.isEmpty(lastBlockHash)) {
            throw new RuntimeException("区块链添加区块错误");
        }
        addBlock(new Block(data,lastBlockHash));
    }

    /**
     * 添加区块
     * @param block
     * @throws RocksDBException
     */
    public void addBlock(Block block) throws RocksDBException {
        RocksDBUntils.getInstance().putLastBlockHash(block.getHash());
        RocksDBUntils.getInstance().putBlock(block);
        lastBlockHash = block.getHash();
    }

    @Override
    public boolean hasNext() {
        if (StringUtils.isEmpty(lastBlockHash) || lastBlockHash.length() == 1) {
            return false;
        }
        try {
            Block lastBlock = RocksDBUntils.getInstance().getBlock(lastBlockHash);
            if (lastBlock == null) {
                return false;
            }
            if (lastBlock.getPreHash().length() == 1) {
                return true;
            }
            return RocksDBUntils.getInstance().getBlock(lastBlock.getPreHash()) != null;
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        return false;
    }

    @Override
    public Block next() {
        try {
            Block currentBlock = RocksDBUntils.getInstance().getBlock(lastBlockHash);
            if (currentBlock != null) {
                lastBlockHash = currentBlock.getPreHash();
                return currentBlock;
            }
        } catch (RocksDBException e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) throws RocksDBException {
        BlockChain blockChain = BlockChain.newBlockchain();
//        blockChain.addBlock("second");
//        blockChain.addBlock("third");
        while (blockChain.hasNext()) {
            Block block = blockChain.next();
            if (block != null) {
                block.mineBlock(difficulty);
                System.out.println(block);
            }
        }
    }
}

运行结果

Block Mind!!!: 000008c3b1723df7f6f5a5078b58e321e63450cab1924f3e1ca61f1b624c7d9b
Block(hash=000008c3b1723df7f6f5a5078b58e321e63450cab1924f3e1ca61f1b624c7d9b, preHash=4668eff67280bbaedb44ec34bb8ea68ab050feca1d12122a4b165a7efc822b5d, data=third, timeStamp=1612050079256, nonce=1245798)
Block Mind!!!: 00000401eecc84a0af717c3b3156bfa1d466e02d1c7f19513ef0cea7402261c0
Block(hash=00000401eecc84a0af717c3b3156bfa1d466e02d1c7f19513ef0cea7402261c0, preHash=567e90bc9316153b9a7125664a11d37d7a511a79f816ada940c3ac417069adbb, data=second, timeStamp=1612050079255, nonce=337767)
Block Mind!!!: 00000d2f12c9120e73ce3a93422a6e56aeb2d9edf8c326b243a956da5c82d6d6
Block(hash=00000d2f12c9120e73ce3a93422a6e56aeb2d9edf8c326b243a956da5c82d6d6, preHash=0, data=first, timeStamp=1612050079241, nonce=960202)

说明保存在RocksDB中的数据被读入了内存中恢复了回来。

04-25 11:40