区块链就是一串或者是一系列区块的集合,类似于链表的概念,每个区块都指向于后面一个区块,然后顺序的连接在一起。在区块链中的每一个区块都存放了很多很有价值的信息,主要包括三个部分:自己的数字签名,上一个区块的数字签名,还有一切需要加密的数据(这些数据在比特币中就相当于是交易的信息,它是加密货币的本质)。每个数字签名不但证明了自己是特有的一个区块,而且指向了前一个区块的来源,让所有的区块在链条中可以串起来,而数据就是一些特定的信息,你可以按照业务逻辑来保存业务数据。
这里的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文件
从持久化中恢复
将添加区块的代码注释掉,我们来看看结果
@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中的数据被读入了内存中恢复了回来。