日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

修改linq结果集_UTXO集优化

發布時間:2025/3/19 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 修改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集优化的全部內容,希望文章能夠幫你解決所遇到的問題。

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

    黄色大全免费网站 | 99精品乱码国产在线观看 | 在线观看中文字幕dvd播放 | 亚洲永久国产精品 | 国产日韩精品欧美 | 91精品综合在线观看 | 午夜精品一区二区三区免费 | 视频国产精品 | 欧美激情va永久在线播放 | 国产九九热 | 天天操天天摸天天干 | 青青五月天 | 在线观看视频在线 | 国产高清不卡在线 | 久99视频 | 天天舔天天搞 | 国产精品一区久久久久 | 国产精品热视频 | 成人黄色大片 | 日韩av电影免费观看 | 手机在线观看国产精品 | 美女久久久久久 | 精品视频久久久 | 天天干天天看 | 在线观看国产www | 日韩色区 | 成人欧美一区二区三区黑人麻豆 | 人人草网站 | 在线日韩中文字幕 | 久久久久久久久久影院 | 中文字幕美女免费在线 | 亚洲精品观看 | 国内精品福利视频 | 久久艹国产 | 91av九色 | 欧美激情片在线观看 | 日韩av在线一区二区 | 免费在线观看国产精品 | 久久夜色精品亚洲噜噜国4 午夜视频在线观看欧美 | 国产美女被啪进深处喷白浆视频 | 久久国产亚洲精品 | 久久久午夜精品福利内容 | 在线观看视频在线 | 波多野结衣小视频 | 在线国产视频 | 91视频高清 | 免费观看黄色av | 精品国产乱码久久久久久浪潮 | 91视频在线 | 国产看片 色 | 天天干天天拍天天操 | 成人 亚洲 欧美 | 狠狠干免费 | 久久免费中文视频 | 久久精品欧美一区二区三区麻豆 | 这里只有精品视频在线观看 | 制服丝袜欧美 | 国产精品精品国产婷婷这里av | 久久久精品午夜 | 亚州精品天堂中文字幕 | 天天综合天天做 | 五月婷婷综合色拍 | 国产亚洲午夜高清国产拍精品 | 中文av在线免费观看 | 六月丁香婷婷久久 | av免费观看高清 | 精品亚洲网 | 狠狠色丁香婷婷综合 | 日日夜夜精品视频天天综合网 | 国产在线观看91 | 日韩中文字幕免费在线播放 | 热久久这里只有精品 | 日韩a欧美 | 欧美一区二区在线刺激视频 | 国产精品久久久久久欧美 | 伊人久久影视 | 国产精品免费一区二区三区在线观看 | 日韩精品一区二区电影 | 亚洲视频2| 狠狠躁夜夜躁人人爽超碰97香蕉 | 精品亚洲欧美一区 | 一区在线播放 | 亚洲综合色网站 | 成人性生交大片免费看中文网站 | 国产视频在线观看一区 | 亚洲小视频在线 | 欧美成人aa | 国产色视频一区二区三区qq号 | 中文字幕色播 | 亚洲精品视频大全 | 日韩精品最新在线观看 | 天天做日日做天天爽视频免费 | 1024手机在线看 | 亚洲人人精品 | 国产精品乱码高清在线看 | 高清一区二区三区 | 欧美成人91| 人人爱天天操 | 最新国产福利 | av日韩在线网站 | 中国一级片视频 | 激情av网址 | 欧洲色综合 | 在线观看视频你懂得 | 我爱av激情网| 精品国产三级a∨在线欧美 免费一级片在线观看 | 久久婷五月 | 久久人人爽人人爽 | 成年一级片 | 国产日韩欧美在线 | 91在线视频在线观看 | 91精品国自产在线观看欧美 | 黄色毛片视频免费 | 久久69精品 | 亚洲资源一区 | 日韩资源视频 | 日韩精品最新在线观看 | 韩国中文三级 | 一区二区三区四区五区在线视频 | 国产中文字幕精品 | 免费视频网 | 国产福利在线免费观看 | 综合久久网站 | 97国产精品 | 免费看片网站91 | 69国产盗摄一区二区三区五区 | aav在线| 国产精品h在线观看 | 在线视频欧美精品 | av一级片在线观看 | 亚洲综合色视频在线观看 | 99久久超碰中文字幕伊人 | 欧美日韩精品在线视频 | 国产三级久久久 | 亚洲精品伦理在线 | 狠狠操操| 视频一区视频二区在线观看 | 久久久久免费网站 | 69视频在线 | 欧美激情第十页 | 久久久电影网站 | 中文av网站| av电影在线不卡 | 国产一区二区三区在线免费观看 | 亚洲国产精品激情在线观看 | 人人添人人澡 | 欧美日韩国产三级 | 88av色 | 国产美女久久久 | 国产精品成人一区二区 | 日韩激情片在线观看 | 狠狠躁18三区二区一区ai明星 | 久久综合婷婷国产二区高清 | 黄色小说视频网站 | 激情一区二区三区欧美 | 久久九九网站 | 亚洲综合精品视频 | 婷婷精品进入 | 99视频国产精品 | 日本bbbb摸bbbb | 久久国产欧美日韩精品 | 射久久久 | 天天操夜操视频 | 精品九九久久 | 五月婷婷久久丁香 | 又黄又网站 | 色综合久久久久综合99 | 操操操天天操 | 久久久久久久电影 | 在线观看视频一区二区三区 | 韩国av免费在线观看 | 综合亚洲视频 | 91在线文字幕 | 一区二区三区电影在线播 | ww亚洲ww亚在线观看 | 午夜精品久久久久久久久久久久 | 天天天天天天天天操 | 国产精品12 | 黄色成人av网址 | 91精品视频一区二区三区 | 国产不卡免费 | 欧美日韩中文在线视频 | 日本不卡视频 | 天天做日日爱夜夜爽 | 97视频总站 | 国产国产人免费人成免费视频 | 国产一级视频在线 | 国产精品久久久久一区二区三区共 | 久久精品草 | 日韩在线观看第一页 | 男女激情免费网站 | av成年人电影 | 久久精品国产免费看久久精品 | 国产精品久久久久久久久久妇女 | 在线草| 国产成人精品一区二区三区在线 | 人人爽爽人人 | 国产高清免费在线观看 | 欧美91片| 久久99最新地址 | 国产美女视频黄a视频免费 久久综合九色欧美综合狠狠 | 最近中文字幕国语免费av | 日韩精品中文字幕有码 | 精品综合久久久 | 国产亚洲精品综合一区91 | 久久99精品久久久久久清纯直播 | 亚洲精品网站在线 | 91资源在线 | 免费观看的av网站 | 在线中文字幕电影 | 国产精品国产三级在线专区 | 一区二区三区动漫 | 91av社区| 亚洲精品九九 | 日韩精品一区二 | 中文字幕精品视频 | 韩日av在线| 国产成人三级在线 | 日韩欧美在线播放 | 国产资源在线观看 | 福利视频精品 | 久久人人爽人人爽 | 精品国产乱码久久久久久1区二区 | 免费看黄色小说的网站 | 超碰97在线看 | 国产午夜精品免费一区二区三区视频 | 久久国产精品免费视频 | 亚洲国产精品成人精品 | 欧美综合国产 | 日韩三级成人 | 国产在线观看一 | 亚洲一本视频 | 久久国产一区 | 国产精品9999久久久久仙踪林 | 欧美日韩破处 | 九九热只有这里有精品 | 日本久热| 午夜久久久精品 | 国内精品久久久久久久久 | 国产高清视频在线观看 | 国产黄色精品 | 五月天激情综合网 | 欧洲色综合 | 日韩精品免费一区二区 | 99精品网站 | 欧美亚洲国产精品久久高清浪潮 | 五月天国产 | 国产午夜三级一二三区 | 亚洲精品国产精品国 | 日韩精品一区二区电影 | 精产嫩模国品一二三区 | 日韩伦理一区二区三区av在线 | 精品美女国产在线 | 午夜久久精品 | 久久视频国产 | 午夜久久久影院 | 天天躁日日躁狠狠躁av中文 | 日韩精品中文字幕在线 | 欧美日韩不卡在线观看 | 久久久久免费电影 | 99久久99久久精品免费 | 成人av一区二区在线观看 | 99久久精品国产亚洲 | 久久久亚洲成人 | 欧美激情第一区 | 国产成人精品午夜在线播放 | 国产福利免费在线观看 | 91在线播 | 欧美日韩亚洲在线观看 | 97av在线视频免费播放 | 在线视频 区 | 久久精品视频中文字幕 | 色国产精品一区在线观看 | 成人中文字幕+乱码+中文字幕 | 久久久久久久久免费视频 | 久久免费试看 | 久久久久女人精品毛片九一 | 国产精久久久久久久 | 久久无码av一区二区三区电影网 | 91爱在线| 欧美在线观看视频 | 人人爽人人看 | 国产综合在线观看视频 | 久草网站在线观看 | 国产精品 中文字幕 亚洲 欧美 | 日韩免费在线视频观看 | 亚洲干视频在线观看 | 国产精品成人一区二区三区吃奶 | 91高清免费观看 | 国产欧美日韩一区 | 成年人视频在线 | 日韩一区二区免费视频 | 国产精品麻豆欧美日韩ww | 成人h电影 | 成人在线观看免费 | 激情在线网站 | 中文字幕色婷婷在线视频 | 99热免费在线 | 中文字幕日韩国产 | 91精品国产九九九久久久亚洲 | 热re99久久精品国产66热 | 久久免费视频一区 | 日韩试看| 正在播放国产精品 | 国产一区欧美二区 | 国产午夜三级一区二区三桃花影视 | 伊人中文在线 | 久久兔费看a级 | 婷婷综合成人 | 久久97久久 | 天天干天天干天天射 | 国产美女精品人人做人人爽 | 日韩精品一区二区在线观看视频 | 欧美成人免费在线 | 久色婷婷 | 久草新在线 | 男女激情麻豆 | 波多野结衣理论片 | 在线国产一区二区 | 国产女人18毛片水真多18精品 | 日韩av看片 | 精品影院一区二区久久久 | 8x8x在线观看视频 | 免费日韩视频 | 国产成人精品亚洲精品 | 8x8x在线观看视频 | 热久久免费国产视频 | 国产尤物在线 | 日韩精品一区电影 | 夜夜爽88888免费视频4848 | 精品国产黄色片 | 99精品视频在线观看 | 午夜91在线 | 99精品久久久久久久久久综合 | 中文字幕av全部资源www中文字幕在线观看 | 国产一区二区在线观看免费 | 久久久久久免费毛片精品 | 亚洲国产中文字幕在线 | 一本色道久久综合亚洲二区三区 | 草久热| 久久久免费毛片 | 狠狠色丁香婷婷综合久久片 | 中文字幕黄色网 | 欧美另类高清 videos | 久久国产香蕉视频 | 日韩一级电影在线观看 | 国产欧美最新羞羞视频在线观看 | 亚洲综合视频在线 | 91九色porny蝌蚪主页 | 欧美成人精品欧美一级乱 | 国产传媒中文字幕 | 美女中文字幕 | 国产一线天在线观看 | 精品国产伦一区二区三区免费 | 久久综合狠狠综合久久综合88 | 日韩视频免费在线观看 | 日本三级不卡 | av在线播放观看 | 亚洲国产一区二区精品专区 | 成人在线观看网址 | 一区二区三区四区五区在线 | 91av电影在线观看 | 麻豆国产精品永久免费视频 | 亚洲va在线va天堂 | 欧美一级免费黄色片 | 美女在线观看网站 | 日韩视频欧美视频 | 丁香影院在线 | 免费av网址在线观看 | 成年人看片 | 久久精品91视频 | 精品少妇一区二区三区在线 | 黄色国产区 | 国产高清在线免费视频 | 在线导航av | 精品a视频 | .精品久久久麻豆国产精品 亚洲va欧美 | 亚洲午夜精品久久久久久久久久久久 | 国产高清不卡av | 欧美日韩视频观看 | 国产黄网在线 | 国产精品美女久久久久久免费 | 黄色av高清 | 97在线观看视频 | 在线观看色网 | 国产麻豆视频 | 青青久草在线视频 | 韩日av在线 | 96超碰在线 | 亚洲精品在线视频网站 | 久草在线电影网 | 在线成人免费av | 久视频在线播放 | 久久久高清一区二区三区 | 天天摸日日摸人人看 | 综合久久久久久久 | 五月在线视频 | 天天综合狠狠精品 | 欧美有色| 国产最新在线视频 | 在线免费观看黄色av | 亚洲不卡在线 | 中文字幕 二区 | 国产精品一区二区久久精品爱微奶 | 91在线视频免费91 | 国产精品毛片一区二区在线 | 中文字幕在线高清 | 久日视频| 天天色天天干天天色 | 亚洲天堂网站视频 | 午夜婷婷网 | 久久综合久色欧美综合狠狠 | 成人av高清 | 91色蜜桃| 免费看黄电影 | 国产精品99免视看9 国产精品毛片一区视频 | 麻豆网站免费观看 | 欧美视频xxx | 亚洲国产精品一区二区尤物区 | 中文字幕国产视频 | 亚洲黄色高清 | 久久成电影 | 欧美aa在线 | 日韩国产精品久久久久久亚洲 | 亚洲国产精品电影 | 亚洲精品中文在线 | 中文字幕精品三级久久久 | 久久福利国产 | 天天干天天插 | 国产精品激情在线观看 | 久久精品视频免费播放 | 中文字幕在线电影 | 中文永久字幕 | 麻豆免费观看视频 | 久久伊人爱 | 午夜在线观看影院 | 三级黄免费看 | 中文字幕一区二区在线观看 | 综合五月婷婷 | 欧美日韩一区二区在线观看 | 国产97碰免费视频 | 中文字幕资源在线观看 | 欧美巨大| 国产精品欧美激情在线观看 | 午夜精品一区二区三区在线观看 | 久久狠狠婷婷 | 欧美一区二区三区在线播放 | 一区二区国产精品 | 国产日韩中文字幕在线 | 久久精品99北条麻妃 | 国产精品不卡视频 | 日韩中文在线视频 | 91视频啊啊啊 | 亚洲黄色大片 | 97福利在线| 久草视频视频在线播放 | 天堂av影院 | 久久爽久久爽久久av东京爽 | 天天综合色天天综合 | 91成人免费在线 | 国产精品电影一区二区 | 国产麻豆电影在线观看 | 久久久噜噜噜久久久 | 国产91国语对白在线 | 亚洲免费精品一区二区 | www麻豆视频| 久草五月 | 精品国产成人av在线免 | 欧美黄色成人 | 亚洲电影院 | 91视频-88av| 久久精品免费 | 国产一区二区三区高清播放 | 国产麻豆传媒 | 亚洲欧美日韩中文在线 | 亚洲欧美日本一区二区三区 | 四川bbb搡bbb爽爽视频 | 在线观看av免费观看 | 91精品国产一区二区在线观看 | 深爱五月激情五月 | 天天色棕合合合合合合 | 亚洲va男人天堂 | 久久艹艹 | 国产一区黄色 | 97精品超碰一区二区三区 | 亚洲免费观看在线视频 | 亚洲天天综合网 | 玖草影院 | 国产精品资源在线观看 | 91精品国自产在线偷拍蜜桃 | 免费看国产视频 | 九九在线国产视频 | 色综合网 | 日韩av一区二区在线 | 91专区在线观看 | 日本女人b| 免费网站在线观看人 | av电影中文 | 中文理论片 | 亚洲国产网站 | 欧美日韩高清一区 | 99视频在线精品国自产拍免费观看 | 国产一级一片免费播放放a 一区二区三区国产欧美 | 日韩午夜大片 | 国内精品久久久久久久久久久久 | 91日韩免费 | 欧美在线不卡一区 | 欧美污在线观看 | 免费在线一区二区三区 | 国产美女久久 | 五月婷婷视频在线观看 | 波多野结衣电影一区二区三区 | 爱射综合 | 在线观看一区 | 三级小视频在线观看 | 成人免费xxxxxx视频 | 久久这里有精品 | 国产美女视频免费观看的网站 | 中文字幕a在线 | 免费av成人在线 | 在线中文字幕播放 | 久久 国产一区 | 一区 二区电影免费在线观看 | 24小时日本在线www免费的 | 免费在线观看的av网站 | 91精选在线 | 中文字幕日韩av | 在线激情av电影 | 久久视频在线看 | 99精品欧美一区二区三区 | 在线精品一区二区 | 欧美一区二区日韩一区二区 | 亚洲成av人片在线观看www | 蜜桃视频色 | 国内偷拍精品视频 | 亚洲精品在线免费播放 | 免费高清在线视频一区· | 色99视频| 天天综合网 天天综合色 | 日韩一级电影在线 | 免费看的黄色网 | 在线视频福利 | 国产成人精品亚洲日本在线观看 | 亚洲综合最新在线 | 日韩在线免费播放 | 色姑娘综合网 | 亚洲91av| 操夜夜操 | 可以免费观看的av片 | 国产视频在线观看一区 | 久久精品在线免费观看 | 欧美日韩p片 | 欧美精品成人在线 | 免费在线观看日韩视频 | 久久综合婷婷综合 | 久久狠狠一本精品综合网 | 97超碰免费 | 欧美精品久久人人躁人人爽 | 91精品国产自产91精品 | 国产精品 亚洲精品 | 激情导航 | 91亚洲精品国偷拍自产在线观看 | 98精品国产自产在线观看 | 免费在线观看一区 | 夜夜摸夜夜爽 | 91亚洲精品国偷拍自产在线观看 | 亚洲精品午夜国产va久久成人 | 成人一级在线观看 | 成人网在线免费视频 | 欧美色888 | 午夜av激情 | 国产精品一区在线观看 | 欧美日韩免费一区二区三区 | 免费日韩电影 | 亚洲成人av片在线观看 | 日本公妇色中文字幕 | 天天摸天天操天天爽 | 久久伊人精品一区二区三区 | 亚洲区另类春色综合小说校园片 | 日韩欧美高清一区二区 | 欧美大jb | 91精品在线免费视频 | 久久久久久久久久久久久国产精品 | 91精品国产自产91精品 | 免费精品视频 | 特级毛片网 | 欧美性极品xxxx做受 | 欧美一级视频一区 | 99精品视频在线观看免费 | 色婷婷综合激情 | 国产在线传媒 | 国产精品18久久久久久久久 | 麻豆一精品传二传媒短视频 | 成人一区影院 | 在线免费观看黄色 | 色一级片 | 麻豆视频在线播放 | wwxxx日本| 久草视频视频在线播放 | 狠狠干婷婷色 | 天天干天天操天天搞 | 激情综合久久 | 日韩一三区 | 日日激情 | 97av视频| 韩国av不卡 | 国产成人a亚洲精品v | 天堂久久电影网 | 黄色的网站在线 | a极黄色片| 黄色资源在线观看 | 久久国际影院 | 嫩嫩影院理论片 | 日韩欧美精品在线观看视频 | 国产精品久久久久久久久久99 | 国产97在线观看 | 国产高清在线 | 在线看成人 | 亚洲综合在线五月天 | 成人黄色在线视频 | 亚洲午夜精品一区二区三区电影院 | av天天澡天天爽天天av | 色婷婷久久一区二区 | 综合色站导航 | 中文免费在线观看 | 免费av片在线 | 九九在线视频免费观看 | 最新国产精品亚洲 | 在线小视频你懂的 | 精产嫩模国品一二三区 | 欧美大片大全 | 久久久久免费精品视频 | 激情五月综合网 | 久操久| 国产中文字幕久久 | 四虎影视成人永久免费观看亚洲欧美 | 91视频免费网址 | 亚洲精品视频免费在线 | 在线观看视频一区二区三区 | 午夜91在线 | 天天艹天天操 | 精品视频免费久久久看 | 欧美激情精品一区 | 久久国产精品99精国产 | 国产精品毛片一区二区在线看 | 欧美在线观看视频一区二区 | 国产精品久久婷婷六月丁香 | 亚洲精品大片www | 成人午夜黄色影院 | 成全在线视频免费观看 | 91精品国产综合久久福利 | 日韩欧美一区二区三区免费观看 | 女人18片 | 色的网站在线观看 | 国产精品原创在线 | www.99在线观看 | 免费在线日韩 | 婷婷中文在线 | 日日夜夜操操操操 | 国产自偷自拍 | 天天干天天天 | 国产精品美女在线 | 久久午夜网 | 91精品国产91热久久久做人人 | 日韩伦理一区二区三区av在线 | 一级α片 | 国产精品美女免费看 | 天天天在线综合网 | 久久精品日产第一区二区三区乱码 | 一区av在线播放 | 国产一区视频导航 | 国产精品乱码久久久 | 97国产超碰在线 | 国产精品永久久久久久久久久 | 久久久国产在线视频 | 精品国产伦一区二区三区观看方式 | 日韩免费在线视频观看 | 国产精品你懂的在线观看 | 日韩欧美在线视频一区二区 | 国产精品久久久久久久99 | 精品久久福利 | 西西444www大胆高清图片 | 成人在线视频网 | 久草免费资源 | 特及黄色片 | 18做爰免费视频网站 | 国产无套视频 | 国产主播99 | 国产中文字幕在线播放 | 91麻豆网站 | 国产黄色片一级 | 免费国产亚洲视频 | 亚洲精品玖玖玖av在线看 | 中文字幕免费播放 | 色干综合 | 国产精品激情偷乱一区二区∴ | 九九99| 国产精品一区二区免费在线观看 | 国产成人一区二区三区久久精品 | 国产精品6999成人免费视频 | 欧美视屏一区二区 | 综合网天天 | 国产精品免费久久久久 | 国产伦精品一区二区三区四区视频 | 婷婷色网址| 男女精品久久 | 亚洲国产美女精品久久久久∴ | 日韩资源在线播放 | 中文字幕一区二区三区四区在线视频 | 欧美亚洲久久 | 91久久国产自产拍夜夜嗨 | 国产精品久久久久久久电影 | 国模一区二区三区四区 | 欧美激情亚洲综合 | 天堂视频一区 | 午夜精品av在线 | 91麻豆精品国产91久久久久久 | 在线网站黄| 天天操天天干天天综合网 | 日韩精品一区二区三区免费观看视频 | 亚洲欧美色婷婷 | 91精品国产乱码在线观看 | 国产精品久久久久久久久久久久冷 | 国产精品99久久久久久小说 | 天天插日日射 | 91精品区 | 精品av网站| 日韩字幕在线观看 | www.久久色 | 亚洲精品自拍视频在线观看 | 亚洲成人精品在线观看 | 五月婷婷天堂 | 婷婷激情久久 | 精品国产91亚洲一区二区三区www | 欧美大香线蕉线伊人久久 | 久久久久国产成人免费精品免费 | 在线成人短视频 | 爱爱av在线 | 欧美孕妇视频 | 欧美日韩国产精品一区二区亚洲 | 国产专区在线看 | 亚洲一区二区精品 | 激情网色 | 久久人91精品久久久久久不卡 | 888av| 国产高清视频免费 | 亚洲精品国产精品国自产 | 91精品老司机久久一区啪 | 91九色视频观看 | 日本黄色免费看 | av片中文 | 一区二区中文字幕在线观看 | 97在线资源 | 国产精品视频全国免费观看 | 人人射| 久久视频热 | 国产在线观看你懂的 | 色在线网站| 日韩美女高潮 | 成年人在线看片 | 99综合电影在线视频 | 国产在线高清视频 | 91av电影| 黄色网址在线播放 | 婷婷精品在线视频 | 免费看一及片 | 一区二区三区 中文字幕 | 国产精品av久久久久久无 | 国产精品女同一区二区三区久久夜 | www日| 亚洲永久av| 久久精品一区二区三区中文字幕 | 日日夜夜精品免费观看 | av片在线观看免费 | 热99在线视频 | 久草视频播放 | 激情综合站 | 国产免费午夜 | 韩日av一区二区 | 日韩免费在线一区 | 99久久99久久精品免费 | 在线观看视频免费播放 | 久久久国产精品人人片99精片欧美一 | 亚洲精品乱码久久久久v最新版 | 国产裸体视频网站 | 色香蕉在线视频 | 国产精品夜夜夜一区二区三区尤 | 亚洲欧洲在线视频 | 久草在线手机观看 | 免费在线观看av的网站 | 久久视频免费 | 日韩理论片| 97超级碰 | 亚洲精品玖玖玖av在线看 | 狠狠躁夜夜av | 国产色视频一区 | 在线视频 一区二区 | 久久精品中文视频 | 日韩免费在线观看视频 | 99久久精品免费看国产一区二区三区 | 久久av电影 | 五月天综合网站 | 在线观看岛国av | 国产欧美精品在线观看 | 久久伊人91 | 日韩精品中字 | 亚洲精品免费视频 | 精品自拍网 | 中文字幕亚洲精品在线观看 | 国产精品二区在线观看 | 欧美最猛性xxxxx免费 | 五月婷婷操| 夜夜爽88888免费视频4848 | 麻豆视频91 | 天天久久综合 | 免费色视频在线 | 1000部国产精品成人观看 | 国产高清精 | 久久黄色美女 | 国产69精品久久久久9999apgf | 亚洲精品www久久久久久 | 亚洲欧美婷婷六月色综合 | 91超在线 | 尤物九九久久国产精品的分类 | 西西44人体做爰大胆视频 | 草久久久久久 | av黄色av| 在线a人片免费观看视频 | 五月天久久婷 | 亚洲韩国一区二区三区 | a黄色一级 | 精品无人国产偷自产在线 | 国产福利精品在线观看 | 狠狠做深爱婷婷综合一区 | 久久精品国产一区二区三 | 亚洲精品色 | 五月天婷婷在线观看视频 | 天天操天天操天天操天天操天天操天天操 | 99热免费在线 | 亚洲最大av在线播放 | 国产精品爽爽久久久久久蜜臀 | 中文字幕人成乱码在线观看 | 人人看人人草 | 午夜精品一区二区三区四区 | 国产色视频一区二区三区qq号 | 国产一级性生活 | 在线观看精品视频 | 91精品老司机久久一区啪 | 啪嗒啪嗒免费观看完整版 | 综合网天天 | 欧美久久久久久久久中文字幕 | 黄污网| 久久看片网| 99精品黄色 | 999国产| 免费在线国产视频 | 又污又黄网站 | 日韩欧美在线综合网 | 天天干天天干天天色 | 综合网久久 | 国产精品理论片在线播放 | 亚洲精品理论片 | 国产国产人免费人成免费视频 | 五月婷婷中文字幕 | 久久免费精品 | 国产成人在线一区 | 丁香一区二区 | 免费精品视频 | 一区二区三区国产精品 | 久久精品4 | 91精品影视 | 国产主播大尺度精品福利免费 | 天天射天天做 | 成人av在线影院 | 99精品免费在线 | 人人爱人人爽 | 成人网444ppp | 天天搞天天干天天色 | av在线之家电影网站 | 在线看片日韩 | 爱爱av网站| 亚洲3级| 在线久久| 成人三级av | 成人免费观看a | 99久久久国产精品免费99 | 亚洲精品在 | 久久精品在线免费观看 | 久久久久五月 | 国内精品久久久久影院日本资源 | 精品日本视频 | 99在线看| 午夜久久久久久久久久久 | 色综合久久久 | 中文字幕在线国产精品 | 一本—道久久a久久精品蜜桃 | 色婷婷骚婷婷 | 久久精品国产精品亚洲精品 | 狠狠操狠狠干2017 | 国产日韩精品一区二区 | 久久99视频精品 | 欧美久久久久 | 婷婷六月天丁香 | 久久久久久高潮国产精品视 | 蜜桃av综合网 | 久草在线免费看视频 | 在线视频 精品 | 丁香婷五月 | 五月天激情综合 | 丁香视频全集免费观看 | 亚洲激情电影在线 | 一区二区三区在线免费观看 | 亚洲国产精品人久久电影 | 色妞色视频一区二区三区四区 | 99热在线国产精品 | 国产a网站 | 日韩xxxbbb| 97国产大学生情侣白嫩酒店 | 免费看一级黄色大全 | 成人av在线看| 日韩在线二区 | 六月丁香色婷婷 | 日韩理论片在线 | 国产主播99 | 欧美一级爽 | 毛片网在线观看 | 国内精品视频在线 | 开心丁香婷婷深爱五月 | 手机在线视频福利 | 国产视频中文字幕在线观看 | 人人干网站 | 精品国产久| 色瓜| 精品高清美女精品国产区 | 国产成人一区二区在线观看 | 91福利免费| 91av在线电影 | 国产无遮挡又黄又爽在线观看 | 欧美成人精品欧美一级乱 | 久草在线免费播放 | 国产视频欧美视频 | 77国产精品| 久久综合久色欧美综合狠狠 | 99精品国产亚洲 | 日韩天天综合 | 国产成人精品网站 | 天天曰夜夜爽 | 欧美成年黄网站色视频 | 欧美aaa一级 | 日韩美女av在线 | 久久国产精品网站 | 中文在线天堂资源 | 91精品国| 国内视频一区二区 | 欧美日韩国产精品一区二区三区 | 中文字幕在线观看视频一区二区三区 | 91视频麻豆 | 国产视频精品免费 | 日韩欧美精品免费 | japanesexxxhd奶水| 亚洲精品网站 | 99av在线视频 | 日韩av电影网站在线观看 | 91精选在线观看 | 在线免费观看视频你懂的 | 成人av影院在线观看 | 日日日操 | 天天天天色射综合 | 在线免费观看视频一区二区三区 | 欧美在线观看小视频 | 999久久久| 天天操天天干天天玩 | av看片在线 | 色久天| 国产精品久久电影观看 | 国产精品一区二区av影院萌芽 | 亚洲精品1234区 | 欧美视频在线观看免费网址 | 欧美另类亚洲 | 韩国av一区二区三区在线观看 | 人人澡人人爱 | 超碰人人做 | 狠狠黄| 国产99在线播放 | 亚洲精品高清视频在线观看 | 国产色秀视频 | 国产系列在线观看 | 欧美日韩不卡一区二区 | 亚在线播放中文视频 | 伊人永久在线 | 国产福利电影网址 | 日韩在线中文字幕视频 | 日韩在线视频免费看 | www国产亚洲精品久久麻豆 | 999国产 | 国产精品99久久久久久武松影视 | 国产偷v国产偷∨精品视频 在线草 | 久久精品久久精品久久精品 |