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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

修改linq结果集_UTXO集优化

發布時間:2025/3/19 编程问答 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 修改linq结果集_UTXO集优化 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在這個系列文章的一開始,我們就提到了,區塊鏈是一個分布式數據庫。不過在之前的文章中,我們選擇性地跳過了“分布式”這個部分,而是將注意力都放到了“數據庫”部分。到目前為止,我們幾乎已經實現了一個區塊鏈數據庫的所有元素(基本原型,工作量證明,持久化,命令行接口,交易,地址,數字簽名等)。今天,我們將會分析之前跳過的一些機制。而在下一篇文章中,我們將會開始討論區塊鏈的分布式特性。

1. 課程目標

  • 知道什么是獎勵

  • 知道什么是UTXO集

  • 學會UTXO集優化的原理

  • 項目中修改代碼實現UTXO集查詢余額

  • 項目中修改代碼實現UTXO集進行轉賬交易

  • 2. 項目代碼及效果展示

    2.1 項目代碼結構

    2.2 項目運行結果

    創建錢包地址,創建創世區塊和使用UTXOSet優化后的轉賬,查詢余額效果圖如下:

    3. 創建項目

    3.1 創建工程

    打開IntelliJ IDEA的工作空間,將上一個項目代碼目錄part7_Signature,復制為part8_Transaction2。

    然后打開IntelliJ IDEA開發工具。

    打開工程:part8_Transaction2,并刪除target目錄。然后進行以下修改:

    step1:先將項目重新命名為:part8_Transaction2。
    step2:修改pom.xml配置文件。
    改為:<artifactId>part8_Transaction2artifactId>標簽
    改為:<name>part8_Transaction2 Maven Webappname>

    3.2 代碼實現

    3.2.1 修改java文件:RocksDBUtils.java

    打開cldy.hanru.blockchain.store包,修改RocksDBUtils.java文件:

    修改步驟:

    修改步驟:
    step1:添加String CHAINSTATE_BUCKET_KEY = "chainstate";
    step2:添加private Map<String, byte[]> chainstateBucket;
    step3:添加initChainStateBucket()方法
    step4:添加cleanChainStateBucket()方法
    step5:添加putUTXOs()
    step6:添加getUTXOs()
    step7:添加deleteUTXOs()
    step8:修改RocksDBUtils()構造函數

    修改完后代碼如下:

    package cldy.hanru.blockchain.store;


    import cldy.hanru.blockchain.block.Block;
    import cldy.hanru.blockchain.transaction.TXOutput;
    import cldy.hanru.blockchain.util.SerializeUtils;
    import lombok.Getter;
    import lombok.extern.slf4j.Slf4j;
    import org.rocksdb.RocksDB;
    import org.rocksdb.RocksDBException;

    import java.util.HashMap;
    import java.util.Map;

    /**
    * 數據庫存儲的工具類
    * @author hanru
    */
    @Slf4j
    public class RocksDBUtils {
    /**
    * 區塊鏈數據文件
    */
    private static final String DB_FILE = "blockchain.db";
    /**
    * 區塊桶前綴
    */
    private static final String BLOCKS_BUCKET_KEY = "blocks";
    /**
    * 鏈狀態桶Key
    */
    private static final String CHAINSTATE_BUCKET_KEY = "chainstate";

    /**
    * 最新一個區塊的hash
    */
    private static final String LAST_BLOCK_KEY = "l";

    private volatile static RocksDBUtils instance;
    /**
    * 獲取RocksDBUtils的單例
    * @return
    */
    public static RocksDBUtils getInstance() {
    if (instance == null) {
    synchronized (RocksDBUtils.class) {
    if (instance == null) {
    instance = new RocksDBUtils();
    }
    }
    }
    return instance;
    }


    private RocksDBUtils() {
    openDB();
    initBlockBucket();
    initChainStateBucket();
    }
    private RocksDB db;

    /**
    * block buckets
    */
    private Mapbyte[]> blocksBucket;/**
    * 打開數據庫
    */private void openDB() {try {
    db = RocksDB.open(DB_FILE);
    } catch (RocksDBException e) {throw new RuntimeException("打開數據庫失敗。。 ! ", e);
    }
    }/**
    * 初始化 blocks 數據桶
    */private void initBlockBucket() {try {//byte[] blockBucketKey = SerializeUtils.serialize(BLOCKS_BUCKET_KEY);byte[] blockBucketBytes = db.get(blockBucketKey);if (blockBucketBytes != null) {
    blocksBucket = (Map) SerializeUtils.deserialize(blockBucketBytes);
    } else {
    blocksBucket = new HashMap<>();
    db.put(blockBucketKey, SerializeUtils.serialize(blocksBucket));
    }
    } catch (RocksDBException e) {throw new RuntimeException("初始化block的bucket失敗。。! ", e);
    }
    }/**
    * 保存區塊
    *
    * @param block
    */public void putBlock(Block block) {try {
    blocksBucket.put(block.getHash(), SerializeUtils.serialize(block));
    db.put(SerializeUtils.serialize(BLOCKS_BUCKET_KEY), SerializeUtils.serialize(blocksBucket));
    } catch (RocksDBException e) {throw new RuntimeException("存儲區塊失敗。。 ", e);
    }
    }/**
    * 查詢區塊
    *
    * @param blockHash
    * @return
    */public Block getBlock(String blockHash) {return (Block) SerializeUtils.deserialize(blocksBucket.get(blockHash));
    }/**
    * 保存最新一個區塊的Hash值
    *
    * @param tipBlockHash
    */public void putLastBlockHash(String tipBlockHash) {try {
    blocksBucket.put(LAST_BLOCK_KEY, SerializeUtils.serialize(tipBlockHash));
    db.put(SerializeUtils.serialize(BLOCKS_BUCKET_KEY), SerializeUtils.serialize(blocksBucket));
    } catch (RocksDBException e) {throw new RuntimeException("數據庫存儲最新區塊hash失敗。。 ", e);
    }
    }/**
    * 查詢最新一個區塊的Hash值
    *
    * @return
    */public String getLastBlockHash() {byte[] lastBlockHashBytes = blocksBucket.get(LAST_BLOCK_KEY);if (lastBlockHashBytes != null) {return (String) SerializeUtils.deserialize(lastBlockHashBytes);
    }return "";
    }/**
    * 關閉數據庫
    */public void closeDB() {try {
    db.close();
    } catch (Exception e) {throw new RuntimeException("關閉數據庫失敗。。 ", e);
    }
    }/**
    * chainstate buckets
    */
    @Getterprivate Mapbyte[]> chainstateBucket;/**
    * 初始化 blocks 數據桶
    */private void initChainStateBucket() {try {byte[] chainstateBucketKey = SerializeUtils.serialize(CHAINSTATE_BUCKET_KEY);byte[] chainstateBucketBytes = db.get(chainstateBucketKey);if (chainstateBucketBytes != null) {
    chainstateBucket = (Map) SerializeUtils.deserialize(chainstateBucketBytes);
    } else {
    chainstateBucket = new HashMap<>();
    db.put(chainstateBucketKey, SerializeUtils.serialize(chainstateBucket));
    }
    } catch (RocksDBException e) {
    log.error("Fail to init chainstate bucket ! ", e);throw new RuntimeException("Fail to init chainstate bucket ! ", e);
    }
    }/**
    * 清空chainstate bucket
    */public void cleanChainStateBucket() {try {
    chainstateBucket.clear();
    } catch (Exception e) {
    log.error("Fail to clear chainstate bucket ! ", e);throw new RuntimeException("Fail to clear chainstate bucket ! ", e);
    }
    }/**
    * 保存UTXO數據
    *
    * @param key 交易ID
    * @param utxos UTXOs
    */public void putUTXOs(String key, TXOutput[] utxos) {try {
    chainstateBucket.put(key, SerializeUtils.serialize(utxos));
    db.put(SerializeUtils.serialize(CHAINSTATE_BUCKET_KEY), SerializeUtils.serialize(chainstateBucket));
    } catch (Exception e) {
    log.error("Fail to put UTXOs into chainstate bucket ! key=" + key, e);throw new RuntimeException("Fail to put UTXOs into chainstate bucket ! key=" + key, e);
    }
    }/**
    * 查詢UTXO數據
    *
    * @param key 交易ID
    */public TXOutput[] getUTXOs(String key) {byte[] utxosByte = chainstateBucket.get(key);if (utxosByte != null) {return (TXOutput[]) SerializeUtils.deserialize(utxosByte);
    }return null;
    }/**
    * 刪除 UTXO 數據
    *
    * @param key 交易ID
    */public void deleteUTXOs(String key) {try {
    chainstateBucket.remove(key);
    db.put(SerializeUtils.serialize(CHAINSTATE_BUCKET_KEY), SerializeUtils.serialize(chainstateBucket));
    } catch (Exception e) {
    log.error("Fail to delete UTXOs by key ! key=" + key, e);throw new RuntimeException("Fail to delete UTXOs by key ! key=" + key, e);
    }
    }
    }

    3.2.2 創建UTXOSet.java文件

    打開cldy.hanru.blockchain.transaction包,創建UTXOSet.java文件。

    添加代碼如下:

    package cldy.hanru.blockchain.transaction;

    import cldy.hanru.blockchain.block.Block;
    import cldy.hanru.blockchain.block.Blockchain;
    import cldy.hanru.blockchain.store.RocksDBUtils;
    import cldy.hanru.blockchain.util.SerializeUtils;
    import lombok.AllArgsConstructor;
    import lombok.NoArgsConstructor;
    import lombok.Synchronized;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.codec.binary.Hex;
    import org.apache.commons.lang3.ArrayUtils;

    import java.util.HashMap;
    import java.util.Map;

    /**
    * 未被花費的交易輸出池
    * @author hanru
    */
    @NoArgsConstructor
    @AllArgsConstructor
    @Slf4j
    public class UTXOSet {

    private Blockchain blockchain;


    /**
    * 重建 UTXO 池索引
    */
    @Synchronized
    public void reIndex() {
    log.info("Start to reIndex UTXO set !");
    RocksDBUtils.getInstance().cleanChainStateBucket();
    Map allUTXOs = blockchain.findAllUTXOs();for (Map.Entry entry : allUTXOs.entrySet()) {
    RocksDBUtils.getInstance().putUTXOs(entry.getKey(), entry.getValue());
    }
    log.info("ReIndex UTXO set finished ! ");
    }/**
    * 查找錢包地址對應的所有UTXO
    *
    * @param pubKeyHash 錢包公鑰Hash
    * @return
    */public TXOutput[] findUTXOs(byte[] pubKeyHash) {
    TXOutput[] utxos = {};
    Mapbyte[]> 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;
    }/**
    * 尋找能夠花費的交易
    *
    * @param pubKeyHash 錢包公鑰Hash
    * @param amount 花費金額
    */public SpendableOutputResult findSpendableOutputs(byte[] pubKeyHash, int amount) {
    Mapint[]> unspentOuts = new HashMap<>();int accumulated = 0;
    Mapbyte[]> chainstateBucket = RocksDBUtils.getInstance().getChainstateBucket();for (Map.Entrybyte[]> 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);
    }/**
    * 更新UTXO池
    * 當一個新的區塊產生時,需要去做兩件事情:
    * 1)從UTXO池中移除花費掉了的交易輸出;
    * 2)保存新的未花費交易輸出;
    *
    * @param tipBlock 最新的區塊
    */@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()) {// 根據交易輸入排查出剩余未被使用的交易輸出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);
    }
    }
    }

    3.2.3 修改Blockchain.java

    打開cldy.hanru.blockchain.block包,修改Blockchain.java文件:

    修改步驟:

    修改步驟:
    step1:修改getAllSpentTXOs()方法
    step2:添加findAllUTXOs()方法
    step3:刪除findUnspentTransactions()
    step4:刪除findUTXO()
    step5:刪除findSpendableOutputs()
    step6:修改mineBlock(),添加返回值
    step7:修改verifyTransactions()方法,添加判斷是否是coinbase交易

    修改完后代碼如下:

    package cldy.hanru.blockchain.block;


    import cldy.hanru.blockchain.store.RocksDBUtils;
    import cldy.hanru.blockchain.transaction.SpendableOutputResult;
    import cldy.hanru.blockchain.transaction.TXInput;
    import cldy.hanru.blockchain.transaction.TXOutput;
    import cldy.hanru.blockchain.transaction.Transaction;
    import cldy.hanru.blockchain.util.ByteUtils;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import org.apache.commons.codec.binary.Hex;
    import org.apache.commons.lang3.ArrayUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;

    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    /**
    * 區塊鏈
    *
    * @author hanru
    */
    @Data
    @AllArgsConstructor
    public class Blockchain {


    /**
    * 最后一個區塊的hash
    */
    private String lastBlockHash;


    /**
    * 創建區塊鏈,createBlockchain
    *
    * @param address
    * @return
    */
    public static Blockchain createBlockchain(String address) {

    String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash();
    if (StringUtils.isBlank(lastBlockHash)) {
    //對應的bucket不存在,說明是第一次獲取區塊鏈實例
    // 創建 coinBase 交易
    Transaction coinbaseTX = Transaction.newCoinbaseTX(address, "");
    Block genesisBlock = Block.newGenesisBlock(coinbaseTX);
    lastBlockHash = genesisBlock.getHash();
    RocksDBUtils.getInstance().putBlock(genesisBlock);
    RocksDBUtils.getInstance().putLastBlockHash(lastBlockHash);

    }
    return new Blockchain(lastBlockHash);
    }

    /**
    * 根據block,添加區塊
    *
    * @param block
    */
    public void addBlock(Block block) {

    RocksDBUtils.getInstance().putLastBlockHash(block.getHash());
    RocksDBUtils.getInstance().putBlock(block);
    this.lastBlockHash = block.getHash();

    }


    /**
    * 區塊鏈迭代器:內部類
    */
    public class BlockchainIterator {

    /**
    * 當前區塊的hash
    */
    private String currentBlockHash;

    /**
    * 構造函數
    *
    * @param currentBlockHash
    */
    public BlockchainIterator(String currentBlockHash) {
    this.currentBlockHash = currentBlockHash;
    }

    /**
    * 判斷是否有下一個區塊
    *
    * @return
    */
    public boolean hashNext() {
    if (ByteUtils.ZERO_HASH.equals(currentBlockHash)) {
    return false;
    }
    Block lastBlock = RocksDBUtils.getInstance().getBlock(currentBlockHash);
    if (lastBlock == null) {
    return false;
    }
    // 如果是創世區塊
    if (ByteUtils.ZERO_HASH.equals(lastBlock.getPrevBlockHash())) {
    return true;
    }
    return RocksDBUtils.getInstance().getBlock(lastBlock.getPrevBlockHash()) != null;
    }


    /**
    * 迭代獲取區塊
    *
    * @return
    */
    public Block next() {
    Block currentBlock = RocksDBUtils.getInstance().getBlock(currentBlockHash);
    if (currentBlock != null) {
    this.currentBlockHash = currentBlock.getPrevBlockHash();
    return currentBlock;
    }
    return null;
    }
    }

    /**
    * 添加方法,用于獲取迭代器實例
    *
    * @return
    */
    public BlockchainIterator getBlockchainIterator() {
    return new BlockchainIterator(lastBlockHash);
    }


    /**
    * 打包交易,進行挖礦
    *
    * @param transactions
    */
    public Block mineBlock(List transactions) throws Exception {
    // 挖礦前,先驗證交易記錄
    for (Transaction tx : transactions) {
    if (!this.verifyTransactions(tx)) {
    throw new Exception("ERROR: Fail to mine block ! Invalid transaction ! ");
    }
    }
    String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash();
    Block lastBlock = RocksDBUtils.getInstance().getBlock(lastBlockHash);
    if (lastBlockHash == null) {
    throw new Exception("ERROR: Fail to get last block hash ! ");
    }
    Block block = Block.newBlock(lastBlockHash, transactions,lastBlock.getHeight()+1);
    this.addBlock(block);
    return block;

    }

    /**
    * 依據交易ID查詢交易信息
    *
    * @param txId 交易ID
    * @return
    */
    private Transaction findTransaction(byte[] txId) throws Exception {

    for (BlockchainIterator iterator = this.getBlockchainIterator(); iterator.hashNext(); ) {
    Block block = iterator.next();
    for (Transaction tx : block.getTransactions()) {
    if (Arrays.equals(tx.getTxId(), txId)) {
    return tx;
    }
    }
    }
    throw new Exception("ERROR: Can not found tx by txId ! ");
    }

    /**
    * 從 DB 從恢復區塊鏈數據
    *
    * @return
    * @throws Exception
    */
    public static Blockchain initBlockchainFromDB() throws Exception {
    String lastBlockHash = RocksDBUtils.getInstance().getLastBlockHash();
    if (lastBlockHash == null) {
    throw new Exception("ERROR: Fail to init blockchain from db. ");
    }
    return new Blockchain(lastBlockHash);
    }


    /**
    * 進行交易簽名
    *
    * @param tx 交易數據
    * @param privateKey 私鑰
    */
    public void signTransaction(Transaction tx, BCECPrivateKey privateKey) throws Exception {
    // 先來找到這筆新的交易中,交易輸入所引用的前面的多筆交易的數據
    Map prevTxMap = new HashMap<>();for (TXInput txInput : tx.getInputs()) {
    Transaction prevTx = this.findTransaction(txInput.getTxId());
    prevTxMap.put(Hex.encodeHexString(txInput.getTxId()), prevTx);
    }
    tx.sign(privateKey, prevTxMap);
    }/**
    * 交易簽名驗證
    *
    * @param tx
    */private boolean verifyTransactions(Transaction tx) throws Exception {if (tx.isCoinbase()) {return true;
    }
    Map prevTx = new HashMap<>();for (TXInput txInput : tx.getInputs()) {
    Transaction transaction = this.findTransaction(txInput.getTxId());
    prevTx.put(Hex.encodeHexString(txInput.getTxId()), transaction);
    }try {return tx.verify(prevTx);
    } catch (Exception e) {throw new Exception("Fail to verify transaction ! transaction invalid ! ");
    }
    }/**
    * 從交易輸入中查詢區塊鏈中所有已被花費了的交易輸出
    *
    * @return 交易ID以及對應的交易輸出下標地址
    */private Mapint[]> getAllSpentTXOs() {// 定義TxId ——> spentOutIndex[],存儲交易ID與已被花費的交易輸出數組索引值
    Mapint[]> spentTXOs = new HashMap<>();for (BlockchainIterator blockchainIterator = this.getBlockchainIterator(); blockchainIterator.hashNext(); ) {
    Block block = blockchainIterator.next();for (Transaction transaction : block.getTransactions()) {// 如果是 coinbase 交易,直接跳過,因為它不存在引用前一個區塊的交易輸出if (transaction.isCoinbase()) {continue;
    }for (TXInput txInput : transaction.getInputs()) {
    String inTxId = Hex.encodeHexString(txInput.getTxId());int[] spentOutIndexArray = spentTXOs.get(inTxId);if (spentOutIndexArray == null) {
    spentTXOs.put(inTxId, new int[]{txInput.getTxOutputIndex()});
    } else {
    spentOutIndexArray = ArrayUtils.add(spentOutIndexArray, txInput.getTxOutputIndex());
    }
    spentTXOs.put(inTxId, spentOutIndexArray);
    }// }
    }
    }return spentTXOs;
    }/**
    * 查找所有的 unspent transaction outputs
    *
    * @return
    */public Map findAllUTXOs() {
    Mapint[]> allSpentTXOs = this.getAllSpentTXOs();
    Map allUTXOs = new HashMap<>();// 再次遍歷所有區塊中的交易輸出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);
    TXOutput[] txOutputs = transaction.getOutputs();for (int outIndex = 0; outIndex < txOutputs.length; outIndex++) {if (spentOutIndexArray != null && ArrayUtils.contains(spentOutIndexArray, outIndex)) {continue;
    }
    TXOutput[] UTXOArray = allUTXOs.get(txId);if (UTXOArray == null) {
    UTXOArray = new TXOutput[]{txOutputs[outIndex]};
    } else {
    UTXOArray = ArrayUtils.add(UTXOArray, txOutputs[outIndex]);
    }
    allUTXOs.put(txId, UTXOArray);
    }
    }
    }return allUTXOs;
    }
    }

    3.2.4 修改Transaction.java文件

    打開cldy.hanru.blockchain.block包,修改Transaction.java文件。

    修改步驟:

    修改步驟:
    step1:添加時間戳字段
    step2:修改newUTXOTransaction方法,從utxoSet中查找轉賬要使用的utxo。
    step3:修改:newCoinbaseTX()

    修改完后代碼如下:

    package cldy.hanru.blockchain.transaction;


    import cldy.hanru.blockchain.block.Blockchain;
    import cldy.hanru.blockchain.util.AddressUtils;
    import cldy.hanru.blockchain.util.SerializeUtils;
    import cldy.hanru.blockchain.wallet.Wallet;
    import cldy.hanru.blockchain.wallet.WalletUtils;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import org.apache.commons.codec.binary.Hex;
    import org.apache.commons.codec.digest.DigestUtils;
    import org.apache.commons.lang3.ArrayUtils;
    import org.apache.commons.lang3.StringUtils;
    import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
    import org.bouncycastle.jce.ECNamedCurveTable;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    import org.bouncycastle.jce.spec.ECParameterSpec;
    import org.bouncycastle.jce.spec.ECPublicKeySpec;
    import org.bouncycastle.math.ec.ECPoint;

    import java.math.BigInteger;
    import java.security.KeyFactory;
    import java.security.PublicKey;
    import java.security.Security;
    import java.security.Signature;
    import java.util.Arrays;
    import java.util.Iterator;
    import java.util.Map;

    /**
    * @author hanru
    */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class Transaction {
    private static final int SUBSIDY = 10;

    /**
    * 交易的Hash
    */
    private byte[] txId;
    /**
    * 交易輸入
    */
    private TXInput[] inputs;
    /**
    * 交易輸出
    */
    private TXOutput[] outputs;


    /**
    * 創建日期
    */
    private long createTime;

    /**
    * 設置交易ID
    */
    private void setTxId() {
    this.setTxId(DigestUtils.sha256(SerializeUtils.serialize(this)));
    }




    /**
    * 要簽名的數據
    * @return
    */
    public byte[] getData() {
    // 使用序列化的方式對Transaction對象進行深度復制
    byte[] serializeBytes = SerializeUtils.serialize(this);
    Transaction copyTx = (Transaction) SerializeUtils.deserialize(serializeBytes);
    copyTx.setTxId(new byte[]{});
    return DigestUtils.sha256(SerializeUtils.serialize(copyTx));
    }

    /**
    * 創建CoinBase交易
    *
    * @param to 收賬的錢包地址
    * @param data 解鎖腳本數據
    * @return
    */
    public static Transaction newCoinbaseTX(String to, String data) {
    if (StringUtils.isBlank(data)) {
    data = String.format("Reward to '%s'", to);
    }
    // 創建交易輸入

    TXInput txInput = new TXInput(new byte[]{}, -1, null, data.getBytes());
    // 創建交易輸出
    TXOutput txOutput = TXOutput.newTXOutput(SUBSIDY, to);
    // 創建交易
    Transaction tx = new Transaction(null, new TXInput[]{txInput}, new TXOutput[]{txOutput}, System.currentTimeMillis());
    // 設置交易ID
    tx.setTxId();

    return tx;
    }

    /**
    * 從 from 向 to 支付一定的 amount 的金額
    *
    * @param from 支付錢包地址
    * @param to 收款錢包地址
    * @param amount 交易金額
    * @param blockchain 區塊鏈
    * @return
    */
    public static Transaction newUTXOTransaction(String from, String to, int amount, Blockchain blockchain) throws Exception {

    // 獲取錢包
    Wallet senderWallet = WalletUtils.getInstance().getWallet(from);
    byte[] pubKey = senderWallet.getPublicKey();
    byte[] pubKeyHash = AddressUtils.ripeMD160Hash(pubKey);

    SpendableOutputResult result = new UTXOSet(blockchain).findSpendableOutputs(pubKeyHash, amount);
    int accumulated = result.getAccumulated();
    Mapint[]> unspentOuts = result.getUnspentOuts();if (accumulated < amount) {throw new Exception("ERROR: Not enough funds");
    }
    Iteratorint[]>> iterator = unspentOuts.entrySet().iterator();
    TXInput[] txInputs = {};while (iterator.hasNext()) {
    Map.Entryint[]> entry = iterator.next();
    String txIdStr = entry.getKey();int[] outIdxs = entry.getValue();byte[] txId = Hex.decodeHex(txIdStr);for (int outIndex : outIdxs) {
    txInputs = ArrayUtils.add(txInputs, new TXInput(txId, outIndex, null, pubKey));
    }
    }
    TXOutput[] txOutput = {};
    txOutput = ArrayUtils.add(txOutput, TXOutput.newTXOutput(amount, to));if (accumulated > amount) {
    txOutput = ArrayUtils.add(txOutput, TXOutput.newTXOutput((accumulated - amount), from));
    }
    Transaction newTx = new Transaction(null, txInputs, txOutput, System.currentTimeMillis());
    newTx.setTxId();// 進行交易簽名
    blockchain.signTransaction(newTx, senderWallet.getPrivateKey());return newTx;
    }/**
    * 是否為 Coinbase 交易
    *
    * @return
    */public boolean isCoinbase() {return this.getInputs().length == 1
    && this.getInputs()[0].getTxId().length == 0
    && this.getInputs()[0].getTxOutputIndex() == -1;
    }/**
    * 創建用于簽名的交易數據副本,交易輸入的 signature 和 pubKey 需要設置為null
    *
    * @return
    */public Transaction trimmedCopy() {
    TXInput[] tmpTXInputs = new TXInput[this.getInputs().length];for (int i = 0; i < this.getInputs().length; i++) {
    TXInput txInput = this.getInputs()[i];
    tmpTXInputs[i] = new TXInput(txInput.getTxId(), txInput.getTxOutputIndex(), null, null);
    }
    TXOutput[] tmpTXOutputs = new TXOutput[this.getOutputs().length];for (int i = 0; i < this.getOutputs().length; i++) {
    TXOutput txOutput = this.getOutputs()[i];
    tmpTXOutputs[i] = new TXOutput(txOutput.getValue(), txOutput.getPubKeyHash());
    }return new Transaction(this.getTxId(), tmpTXInputs, tmpTXOutputs, this.getCreateTime());
    }/**
    * 簽名
    *
    * @param privateKey 私鑰
    * @param prevTxMap 前面多筆交易集合
    */public void sign(BCECPrivateKey privateKey, Map prevTxMap) throws Exception {// coinbase 交易信息不需要簽名,因為它不存在交易輸入信息if (this.isCoinbase()) {return;
    }// 再次驗證一下交易信息中的交易輸入是否正確,也就是能否查找對應的交易數據for (TXInput txInput : this.getInputs()) {if (prevTxMap.get(Hex.encodeHexString(txInput.getTxId())) == null) {throw new Exception("ERROR: Previous transaction is not correct");
    }
    }// 創建用于簽名的交易信息的副本
    Transaction txCopy = this.trimmedCopy();
    Security.addProvider(new BouncyCastleProvider());
    Signature ecdsaSign = Signature.getInstance("SHA256withECDSA", BouncyCastleProvider.PROVIDER_NAME);
    ecdsaSign.initSign(privateKey);for (int i = 0; i < txCopy.getInputs().length; i++) {
    TXInput txInputCopy = txCopy.getInputs()[i];// 獲取交易輸入TxID對應的交易數據
    Transaction prevTx = prevTxMap.get(Hex.encodeHexString(txInputCopy.getTxId()));// 獲取交易輸入所對應的上一筆交易中的交易輸出
    TXOutput prevTxOutput = prevTx.getOutputs()[txInputCopy.getTxOutputIndex()];
    txInputCopy.setPubKey(prevTxOutput.getPubKeyHash());
    txInputCopy.setSignature(null);// 得到要簽名的數據byte[] signData = txCopy.getData();
    txInputCopy.setPubKey(null);// 對整個交易信息僅進行簽名
    ecdsaSign.update(signData);byte[] signature = ecdsaSign.sign();// 將整個交易數據的簽名賦值給交易輸入,因為交易輸入需要包含整個交易信息的簽名// 注意是將得到的簽名賦值給原交易信息中的交易輸入this.getInputs()[i].setSignature(signature);
    }
    }/**
    * 驗證交易信息
    *
    * @param prevTxMap 前面多筆交易集合
    * @return
    */public boolean verify(Map prevTxMap) throws Exception {// coinbase 交易信息不需要簽名,也就無需驗證if (this.isCoinbase()) {return true;
    }// 再次驗證一下交易信息中的交易輸入是否正確,也就是能否查找對應的交易數據for (TXInput txInput : this.getInputs()) {if (prevTxMap.get(Hex.encodeHexString(txInput.getTxId())) == null) {throw new Exception("ERROR: Previous transaction is not correct");
    }
    }// 創建用于簽名驗證的交易信息的副本
    Transaction txCopy = this.trimmedCopy();
    Security.addProvider(new BouncyCastleProvider());
    ECParameterSpec ecParameters = ECNamedCurveTable.getParameterSpec("secp256k1");
    KeyFactory keyFactory = KeyFactory.getInstance("ECDSA", BouncyCastleProvider.PROVIDER_NAME);
    Signature ecdsaVerify = Signature.getInstance("SHA256withECDSA", BouncyCastleProvider.PROVIDER_NAME);for (int i = 0; i < this.getInputs().length; i++) {
    TXInput txInput = this.getInputs()[i];// 獲取交易輸入TxID對應的交易數據
    Transaction prevTx = prevTxMap.get(Hex.encodeHexString(txInput.getTxId()));// 獲取交易輸入所對應的上一筆交易中的交易輸出
    TXOutput prevTxOutput = prevTx.getOutputs()[txInput.getTxOutputIndex()];
    TXInput txInputCopy = txCopy.getInputs()[i];
    txInputCopy.setSignature(null);
    txInputCopy.setPubKey(prevTxOutput.getPubKeyHash());// 得到要簽名的數據byte[] signData = txCopy.getData();
    txInputCopy.setPubKey(null);// 使用橢圓曲線 x,y 點去生成公鑰Key
    BigInteger x = new BigInteger(1, Arrays.copyOfRange(txInput.getPubKey(), 1, 33));
    BigInteger y = new BigInteger(1, Arrays.copyOfRange(txInput.getPubKey(), 33, 65));
    ECPoint ecPoint = ecParameters.getCurve().createPoint(x, y);
    ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPoint, ecParameters);
    PublicKey publicKey = keyFactory.generatePublic(keySpec);
    ecdsaVerify.initVerify(publicKey);
    ecdsaVerify.update(signData);if (!ecdsaVerify.verify(txInput.getSignature())) {return false;
    }
    }return true;
    }
    }

    3.2.5 修改CLI.java文件

    打開cldy.hanru.blockchain.cli包,修改CLI.java文件。

    修改步驟:

    修改步驟:
    step1:修改getBalance()方法
    step2:修改send()方法

    修改完后代碼如下:

    package cldy.hanru.blockchain.cli;

    import cldy.hanru.blockchain.block.Block;
    import cldy.hanru.blockchain.block.Blockchain;
    import cldy.hanru.blockchain.pow.ProofOfWork;
    import cldy.hanru.blockchain.store.RocksDBUtils;
    import cldy.hanru.blockchain.transaction.TXInput;
    import cldy.hanru.blockchain.transaction.TXOutput;
    import cldy.hanru.blockchain.transaction.Transaction;
    import cldy.hanru.blockchain.transaction.UTXOSet;
    import cldy.hanru.blockchain.util.Base58Check;
    import cldy.hanru.blockchain.wallet.Wallet;
    import cldy.hanru.blockchain.wallet.WalletUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.cli.*;
    import org.apache.commons.codec.binary.Hex;
    import org.apache.commons.lang3.StringUtils;
    import org.apache.commons.lang3.math.NumberUtils;

    import java.text.SimpleDateFormat;
    import java.util.*;

    /**
    * @author hanru
    */
    @Slf4j
    public class CLI {
    private String[] args;
    private Options options = new Options();


    public CLI(String[] args) {
    this.args = args;

    Option helpCmd = Option.builder("h").desc("show help").build();
    options.addOption(helpCmd);

    Option address = Option.builder("address").hasArg(true).desc("Source wallet address").build();
    Option sendFrom = Option.builder("from").hasArg(true).desc("Source wallet address").build();
    Option sendTo = Option.builder("to").hasArg(true).desc("Destination wallet address").build();
    Option sendAmount = Option.builder("amount").hasArg(true).desc("Amount to send").build();

    options.addOption(address);
    options.addOption(sendFrom);
    options.addOption(sendTo);
    options.addOption(sendAmount);
    }

    /**
    * 打印幫助信息
    */
    private void help() {

    System.out.println("Usage:");
    System.out.println(" createwallet - Generates a new key-pair and saves it into the wallet file");
    System.out.println(" printaddresses - print all wallet address");
    System.out.println(" getbalance -address ADDRESS - Get balance of ADDRESS");
    System.out.println(" createblockchain -address ADDRESS - Create a blockchain and send genesis block reward to ADDRESS");
    System.out.println(" printchain - Print all the blocks of the blockchain");
    System.out.println(" send -from FROM -to TO -amount AMOUNT - Send AMOUNT of coins from FROM address to TO");
    System.exit(0);
    }

    /**
    * 驗證入參
    *
    * @param args
    */
    private void validateArgs(String[] args) {
    if (args == null || args.length < 1) {
    help();
    }
    }

    /**
    * 命令行解析入口
    */
    public void run() {
    this.validateArgs(args);
    CommandLineParser parser = new DefaultParser();
    CommandLine cmd = null;
    try {
    cmd = parser.parse(options, args);
    } catch (ParseException e) {
    e.printStackTrace();
    }

    switch (args[0]) {
    case "createblockchain":
    String createblockchainAddress = cmd.getOptionValue("address");
    if (StringUtils.isBlank(createblockchainAddress)) {
    help();
    }
    this.createBlockchain(createblockchainAddress);
    break;
    case "getbalance":
    String getBalanceAddress = cmd.getOptionValue("address");
    if (StringUtils.isBlank(getBalanceAddress)) {
    help();
    }
    try {
    this.getBalance(getBalanceAddress);
    } catch (Exception e) {
    e.printStackTrace();
    }finally {
    RocksDBUtils.getInstance().closeDB();
    }
    break;
    case "send":
    String sendFrom = cmd.getOptionValue("from");
    String sendTo = cmd.getOptionValue("to");
    String sendAmount = cmd.getOptionValue("amount");
    if (StringUtils.isBlank(sendFrom) ||
    StringUtils.isBlank(sendTo) ||
    !NumberUtils.isDigits(sendAmount)) {
    help();
    }
    try {
    this.send(sendFrom, sendTo, Integer.valueOf(sendAmount));
    } catch (Exception e) {
    e.printStackTrace();
    }finally {
    RocksDBUtils.getInstance().closeDB();
    }
    break;
    case "printchain":
    this.printChain();
    break;
    case "h":
    this.help();
    break;

    case "createwallet":
    try {
    this.createWallet();
    } catch (Exception e) {
    e.printStackTrace();
    }
    break;
    case "printaddresses":
    try {
    this.printAddresses();
    } catch (Exception e) {
    e.printStackTrace();
    }
    break;
    default:
    this.help();
    }

    }

    /**
    * 創建區塊鏈
    *
    * @param address
    */
    private void createBlockchain(String address) {

    Blockchain blockchain = Blockchain.createBlockchain(address);
    UTXOSet utxoSet = new UTXOSet(blockchain);
    utxoSet.reIndex();
    log.info("Done ! ");
    }



    /**
    * 打印出區塊鏈中的所有區塊
    */
    private void printChain() {
    Blockchain blockchain = null;
    try {
    blockchain = Blockchain.initBlockchainFromDB();
    } catch (Exception e) {
    e.printStackTrace();
    }

    Blockchain.BlockchainIterator iterator = blockchain.getBlockchainIterator();
    long index = 0;
    while (iterator.hashNext()) {
    Block block = iterator.next();
    System.out.println("第" + block.getHeight() + "個區塊信息:");

    if (block != null) {
    boolean validate = ProofOfWork.newProofOfWork(block).validate();
    System.out.println("validate = " + validate);
    System.out.println("\tprevBlockHash: " + block.getPrevBlockHash());

    System.out.println("\tTransaction: ");
    for (Transaction tx : block.getTransactions()) {
    System.out.printf("\t\t交易ID:%s\n", Hex.encodeHexString(tx.getTxId()));
    System.out.println("\t\t輸入:");
    for (TXInput in : tx.getInputs()) {
    System.out.printf("\t\t\tTxID:%s\n", Hex.encodeHexString(in.getTxId()));
    System.out.printf("\t\t\tOutputIndex:%d\n", in.getTxOutputIndex());

    System.out.printf("\t\t\tPubKey:%s\n", Hex.encodeHexString(in.getPubKey()));
    }
    System.out.println("\t\t輸出:");
    for (TXOutput out : tx.getOutputs()) {
    System.out.printf("\t\t\tvalue:%d\n", out.getValue());

    System.out.printf("\t\t\tPubKeyHash:%s\n", Hex.encodeHexString(out.getPubKeyHash()));
    }


    }


    System.out.println("\tHash: " + block.getHash());
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String date = sdf.format(new Date(block.getTimeStamp() * 1000L));
    System.out.println("\ttimeStamp:" + date);
    System.out.println();
    }
    }
    }

    /**
    * 查詢錢包余額
    *
    * @param address 錢包地址
    */
    private void getBalance(String address) throws Exception {

    // 檢查錢包地址是否合法
    try {
    Base58Check.base58ToBytes(address);
    } catch (Exception e) {
    throw new Exception("ERROR: invalid wallet address");
    }
    Blockchain blockchain = Blockchain.createBlockchain(address);
    // 得到公鑰Hash值
    byte[] versionedPayload = Base58Check.base58ToBytes(address);
    byte[] pubKeyHash = Arrays.copyOfRange(versionedPayload, 1, versionedPayload.length);

    UTXOSet utxoSet = new UTXOSet(blockchain);
    TXOutput[] txOutputs = utxoSet.findUTXOs(pubKeyHash);
    int balance = 0;
    if (txOutputs != null && txOutputs.length > 0) {
    for (TXOutput txOutput : txOutputs) {
    balance += txOutput.getValue();
    }
    }
    System.out.printf("Balance of '%s': %d\n", address, balance);
    }

    /**
    * 轉賬
    *
    * @param from
    * @param to
    * @param amount
    */
    private void send(String from, String to, int amount) throws Exception {
    // 檢查錢包地址是否合法
    try {
    Base58Check.base58ToBytes(from);
    } catch (Exception e) {
    throw new Exception("ERROR: sender address invalid ! address=" + from);
    }
    // 檢查錢包地址是否合法
    try {
    Base58Check.base58ToBytes(to);
    } catch (Exception e) {
    throw new Exception("ERROR: receiver address invalid ! address=" + to);
    }
    if (amount < 1) {
    throw new Exception("ERROR: amount invalid ! ");
    }


    Blockchain blockchain = Blockchain.createBlockchain(from);
    // 新交易
    Transaction transaction = Transaction.newUTXOTransaction(from, to, amount, blockchain);
    // 獎勵
    Transaction rewardTx = Transaction.newCoinbaseTX(from, "");
    List transactions = new ArrayList<>();
    transactions.add(transaction);
    transactions.add(rewardTx);
    Block newBlock = blockchain.mineBlock(transactions);new UTXOSet(blockchain).update(newBlock);
    log.info("Success!");
    }/**
    * 創建錢包
    *
    * @throws Exception
    */private void createWallet() throws Exception {
    Wallet wallet = WalletUtils.getInstance().createWallet();
    System.out.println("wallet address : " + wallet.getAddress());
    }/**
    * 打印錢包地址
    *
    * @throws Exception
    */private void printAddresses() throws Exception {
    Set addresses = WalletUtils.getInstance().getAddresses();if (addresses == null || addresses.isEmpty()) {
    System.out.println("There isn't address");return;
    }for (String address : addresses) {
    System.out.println("Wallet address: " + address);
    }
    }
    }

    3.2.6 修改blockchain.sh腳本文件

    最后修改blockchain.sh腳本文件,修改后內容如下:

    #!/bin/bash
    set -e

    # Check if the jar has been built.
    if [ ! -e target/part8_Transaction2-jar-with-dependencies.jar ]; then
    echo "Compiling blockchain project to a JAR"
    mvn package -DskipTests
    fi

    java -jar target/part8_Transaction2-jar-with-dependencies.jar "$@"

    4. 優化內容講解

    4.1 獎勵Reward

    在上一篇文章中,我們略過的一個小細節是挖礦獎勵。現在,我們已經可以來完善這個細節了。

    挖礦獎勵,實際上就是一筆coinbase?交易。當一個挖礦節點開始挖出一個新塊時,它會將交易從隊列中取出,并在前面附加一筆coinbase交易。coinbase?交易只有一個輸出,里面包含了礦工的公鑰哈希。

    實現獎勵,非常簡單,更新CLI類即可,修改?send()?方法:

    /**
    * 轉賬
    *
    * @param from
    * @param to
    * @param amount
    */
    private void send(String from, String to, int amount) throws Exception {
    // 檢查錢包地址是否合法
    try {
    Base58Check.base58ToBytes(from);
    } catch (Exception e) {
    throw new Exception("ERROR: sender address invalid ! address=" + from);
    }
    // 檢查錢包地址是否合法
    try {
    Base58Check.base58ToBytes(to);
    } catch (Exception e) {
    throw new Exception("ERROR: receiver address invalid ! address=" + to);
    }
    if (amount < 1) {
    throw new Exception("ERROR: amount invalid ! ");
    }


    Blockchain blockchain = Blockchain.createBlockchain(from);
    // 新交易
    Transaction transaction = Transaction.newUTXOTransaction(from, to, amount, blockchain);
    // 獎勵
    Transaction rewardTx = Transaction.newCoinbaseTX(from, "");
    List transactions = new ArrayList<>();
    transactions.add(transaction);
    transactions.add(rewardTx);
    Block newBlock = blockchain.mineBlock(transactions);new UTXOSet(blockchain).update(newBlock);
    log.info("Success!");
    }

    現在我們的邏輯調整為 ,每次轉賬都會給與獎勵,而獎勵通過coinbase交易實現,為了讓這些coinbase交易的交易ID不同,我們需要在Transaction類添加時間字段:

    public class Transaction {
    private static final int SUBSIDY = 10;

    ...

    /**
    * 創建日期
    */
    private long createTime;
    }

    接下來就需要修改創建coinbase交易和創建轉賬交易的方法,添加創建日期:

    /**
    * 創建CoinBase交易
    *
    * @param to 收賬的錢包地址
    * @param data 解鎖腳本數據
    * @return
    */
    public static Transaction newCoinbaseTX(String to, String data) {
    if (StringUtils.isBlank(data)) {
    data = String.format("Reward to '%s'", to);
    }
    // 創建交易輸入
    TXInput txInput = new TXInput(new byte[]{}, -1, null, data.getBytes());
    // 創建交易輸出
    TXOutput txOutput = TXOutput.newTXOutput(SUBSIDY, to);
    // 創建交易
    Transaction tx = new Transaction(null, new TXInput[]{txInput}, new TXOutput[]{txOutput}, System.currentTimeMillis());
    // 設置交易ID
    tx.setTxId();

    return tx;
    }

    以及:

    /**
    * 從 from 向 to 支付一定的 amount 的金額
    *
    * @param from 支付錢包地址
    * @param to 收款錢包地址
    * @param amount 交易金額
    * @param blockchain 區塊鏈
    * @return
    */
    public static Transaction newUTXOTransaction(String from, String to, int amount, Blockchain blockchain) throws Exception {

    // 獲取錢包
    Wallet senderWallet = WalletUtils.getInstance().getWallet(from);
    byte[] pubKey = senderWallet.getPublicKey();
    byte[] pubKeyHash = AddressUtils.ripeMD160Hash(pubKey);

    SpendableOutputResult result = blockchain.findSpendableOutputs(pubKeyHash, amount);
    int accumulated = result.getAccumulated();
    Mapint[]> unspentOuts = result.getUnspentOuts();if (accumulated < amount) {throw new Exception("ERROR: Not enough funds");
    }
    Iteratorint[]>> iterator = unspentOuts.entrySet().iterator();
    TXInput[] txInputs = {};while (iterator.hasNext()) {
    Map.Entryint[]> entry = iterator.next();
    String txIdStr = entry.getKey();int[] outIdxs = entry.getValue();byte[] txId = Hex.decodeHex(txIdStr);for (int outIndex : outIdxs) {
    txInputs = ArrayUtils.add(txInputs, new TXInput(txId, outIndex, null, pubKey));
    }
    }
    TXOutput[] txOutput = {};
    txOutput = ArrayUtils.add(txOutput, TXOutput.newTXOutput(amount, to));if (accumulated > amount) {
    txOutput = ArrayUtils.add(txOutput, TXOutput.newTXOutput((accumulated - amount), from));
    }
    Transaction newTx = new Transaction(null, txInputs, txOutput, System.currentTimeMillis());
    newTx.setTxId();// 進行交易簽名
    blockchain.signTransaction(newTx, senderWallet.getPrivateKey());return newTx;
    }

    在我們的實現中,創建交易的第一個人同時挖出了新塊,所以會得到一筆獎勵。

    在項目添加了獎勵之后,我們進行測試,首先在終端創建3個地址:

    hanru:part8_Transaction2 ruby$ ./blockchain.sh h
    hanru:part8_Transaction2 ruby$ ./blockchain.sh createwallet
    hanru:part8_Transaction2 ruby$ ./blockchain.sh createwallet
    hanru:part8_Transaction2 ruby$ ./blockchain.sh printaddresses

    運行結果如下:

    接下來我們創建創世區塊,并進行一次轉賬:

    hanru:part8_Transaction2 ruby$ ./blockchain.sh createblockchain -address 1FSwdBA55ne6a3QWLnzH3PtPtbDbNHBkXt
    hanru:part8_Transaction2 ruby$ ./blockchain.sh getbalance -address 1FSwdBA55ne6a3QWLnzH3PtPtbDbNHBkXt
    hanru:part8_Transaction2 ruby$ ./blockchain.sh send -from 1FSwdBA55ne6a3QWLnzH3PtPtbDbNHBkXt -to 19a5Lfex3v9EgFUuNG7n5qR2DaXe5RzUT2 -amount 4
    hanru:part8_Transaction2 ruby$ ./blockchain.sh getbalance -address 1FSwdBA55ne6a3QWLnzH3PtPtbDbNHBkXt
    hanru:part8_Transaction2 ruby$ ./blockchain.sh getbalance -address 19a5Lfex3v9EgFUuNG7n5qR2DaXe5RzUT2

    運行結果如下:

    說明:

  • 比如創世區塊中1FSwdBA55ne6a3QWLnzH3PtPtbDbNHBkXt地址有10個Token,

  • 轉賬給19a5Lfex3v9EgFUuNG7n5qR2DaXe5RzUT2地址4個Token,

  • 那么1FSwdBA55ne6a3QWLnzH3PtPtbDbNHBkXt的余額是16(轉賬找零6個,加上得到獎勵10個),

  • 19a5Lfex3v9EgFUuNG7n5qR2DaXe5RzUT2的余額是4。

  • 4.2 UTXO 集

    在持久化的章節中,我們研究了 Bitcoin Core 是如何在一個數據庫中存儲塊的,并且了解到區塊被存儲在?blocks?數據庫,交易輸出被存儲在?chainstate?數據庫。會回顧一下?chainstate?的結構:

  • c?+ 32 字節的交易哈希 -> 該筆交易的未花費交易輸出記錄

  • B?+ 32 字節的塊哈希 -> 未花費交易輸出的塊哈希

  • 在之前那篇文章中,雖然我們已經實現了交易,但是并沒有使用?chainstate?來存儲交易的輸出。所以,接下來我們繼續完成這部分。

    chainstate?不存儲交易。它所存儲的是 UTXO 集,也就是未花費交易輸出的集合。除此以外,它還存儲了“數據庫表示的未花費交易輸出的塊哈希”,不過我們會暫時略過塊哈希這一點,因為我們還沒有用到塊高度(但是我們會在接下來的文章中繼續改進)。

    那么,我們為什么需要 UTXO 集呢?

    來思考一下我們早先實現的在Blockchain類中的findUnspentTransactions()?方法:

    /**
    * 查找錢包地址對應的所有未花費的交易
    *
    * @param pubKeyHash 錢包公鑰Hash
    * @return
    */

    private Transaction[] findUnspentTransactions(byte[] pubKeyHash) throws Exception {
    Mapint[]> allSpentTXOs = this.getAllSpentTXOs(pubKeyHash);
    Transaction[] unspentTxs = {};// 再次遍歷所有區塊中的交易輸出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;
    }

    這個函數找到有未花費輸出的交易。由于交易被保存在區塊中,所以它會對區塊鏈里面的每一個區塊進行迭代,檢查里面的每一筆交易。截止 2017 年 9 月 18 日,在比特幣中已經有 485,860 個塊,整個數據庫所需磁盤空間超過 140 Gb。這意味著一個人如果想要驗證交易,必須要運行一個全節點。此外,驗證交易將會需要在許多塊上進行迭代。

    整個問題的解決方案是有一個僅有未花費輸出的索引,這就是 UTXO 集要做的事情:這是一個從所有區塊鏈交易中構建(對區塊進行迭代,但是只須做一次)而來的緩存,然后用它來計算余額和驗證新的交易。截止 2017 年 9 月,UTXO 集大概才有 2.7 Gb。

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

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

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

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

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

    Blockchain.findUnspentTransactions?方法。

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

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

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

  • Blockchain.findUTXO?—— 通過遍歷所有的區塊來找到所有未被花費的交易輸出.

  • UTXOSet.reindex?—— 調用上面?findUTXO?方法,然后將查詢結果存儲在數據庫中。也即需要進行緩存的地方。

  • UTXOSet.findSpendableOutputs?—— 與?Blockchain.findSpendableOutputs?類似,區別在于會使用 UTXO 池。

  • UTXOSet.findUTXO?—— 與Blockchain.findUTXO?類似,區別在于會使用 UTXO 池。

  • Blockchain.findTransaction?—— 邏輯保持不變。

  • 這樣,兩個使用最頻繁的方法將從現在開始使用緩存!

    接下來,我們個通過代碼實現一下:

    首先,我們在cldy.hanru.blockchain.transaction包下再創建一個java文件,命名為:UTXOSet.java。添加一個類:

    /**
    * 未被花費的交易輸出池
    * @author hanru
    */
    @NoArgsConstructor
    @AllArgsConstructor
    @Slf4j
    public class UTXOSet {

    private Blockchain blockchain;

    }

    我們將會使用一個單一數據庫,但是我們會將 UTXO 集從存儲在不同的 bucket 中。因此,UTXOSet?跟?Blockchain?一起。

    因為我們需要有個bucket來存儲所有的UTXO,所以我們修改RocksDBUtils.java文件,添加對應的存儲操作方法。

    首先先定義存儲UTXO的bucket

    /**
    * 鏈狀態桶Key
    */
    private static final String CHAINSTATE_BUCKET_KEY = "chainstate";
    /**
    * chainstate buckets
    */
    @Getter
    private Map<String, byte[]> chainstateBucket;

    接下來定義一些工具方法,initChainStateBucket()方法用于初始化bucket,并在該類的構造函數中調用:

    /**
    * 初始化 blocks 數據桶
    */
    private void initChainStateBucket() {
    try {
    byte[] chainstateBucketKey = SerializeUtils.serialize(CHAINSTATE_BUCKET_KEY);
    byte[] chainstateBucketBytes = db.get(chainstateBucketKey);
    if (chainstateBucketBytes != null) {
    chainstateBucket = (Map) SerializeUtils.deserialize(chainstateBucketBytes);
    } else {
    chainstateBucket = new HashMap();
    db.put(chainstateBucketKey, SerializeUtils.serialize(chainstateBucket));
    }
    } catch (RocksDBException e) {
    log.error("Fail to init chainstate bucket ! ", e);
    throw new RuntimeException("Fail to init chainstate bucket ! ", e);
    }
    }

    cleanChainStateBucket()方法用于清空bucket:

    /**
    * 清空chainstate bucket
    */
    public void cleanChainStateBucket() {
    try {
    chainstateBucket.clear();
    } catch (Exception e) {
    log.error("Fail to clear chainstate bucket ! ", e);
    throw new RuntimeException("Fail to clear chainstate bucket ! ", e);
    }
    }

    最后定義3個方法,用于表示添加、獲取、刪除UTXO:

    /**
    * 保存UTXO數據
    *
    * @param key 交易ID
    * @param utxos UTXOs
    */
    public void putUTXOs(String key, TXOutput[] utxos) {
    try {
    chainstateBucket.put(key, SerializeUtils.serialize(utxos));
    db.put(SerializeUtils.serialize(CHAINSTATE_BUCKET_KEY), SerializeUtils.serialize(chainstateBucket));
    } catch (Exception e) {
    log.error("Fail to put UTXOs into chainstate bucket ! key=" + key, e);
    throw new RuntimeException("Fail to put UTXOs into chainstate bucket ! key=" + key, e);
    }
    }

    /**
    * 查詢UTXO數據
    *
    * @param key 交易ID
    */
    public TXOutput[] getUTXOs(String key) {
    byte[] utxosByte = chainstateBucket.get(key);
    if (utxosByte != null) {
    return (TXOutput[]) SerializeUtils.deserialize(utxosByte);
    }
    return null;
    }

    /**
    * 刪除 UTXO 數據
    *
    * @param key 交易ID
    */
    public void deleteUTXOs(String key) {
    try {
    chainstateBucket.remove(key);
    db.put(SerializeUtils.serialize(CHAINSTATE_BUCKET_KEY), SerializeUtils.serialize(chainstateBucket));
    } catch (Exception e) {
    log.error("Fail to delete UTXOs by key ! key=" + key, e);
    throw new RuntimeException("Fail to delete UTXOs by key ! key=" + key, e);
    }
    }

    工具類準備好后,我們在UTXOSet.java中,繼續定義一個reIndex()方法:

    /**
    * 重建 UTXO 池索引
    */
    @Synchronizedpublic void reIndex() {
    log.info("Start to reIndex UTXO set !");
    RocksDBUtils.getInstance().cleanChainStateBucket();
    Map allUTXOs = blockchain.findAllUTXOs();for (Map.Entry entry : allUTXOs.entrySet()) {
    RocksDBUtils.getInstance().putUTXOs(entry.getKey(), entry.getValue());
    }log.info("ReIndex UTXO set finished ! ");
    }

    這個方法初始化了 UTXO 集。首先,先清除之前的bucket。然后從區塊鏈中獲取所有的未花費輸出,最終將輸出保存到 bucket 中。該方法也用于重置UTXO集。

    在Blockchain.java中添加方法,用于查詢所有的未花費輸出:

    /**
    * 查找所有的 unspent transaction outputs
    *
    * @return
    */
    public Map findAllUTXOs() {
    Mapint[]> allSpentTXOs = this.getAllSpentTXOs();
    Map allUTXOs = new HashMap();// 再次遍歷所有區塊中的交易輸出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);
    TXOutput[] txOutputs = transaction.getOutputs();for (int outIndex = 0; outIndex < txOutputs.length; outIndex++) {if (spentOutIndexArray != null && ArrayUtils.contains(spentOutIndexArray, outIndex)) {continue;
    }
    TXOutput[] UTXOArray = allUTXOs.get(txId);if (UTXOArray == null) {
    UTXOArray = new TXOutput[]{txOutputs[outIndex]};
    } else {
    UTXOArray = ArrayUtils.add(UTXOArray, txOutputs[outIndex]);
    }
    allUTXOs.put(txId, UTXOArray);
    }
    }
    }return allUTXOs;
    }

    接下來,修改CLI.java文件,修改createBlockchain()方法代碼如下:

    /**
    * 創建區塊鏈
    *
    * @param address
    */
    private void createBlockchain(String address) {

    Blockchain blockchain = Blockchain.createBlockchain(address);
    UTXOSet utxoSet = new UTXOSet(blockchain);
    utxoSet.reIndex();
    log.info("Done ! ");
    }

    當創建創世區塊后,就會立刻進行初始化UTXO集。目前,即使這里看起來有點“殺雞用牛刀”,因為一條鏈開始的時候,只有一個塊,里面只有一筆交易。

    因為創建了創世區塊,有CoinBase交易的10個Token,這是一個未花費的TxOutput,找到之后可以存儲到UTXO集中。

    4.3 優化查詢余額

    有了UTXO集,我們想要查詢余額,可以不用遍歷整個區塊鏈的所有區塊,而是直接查找UTXO集,找出對應地址的utxo,進行累加即可。

    接下來,我們在UTXOSet.java中,添加findUTXOs(),用于查找指定賬戶的所有的utxo,代碼如下:

    /**
    * 查找錢包地址對應的所有UTXO
    *
    * @param pubKeyHash 錢包公鑰Hash
    * @return
    */
    public TXOutput[] findUTXOs(byte[] pubKeyHash) {
    TXOutput[] utxos = {};
    Mapbyte[]> 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;
    }

    接下來修改,修改CLI.java文件,不再通過blockchain對象調用原來的查詢方法了,改用utxoSet對象進行查詢余額,代碼如下:

    /**
    * 查詢錢包余額
    *
    * @param address 錢包地址
    */
    private void getBalance(String address) throws Exception {

    // 檢查錢包地址是否合法
    try {
    Base58Check.base58ToBytes(address);
    } catch (Exception e) {
    throw new Exception("ERROR: invalid wallet address");
    }
    Blockchain blockchain = Blockchain.createBlockchain(address);
    // 得到公鑰Hash值
    byte[] versionedPayload = Base58Check.base58ToBytes(address);
    byte[] pubKeyHash = Arrays.copyOfRange(versionedPayload, 1, versionedPayload.length);

    UTXOSet utxoSet = new UTXOSet(blockchain);
    TXOutput[] txOutputs = utxoSet.findUTXOs(pubKeyHash);
    int balance = 0;
    if (txOutputs != null && txOutputs.length > 0) {
    for (TXOutput txOutput : txOutputs) {
    balance += txOutput.getValue();
    }
    }
    System.out.printf("Balance of '%s': %d\n", address, balance);
    }

    接下來,我們測試一下,先進行一次轉賬(轉賬我們還沒有使用UTXO集優化),然后再查詢余額。在終端中輸入以下命令:

    # 首先重新編譯程序
    hanru:part8_Transaction2 ruby$ mvn package
    # 創建新的錢包地址
    hanru:part8_Transaction2 ruby$ ./blockchain.sh createwallet
    # 轉賬,19a5Lfex3v9EgFUuNG7n5qR2DaXe5RzUT2賬戶因為之前的轉賬操作,有4個Token
    hanru:part8_Transaction2 ruby$ ./blockchain.sh send -from 19a5Lfex3v9EgFUuNG7n5qR2DaXe5RzUT2 -to 17mW1XamRdFZBsnmfh3DxbC5pW1AttJznn -amount 3
    # 查詢余額
    hanru:part8_Transaction2 ruby$ ./blockchain.sh getbalance -address 19a5Lfex3v9EgFUuNG7n5qR2DaXe5RzUT2
    hanru:part8_Transaction2 ruby$ ./blockchain.sh getbalance -address 17mW1XamRdFZBsnmfh3DxbC5pW1AttJznn

    運行結果如下:

    5. 總結

    本章節中,我們并沒有在項目中新增功能,只是做了一些優化。首先加入了獎勵Reward機制。雖然程序中我們實現的比較建議,僅僅是給發起轉賬的人獎勵10個Token,(如果一次轉賬多筆交易,只獎勵給第一個轉賬人)。然后我們引入了UTXO集,進行項目代碼優化。UTXO集的原理,就是我們將所有區塊的未花費的utxo,單獨存儲到一個bucket中。無論是進行余額查詢,還是轉賬操作,都無需遍歷查詢所有的區塊,查詢所有的交易去找未花費utxo了,只需要查詢UTXO集即可。如果是轉賬操作,轉賬后需要及時更新UTXO集。

    總結

    以上是生活随笔為你收集整理的修改linq结果集_UTXO集优化的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。