日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

基于Java语言构建区块链(六)—— 交易(Merkle Tree)

發(fā)布時間:2025/3/21 java 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于Java语言构建区块链(六)—— 交易(Merkle Tree) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

基于Java語言構(gòu)建區(qū)塊鏈(六)—— 交易(Merkle Tree)

2018年04月16日 10:21:35?wangwei_hz?閱讀數(shù):480更多

個人分類:?區(qū)塊鏈比特幣bitcoin

最終內(nèi)容請以原文為準(zhǔn):https://wangwei.one/posts/630e7ae5.html

引言

在這一系列文章的最開始部分,我們提到過區(qū)塊鏈?zhǔn)且粋€分布式的數(shù)據(jù)庫。那時候,我們決定跳過”分布式”這一環(huán)節(jié),并且聚焦于”數(shù)據(jù)存儲”這一環(huán)節(jié)。到目前為止,我們幾乎實現(xiàn)了區(qū)塊鏈的所有組成部分。在本篇文章中,我們將會涉及一些在前面的文章中所忽略的一些機制,并且在下一篇文章中我們將開始研究區(qū)塊鏈的分布式特性。

前面各個部分內(nèi)容:

  • 基本原型
  • 工作量證明
  • 持久化 & 命令行
  • 交易(UTXO)
  • 地址(錢包)
  • UTXO池

    在?持久化 & 命令行?這篇文章中,我們研究了比特幣核心存儲區(qū)塊的方式。當(dāng)中我們提到過與區(qū)塊相關(guān)的數(shù)據(jù)存儲在?blocks?這個數(shù)據(jù)桶中,而交易數(shù)據(jù)則存儲在?chainstate?這個數(shù)據(jù)桶中,讓我們來回憶一下,chainstate?數(shù)據(jù)桶的數(shù)據(jù)結(jié)構(gòu):

    • ‘c’ + 32-byte transaction hash -> unspent transaction output record for that transaction

      某筆交易的UTXO記錄

    • ‘B’ -> 32-byte block hash: the block hash up to which the database represents the unspent transaction outputs

      數(shù)據(jù)庫所表示的UTXO的區(qū)塊Hash

    從那篇文章開始,我們已經(jīng)實現(xiàn)了比特幣的交易機制,但是我們還沒有用到?chainstate?數(shù)據(jù)桶去存儲我們的交易輸出。所以,這將是我們現(xiàn)在要去做的事情。

    chainstate?不會去存儲交易數(shù)據(jù)。相反,它存儲的是 UTXO 集,也就是未被花費的交易輸出集合。除此之外,它還存儲了”數(shù)據(jù)庫所表示的UTXO的區(qū)塊Hash”,我們這里先暫且忽略這一點,因為我們還沒有用到區(qū)塊高度(這一點我們會在后面的文章進行實現(xiàn))。

    那么,我們?yōu)槭裁葱枰?UTXO 池呢?

    一起來看一下我們前面實現(xiàn)的?findUnspentTransactions?方法:

    /*** 查找錢包地址對應(yīng)的所有未花費的交易** @param pubKeyHash 錢包公鑰Hash* @return*/private Transaction[] findUnspentTransactions(byte[] pubKeyHash) throws Exception {Map<String, int[]> allSpentTXOs = this.getAllSpentTXOs(pubKeyHash);Transaction[] unspentTxs = {};// 再次遍歷所有區(qū)塊中的交易輸出for (BlockchainIterator blockchainIterator = this.getBlockchainIterator(); blockchainIterator.hashNext(); ) {Block block = blockchainIterator.next();for (Transaction transaction : block.getTransactions()) {String txId = Hex.encodeHexString(transaction.getTxId());int[] spentOutIndexArray = allSpentTXOs.get(txId);for (int outIndex = 0; outIndex < transaction.getOutputs().length; outIndex++) {if (spentOutIndexArray != null && ArrayUtils.contains(spentOutIndexArray, outIndex)) {continue;}// 保存不存在 allSpentTXOs 中的交易if (transaction.getOutputs()[outIndex].isLockedWithKey(pubKeyHash)) {unspentTxs = ArrayUtils.add(unspentTxs, transaction);}}}}return unspentTxs;}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    該方法是用來查找錢包地址對應(yīng)的包含未花費交易輸出的交易信息。由于交易信息是存儲在區(qū)塊當(dāng)中,所以我們現(xiàn)有的做法是遍歷區(qū)塊鏈中的每個區(qū)塊,然后遍歷每個區(qū)塊中的交易信息,再然后遍歷每個交易中的交易輸出,并檢查交易輸出是否被相應(yīng)的錢包地址所鎖定,效率非常低下。截止2018年3月29號,比特幣中有?515698?個區(qū)塊,并且這些數(shù)據(jù)占據(jù)了140+Gb 的磁盤空間。這也就意味著一個人必須運行全節(jié)點(下載所有的區(qū)塊數(shù)據(jù))才能驗證交易信息。此外,驗證交易信息需要遍歷所有的區(qū)塊。

    針對這個問題的解決辦法是需要有一個存儲了所有UTXOs(未花費交易輸出)的索引,這就是 UTXOs 池所要做的事情:UTXOs池其實是一個緩存空間,它所緩存的數(shù)據(jù)需要從構(gòu)建區(qū)塊鏈中所有的交易數(shù)據(jù)中獲得(通過遍歷所有的區(qū)塊鏈,不過這個構(gòu)建操作只需要執(zhí)行一次即可),并且它后續(xù)還會用于錢包余額的計算以及新的交易數(shù)據(jù)的驗證。截止到2017年9月,UTXOs池大約為 2.7Gb。

    好了,讓我們來想一下,為了實現(xiàn) UTXOs 池我們需要做哪些事情。當(dāng)前,有下列方法被用于查找交易信息:

  • Blockchain.getAllSpentTXOs?—— 查詢所有已被花費的交易輸出。它需要遍歷區(qū)塊鏈中所有區(qū)塊中交易信息。

  • Blockchain.findUnspentTransactions?—— 查詢包含未被花費的交易輸出的交易信息。它也需要遍歷區(qū)塊鏈中所有區(qū)塊中交易信息。

  • Blockchain.findSpendableOutputs?—— 該方法用于新的交易創(chuàng)建之時。它需要找到足夠多的交易輸出,以滿足所需支付的金額。需要調(diào)用?Blockchain.findUnspentTransactions?方法。

  • Blockchain.findUTXO?—— 查詢錢包地址所對應(yīng)的所有未花費交易輸出,然后用于計算錢包余額。需要調(diào)用

    Blockchain.findUnspentTransactions?方法。

  • Blockchain.findTransaction?—— 通過交易ID查詢交易信息。它需要遍歷所有的區(qū)塊直到找到交易信息為止。

  • 如你所見,上面這些方法都需要去遍歷數(shù)據(jù)庫中的所有區(qū)塊。由于UTXOs池只存儲未被花費的交易輸出,而不會存儲所有的交易信息,因此我們不會對有?Blockchain.findTransaction?進行優(yōu)化。

    那么,我們需要下列這些方法:

  • Blockchain.findUTXO?—— 通過遍歷所有的區(qū)塊來找到所有未被花費的交易輸出.
  • UTXOSet.reindex?—— 調(diào)用上面?findUTXO?方法,然后將查詢結(jié)果存儲在數(shù)據(jù)庫中。也即需要進行緩存的地方。
  • UTXOSet.findSpendableOutputs?—— 與?Blockchain.findSpendableOutputs?類似,區(qū)別在于會使用 UTXO 池。
  • UTXOSet.findUTXO?—— 與Blockchain.findUTXO?類似,區(qū)別在于會使用 UTXO 池。
  • Blockchain.findTransaction?—— 邏輯保持不變。
  • 這樣,兩個使用最頻繁的方法將從現(xiàn)在開始使用緩存!讓我們開始編碼吧!

    定義?UTXOSet

    @NoArgsConstructor @AllArgsConstructor @Slf4j public class UTXOSet {private Blockchain blockchain; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    重建 UTXO 池索引:

    public class UTXOSet {.../*** 重建 UTXO 池索引*/@Synchronized public void reIndex() {log.info("Start to reIndex UTXO set !");RocksDBUtils.getInstance().cleanChainStateBucket();Map<String, TXOutput[]> allUTXOs = blockchain.findAllUTXOs();for (Map.Entry<String, TXOutput[]> entry : allUTXOs.entrySet()) {RocksDBUtils.getInstance().putUTXOs(entry.getKey(), entry.getValue());}log.info("ReIndex UTXO set finished ! ");}... }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    此方法用于初始化 UTXOSet。首先,需要清空?chainstate?數(shù)據(jù)桶,然后查詢所有未被花費的交易輸出,并將它們保存到?chainstate?數(shù)據(jù)桶中。

    實現(xiàn)?findSpendableOutputs?方法,供?Transation.newUTXOTransaction?調(diào)用

    public class UTXOSet {... /*** 尋找能夠花費的交易** @param pubKeyHash 錢包公鑰Hash* @param amount 花費金額*/public SpendableOutputResult findSpendableOutputs(byte[] pubKeyHash, int amount) {Map<String, int[]> unspentOuts = Maps.newHashMap();int accumulated = 0;Map<String, byte[]> chainstateBucket = RocksDBUtils.getInstance().getChainstateBucket();for (Map.Entry<String, byte[]> entry : chainstateBucket.entrySet()) {String txId = entry.getKey();TXOutput[] txOutputs = (TXOutput[]) SerializeUtils.deserialize(entry.getValue());for (int outId = 0; outId < txOutputs.length; outId++) {TXOutput txOutput = txOutputs[outId];if (txOutput.isLockedWithKey(pubKeyHash) && accumulated < amount) {accumulated += txOutput.getValue();int[] outIds = unspentOuts.get(txId);if (outIds == null) {outIds = new int[]{outId};} else {outIds = ArrayUtils.add(outIds, outId);}unspentOuts.put(txId, outIds);if (accumulated >= amount) {break;}}}}return new SpendableOutputResult(accumulated, unspentOuts);}...}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    實現(xiàn)?findUTXOs?接口,供?CLI.getBalance?調(diào)用:

    public class UTXOSet {... /*** 查找錢包地址對應(yīng)的所有UTXO** @param pubKeyHash 錢包公鑰Hash* @return*/public TXOutput[] findUTXOs(byte[] pubKeyHash) {TXOutput[] utxos = {};Map<String, byte[]> chainstateBucket = RocksDBUtils.getInstance().getChainstateBucket();if (chainstateBucket.isEmpty()) {return utxos;}for (byte[] value : chainstateBucket.values()) {TXOutput[] txOutputs = (TXOutput[]) SerializeUtils.deserialize(value);for (TXOutput txOutput : txOutputs) {if (txOutput.isLockedWithKey(pubKeyHash)) {utxos = ArrayUtils.add(utxos, txOutput);}}}return utxos;}...}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    以上這些方法都是先前?Blockchain?中相應(yīng)方法的微調(diào)版,先前的方法將不再使用。

    有了UTXO池之后,意味著我們的交易數(shù)據(jù)分開存儲到了兩個不同的數(shù)據(jù)桶中:交易數(shù)據(jù)存儲到了?block?數(shù)據(jù)桶中,而UTXO存儲到了?chainstate?數(shù)據(jù)桶中。這就需要一種同步機制來保證每當(dāng)一個新的區(qū)塊產(chǎn)生時,UTXO池能夠及時同步最新區(qū)塊中的交易數(shù)據(jù),畢竟我們不想頻地進行?reIndex?。因此,我們需要如下方法:

    更新UTXO池:

    public class UTXOSet {... /*** 更新UTXO池* <p>* 當(dāng)一個新的區(qū)塊產(chǎn)生時,需要去做兩件事情:* 1)從UTXO池中移除花費掉了的交易輸出;* 2)保存新的未花費交易輸出;** @param tipBlock 最新的區(qū)塊*/@Synchronizedpublic void update(Block tipBlock) {if (tipBlock == null) {log.error("Fail to update UTXO set ! tipBlock is null !");throw new RuntimeException("Fail to update UTXO set ! ");}for (Transaction transaction : tipBlock.getTransactions()) {// 根據(jù)交易輸入排查出剩余未被使用的交易輸出if (!transaction.isCoinbase()) {for (TXInput txInput : transaction.getInputs()) {// 余下未被使用的交易輸出TXOutput[] remainderUTXOs = {};String txId = Hex.encodeHexString(txInput.getTxId());TXOutput[] txOutputs = RocksDBUtils.getInstance().getUTXOs(txId);if (txOutputs == null) {continue;}for (int outIndex = 0; outIndex < txOutputs.length; outIndex++) {if (outIndex != txInput.getTxOutputIndex()) {remainderUTXOs = ArrayUtils.add(remainderUTXOs, txOutputs[outIndex]);}}// 沒有剩余則刪除,否則更新if (remainderUTXOs.length == 0) {RocksDBUtils.getInstance().deleteUTXOs(txId);} else {RocksDBUtils.getInstance().putUTXOs(txId, remainderUTXOs);}}}// 新的交易輸出保存到DB中TXOutput[] txOutputs = transaction.getOutputs();String txId = Hex.encodeHexString(transaction.getTxId());RocksDBUtils.getInstance().putUTXOs(txId, txOutputs);}}...}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    讓我們將 UTXOSet 用到它們所需之處去:

    public class CLI {.../*** 創(chuàng)建區(qū)塊鏈** @param address*/private void createBlockchain(String address) {Blockchain blockchain = Blockchain.createBlockchain(address);UTXOSet utxoSet = new UTXOSet(blockchain);utxoSet.reIndex();log.info("Done ! ");}...}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    當(dāng)創(chuàng)建一個新的區(qū)塊鏈?zhǔn)?#xff0c;我們需要重建 UTXO 池索引。截止目前,這是唯一一處用到?reIndex?的地方,盡管看起有些多余,因為在區(qū)塊鏈創(chuàng)建之初僅僅只有一個區(qū)塊和一筆交易。

    修改?CLI.send?接口:

    public class CLI {.../*** 轉(zhuǎn)賬** @param from* @param to* @param amount*/private void send(String from, String to, int amount) throws Exception {...Blockchain blockchain = Blockchain.createBlockchain(from);Transaction transaction = Transaction.newUTXOTransaction(from, to, amount, blockchain);Block newBlock = blockchain.mineBlock(new Transaction[]{transaction});new UTXOSet(blockchain).update(newBlock);...}...}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    當(dāng)一個新的區(qū)塊產(chǎn)生后,需要去更新 UTXO 池數(shù)據(jù)。

    讓我們來檢查一下它們的運行情況:

    $ java -jar blockchain-java-jar-with-dependencies.jar createwallet wallet address : 1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf$ java -jar blockchain-java-jar-with-dependencies.jar createwallet wallet address : 1HX7bWwCjvxkjq65GUgAVRFfTZy6yKWkoG$ java -jar blockchain-java-jar-with-dependencies.jar createwallet wallet address : 1L1RoFgyjCrNPCPHmSEBtNiV3h2wiF9mZV$ java -jar blockchain-java-jar-with-dependencies.jar createblockchain -address 1JgppX2xMshr35wHzvNWQBejUAZ3Te5MdfElapsed Time: 164.961 seconds correct hash Hex: 00225493862611bc517cb6b3610e99d26d98a6b52484c9fa745df6ceff93f445 Done ! $ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address 1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf Balance of '1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf': 10$ java -jar blockchain-java-jar-with-dependencies.jar send -from 1HX7bWwCjvxkjq65GUgAVRFfTZy6yKWkoG -to 1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf -amount 5 java.lang.Exception: ERROR: Not enough funds$ java -jar blockchain-java-jar-with-dependencies.jar send -from 1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf -to 1HX7bWwCjvxkjq65GUgAVRFfTZy6yKWkoG -amount 2 Elapsed Time: 54.92 seconds correct hash Hex: 0001ab21f71ff2d6d532bf3b3388db790c2b03e28d7bd27bd669c5f6380a4e5b Success!$ java -jar blockchain-java-jar-with-dependencies.jar send -from 1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf -to 1L1RoFgyjCrNPCPHmSEBtNiV3h2wiF9mZV -amount 2 Elapsed Time: 54.92 seconds correct hash Hex: 0009b925cc94e3db8bab2958b1fc2d1764aa15531e20756d92c3a93065c920f0 Success!$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address 1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf Balance of '1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf': 6$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address 1HX7bWwCjvxkjq65GUgAVRFfTZy6yKWkoG Balance of '1HX7bWwCjvxkjq65GUgAVRFfTZy6yKWkoG': 2$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address 1L1RoFgyjCrNPCPHmSEBtNiV3h2wiF9mZV Balance of '1L1RoFgyjCrNPCPHmSEBtNiV3h2wiF9mZV': 2
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    獎勵機制

    前面的章節(jié)中我們省略了礦工挖礦的獎勵機制。時機已經(jīng)成熟,該實現(xiàn)它了。

    礦工獎勵其實是一個 coinbase 交易(創(chuàng)幣交易)。當(dāng)一個礦工節(jié)點開始去生產(chǎn)一個新的區(qū)塊時,他會從隊列中取出一些交易數(shù)據(jù),并且為它們預(yù)制一個 coinbase 交易。這筆 coinbase 交易中僅有的交易輸出包含了礦工的公鑰hash。

    只需要更新?send?命令接口,我們就可以輕松實現(xiàn)礦工的獎勵機制:

    public class CLI {.../*** 轉(zhuǎn)賬** @param from* @param to* @param amount*/private void send(String from, String to, int amount) throws Exception {...Blockchain blockchain = Blockchain.createBlockchain(from);// 新交易Transaction transaction = Transaction.newUTXOTransaction(from, to, amount, blockchain);// 獎勵Transaction rewardTx = Transaction.newCoinbaseTX(from, "");Block newBlock = blockchain.mineBlock(new Transaction[]{transaction, rewardTx});new UTXOSet(blockchain).update(newBlock);...}...}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    還需要修改交易驗證方法,coinbase 交易直接驗證通過:

    public class Blockchain {/*** 交易簽名驗證** @param tx*/private boolean verifyTransactions(Transaction tx) {if (tx.isCoinbase()) {return true;}...}...}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在我們的實現(xiàn)邏輯中,代幣的發(fā)送也是區(qū)塊的生產(chǎn)者,因此,獎勵也歸他所有。

    讓我們來驗證一下獎勵機制:

    $ java -jar blockchain-java-jar-with-dependencies.jar createwallet wallet address : 1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUD$ java -jar blockchain-java-jar-with-dependencies.jar createwallet wallet address : 17crpQoWy7TEkY9UPjZ3Qt9Fc2rWPUt8KX$ java -jar blockchain-java-jar-with-dependencies.jar createwallet wallet address : 12L868QZW1ySYzf2oT5ha9py9M5JrSRhvT$ java -jar blockchain-java-jar-with-dependencies.jar createblockchain -address 1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUDElapsed Time: 17.973 seconds correct hash Hex: 0000defe83a851a5db3803d5013bbc20c6234f176b2c52ae36fdb53d28b33d93 Done ! $ java -jar blockchain-java-jar-with-dependencies.jar send -from 1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUD -to 17crpQoWy7TEkY9UPjZ3Qt9Fc2rWPUt8KX -amount 6 Elapsed Time: 30.887 seconds correct hash Hex: 00005fd36a2609b43fd940577f93b8622e88e854f5ccfd70e113f763b6df69f7 Success!$ java -jar blockchain-java-jar-with-dependencies.jar send -from 1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUD -to 12L868QZW1ySYzf2oT5ha9py9M5JrSRhvT -amount 3 Elapsed Time: 45.267 seconds correct hash Hex: 00009fd7c59b830b60ec21ade7672921d2fb0962a1b06a42c245450e47582a13 Success!$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address 1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUD Balance of '1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUD': 21$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address 17crpQoWy7TEkY9UPjZ3Qt9Fc2rWPUt8KX Balance of '17crpQoWy7TEkY9UPjZ3Qt9Fc2rWPUt8KX': 6$ java -jar blockchain-java-jar-with-dependencies.jar getbalance -address 12L868QZW1ySYzf2oT5ha9py9M5JrSRhvT Balance of '12L868QZW1ySYzf2oT5ha9py9M5JrSRhvT': 3
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUD?這個地址一共收到了三份獎勵:

    • 第一次是開采創(chuàng)世區(qū)塊;

    • 第二次是開采區(qū)塊:00005fd36a2609b43fd940577f93b8622e88e854f5ccfd70e113f763b6df69f7

    • 第三次是開采區(qū)塊:00009fd7c59b830b60ec21ade7672921d2fb0962a1b06a42c245450e47582a13

    Merkle Tree

    Merkle Tree(默克爾樹) 是這篇文章中我們需要重點討論的一個機制。

    正如我前面提到的那樣,整個比特幣的數(shù)據(jù)庫占到了大約140G的磁盤空間。由于比特幣的分布式特性,網(wǎng)絡(luò)中的每一個節(jié)點必須是獨立且自給自足的。每個比特幣節(jié)點都是路由、區(qū)塊鏈數(shù)據(jù)庫、挖礦、錢包服務(wù)的功能集合。每個節(jié)點都參與全網(wǎng)絡(luò)的路由功能,同時也可能包含其他功能。每個節(jié)點都參與驗證并傳播交易及區(qū)塊信息,發(fā)現(xiàn)并維持與對等節(jié)點的連接。一個全節(jié)點(full node)包括以下四個功能:

    隨著越來越多的人開始使用比特幣,這條規(guī)則開始變得越來越難以遵循:讓每一個人都去運行一個完整的節(jié)點不太現(xiàn)實。在中本聰發(fā)布的?比特幣白皮書?中,針對這個問題提出了一個解決方案:Simplified Payment Verification (SPV)(簡易支付驗證)。SPV是比特幣的輕量級節(jié)點,它不需要下載所有的區(qū)塊鏈數(shù)據(jù),也不需要驗證區(qū)塊和交易數(shù)據(jù)。相反,當(dāng)SPV想要驗證一筆交易的有效性時,它會從它所連接的全節(jié)點上檢索所需要的一些數(shù)據(jù)。這種機制保證了在只有一個全節(jié)點的情況,可以運行多個SPV輕錢包節(jié)點。

    更多有關(guān)SPV的介紹,請查看:《精通比特幣(第二版)》第八章

    為了使SPV成為可能,就需要有一種方法在沒有全量下載區(qū)塊數(shù)據(jù)的情況下,來檢查一個區(qū)塊是否包含了某筆交易。這就是 Merkle Tree 發(fā)揮作用的地方了。

    比特幣中所使用的Merkle Tree是為了獲得交易的Hash值,隨后這個已經(jīng)被Pow(工作量證明)系統(tǒng)認(rèn)可了的Hash值會被保存到區(qū)塊頭中。到目前為止,我們只是簡單地計算了一個區(qū)塊中每筆交易的Hash值,然后在準(zhǔn)備Pow數(shù)據(jù)時,再對這些交易進行?SHA-256?計算。雖然這是一個用于獲取區(qū)塊交易唯一表示的一個不錯的途徑,但是它不具有到 Merkle Tree的優(yōu)點。

    來看一下Merkle Tree的結(jié)構(gòu):

    每一個區(qū)塊都會構(gòu)建一個Merkle Tree,它從最底部的葉子節(jié)點開始往上構(gòu)建,每一個交易的Hash就是一個葉子節(jié)點(比特幣中用的雙SHA256算法)。葉子節(jié)點的數(shù)量必須是偶數(shù)個,但是并不是每一個區(qū)塊都能包含偶數(shù)筆交易數(shù)據(jù)。如果存在奇數(shù)筆交易數(shù)據(jù),那么最后一筆交易數(shù)據(jù)將會被復(fù)制一份(這僅僅發(fā)生在Merkle Tree中,而不是區(qū)塊中)。

    從下往上移動,葉子節(jié)點成對分組,它們的Hash值被連接到一起,并且在此基礎(chǔ)上再次計算出新的Hash值。新的Hash 形成新的樹節(jié)點。這個過程不斷地被重復(fù),直到最后僅剩一個被稱為根節(jié)點的樹節(jié)點。這個根節(jié)點的Hash就是區(qū)塊中交易數(shù)據(jù)們的唯一代表,它會被保存到區(qū)塊頭中,并被用于參與POW系統(tǒng)的計算。

    Merkle樹的好處是節(jié)點可以在不下載整個塊的情況下驗證某筆交易的合法性。 為此,只需要交易Hash,Merkle樹根Hash和Merkle路徑。

    Merkle Tree代碼實現(xiàn)如下:

    package one.wangwei.blockchain.transaction;import com.google.common.collect.Lists; import lombok.Data; import one.wangwei.blockchain.util.ByteUtils; import org.apache.commons.codec.digest.DigestUtils;import java.util.List;/*** 默克爾樹** @author wangwei* @date 2018/04/15*/ @Data public class MerkleTree {/*** 根節(jié)點*/private Node root;/*** 葉子節(jié)點Hash*/private byte[][] leafHashes;public MerkleTree(byte[][] leafHashes) {constructTree(leafHashes);}/*** 從底部葉子節(jié)點開始往上構(gòu)建整個Merkle Tree** @param leafHashes*/private void constructTree(byte[][] leafHashes) {if (leafHashes == null || leafHashes.length < 1) {throw new RuntimeException("ERROR:Fail to construct merkle tree ! leafHashes data invalid ! ");}this.leafHashes = leafHashes;List<Node> parents = bottomLevel(leafHashes);while (parents.size() > 1) {parents = internalLevel(parents);}root = parents.get(0);}/*** 構(gòu)建一個層級節(jié)點** @param children* @return*/private List<Node> internalLevel(List<Node> children) {List<Node> parents = Lists.newArrayListWithCapacity(children.size() / 2);for (int i = 0; i < children.size() - 1; i += 2) {Node child1 = children.get(i);Node child2 = children.get(i + 1);Node parent = constructInternalNode(child1, child2);parents.add(parent);}// 內(nèi)部節(jié)點奇數(shù)個,只對left節(jié)點進行計算if (children.size() % 2 != 0) {Node child = children.get(children.size() - 1);Node parent = constructInternalNode(child, null);parents.add(parent);}return parents;}/*** 底部節(jié)點構(gòu)建** @param hashes* @return*/private List<Node> bottomLevel(byte[][] hashes) {List<Node> parents = Lists.newArrayListWithCapacity(hashes.length / 2);for (int i = 0; i < hashes.length - 1; i += 2) {Node leaf1 = constructLeafNode(hashes[i]);Node leaf2 = constructLeafNode(hashes[i + 1]);Node parent = constructInternalNode(leaf1, leaf2);parents.add(parent);}if (hashes.length % 2 != 0) {Node leaf = constructLeafNode(hashes[hashes.length - 1]);// 奇數(shù)個節(jié)點的情況,復(fù)制最后一個節(jié)點Node parent = constructInternalNode(leaf, leaf);parents.add(parent);}return parents;}/*** 構(gòu)建葉子節(jié)點** @param hash* @return*/private static Node constructLeafNode(byte[] hash) {Node leaf = new Node();leaf.hash = hash;return leaf;}/*** 構(gòu)建內(nèi)部節(jié)點** @param leftChild* @param rightChild* @return*/private Node constructInternalNode(Node leftChild, Node rightChild) {Node parent = new Node();if (rightChild == null) {parent.hash = leftChild.hash;} else {parent.hash = internalHash(leftChild.hash, rightChild.hash);}parent.left = leftChild;parent.right = rightChild;return parent;}/*** 計算內(nèi)部節(jié)點Hash** @param leftChildHash* @param rightChildHash* @return*/private byte[] internalHash(byte[] leftChildHash, byte[] rightChildHash) {byte[] mergedBytes = ByteUtils.merge(leftChildHash, rightChildHash);return DigestUtils.sha256(mergedBytes);}/*** Merkle Tree節(jié)點*/@Datapublic static class Node {private byte[] hash;private Node left;private Node right;}}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156

    然后修改?Block.hashTransaction?接口:

    public class Block {... /*** 對區(qū)塊中的交易信息進行Hash計算** @return*/public byte[] hashTransaction() {byte[][] txIdArrays = new byte[this.getTransactions().length][];for (int i = 0; i < this.getTransactions().length; i++) {txIdArrays[i] = this.getTransactions()[i].hash();}return new MerkleTree(txIdArrays).getRoot().getHash();}...}
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    MerkleTree的根節(jié)點的Hash值,就是區(qū)塊中交易信息的唯一代表。

    小結(jié)

    這一節(jié)我們主要是對前面的交易機制做了進一步的優(yōu)化,加入UTXO池和Merkle Tree機制。

    資料

  • 源碼:https://github.com/wangweiX/blockchain-java/tree/part6-transaction2
  • The UTXO Set
  • UTXO set statistics
  • Merkle Tree
  • Why every Bitcoin user should understand “SPV security”
  • Script
  • “Ultraprune” Bitcoin Core commit
  • Smart contracts and Bitcoin
  • 總結(jié)

    以上是生活随笔為你收集整理的基于Java语言构建区块链(六)—— 交易(Merkle Tree)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。