Building blockchain based on Java language (6) — Merkle tree

Time:2020-10-28

Building blockchain based on Java language (6) -- Merkle tree

The final content is subject to the original texthttps://wangwei.one/posts/630…

introduction

At the beginning of this series, we mentioned that blockchain is a distributed database. At that time, we decided to skip distributed and focus on data storage. So far, we have implemented almost all the components of the blockchain. In this article, we will cover some of the mechanisms that were ignored in previous articles, and in the next article, we will start to study the distributed features of blockchain.

The contents of the previous parts are as follows:

  1. Basic prototype
  2. proof of work
  3. Persistence & command line
  4. Transaction (utxo)
  5. Address (wallet)

Utxo pool

stayPersistence & command lineIn this article, we study the way bitcoin core blocks are stored. We mentioned that block related data is stored inblocksIn this bucket, the transaction data is stored in thechainstateIn this data bucket, let’s recall,chainstateData structure of data bucket:

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

    Utxo record of a transaction

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

    Block hash of utxo represented by database

Since that article, we have implemented the bitcoin trading mechanism, but we haven’t used it yetchainstateData bucket to store our trade output. So that’s what we’re going to do now.

chainstateIt doesn’t store transaction data. Instead, it stores the utxo set, which is the set of transaction outputs that are not spent. In addition, it also stores the “block hash of utxo represented by the database”. We will ignore this point for the moment, because we have not used the block height (we will implement this in a later article).

So why do we need a utxo pool?

Let’s take a look at our previous implementationfindUnspentTransactionsmethod:

/**
     *Find all unused transactions corresponding to the wallet address
     *
     *@ param pubkeyhash wallet public key hash
     * @return
     */
    private Transaction[] findUnspentTransactions(byte[] pubKeyHash) throws Exception {
        Map<String, int[]> allSpentTXOs = this.getAllSpentTXOs(pubKeyHash);
        Transaction[] unspentTxs = {};

        //Traverse the transaction output in all blocks again
        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;
                    }

                    //Save transactions that do not exist in allspentxos
                    if (transaction.getOutputs()[outIndex].isLockedWithKey(pubKeyHash)) {
                        unspentTxs = ArrayUtils.add(unspentTxs, transaction);
                    }
                }
            }
        }
        return unspentTxs;
    }

This method is used to find the transaction information corresponding to the wallet address containing the output of the unused transaction. Since transaction information is stored in blocks, our existing method is to traverse each block in the blockchain, then traverse the transaction information in each block, and then traverse the transaction output in each transaction, and check whether the transaction output is locked by the corresponding wallet address, which is very inefficient. As of March 29, 2018, there are515698And the data takes up 140 + GB of disk space. This means that one has to run the whole node (download all the block data) to verify the transaction information. In addition, verifying transaction information requires traversing all blocks.

The solution to this problem is to have an index that stores all utxos (no transaction output spent), which is utxos What the pool needs to do: the utxos pool is actually a cache space. The data it caches needs to be obtained from all transaction data in the construction blockchain (by traversing all the blockchains, but this construction operation only needs to be performed once), and it will be used for the calculation of wallet balance and the verification of new transaction data. As of September 2017, the utxos pool was about 2.7gb.

Well, let’s think about what we need to do to implement the utxos pool. Currently, the following methods are used to find transaction information:

  1. Blockchain.getAllSpentTXOs——Query all transaction outputs that have been spent. It needs to traverse the transaction information of all blocks in the blockchain.
  2. Blockchain.findUnspentTransactions——Query the transaction information that contains the output of the transaction that has not been spent. It also needs to traverse the transaction information in all blocks in the blockchain.
  3. Blockchain.findSpendableOutputs——This method is used when a new transaction is created. It needs to find enough transaction output to meet the amount of money it needs to pay. Need to callBlockchain.findUnspentTransactionsmethod.
  4. Blockchain.findUTXO——Query all the unused transaction output corresponding to the wallet address, and then use it to calculate the wallet balance. Need to call

    Blockchain.findUnspentTransactionsmethod.

  5. Blockchain.findTransaction——Query transaction information through transaction ID. It needs to traverse all the blocks until it finds the transaction information.

As you can see, all of these methods need to traverse all the blocks in the database. Since the utxos pool only stores the unused transaction output and does not store all transaction information, we will not store any transaction informationBlockchain.findTransactionTo optimize.

So, we need the following methods:

  1. Blockchain.findUTXO——By traversing all the blocks to find all the transaction outputs that have not been spent
  2. UTXOSet.reindex——Call abovefindUTXOMethod, and then store the query results in the database. This is where caching is needed.
  3. UTXOSet.findSpendableOutputs——AndBlockchain.findSpendableOutputsSimilarly, the difference is that the utxo pool is used.
  4. UTXOSet.findUTXO——AndBlockchain.findUTXOSimilarly, the difference is that the utxo pool is used.
  5. Blockchain.findTransaction——The logic remains the same.

In this way, the two most frequently used methods will use caching from now on! Let’s start coding!

definitionUTXOSet

@NoArgsConstructor
@AllArgsConstructor
@Slf4j
public class UTXOSet {
    private Blockchain blockchain;
}

Rebuild utxo pool index:

public class UTXOSet {
 
   ...
 
  /**
    *Rebuild utxo pool index
    */
    @Synchronized   
    public void reIndex() {
        log.info("Start to reIndex UTXO set !");
        RocksDBUtils.getInstance().cleanChainStateBucket();
        Map<String, TXOutput[]> allUTXOs = blockchain.findAllUTXOs();
        for (Map.Entry<String, TXOutput[]> entry : allUTXOs.entrySet()) {
            RocksDBUtils.getInstance().putUTXOs(entry.getKey(), entry.getValue());
        }
        log.info("ReIndex UTXO set finished ! ");
    }
    
    ...
}

This method is used to initialize utxoset. First, it needs to be emptiedchainstateData bucket, then query all unused transaction output and save them tochainstateIn the data bucket.

realizationfindSpendableOutputsMethods forTransation.newUTXOTransactioncall

public class UTXOSet {
 
   ... 
 
   /**
     *Look for deals that can be spent
     *
     *@ param pubkeyhash wallet public key hash
     *@ param amount
     */
    public SpendableOutputResult findSpendableOutputs(byte[] pubKeyHash, int amount) {
        Map<String, int[]> unspentOuts = Maps.newHashMap();
        int accumulated = 0;
        Map<String, byte[]> chainstateBucket = RocksDBUtils.getInstance().getChainstateBucket();
        for (Map.Entry<String, byte[]> entry : chainstateBucket.entrySet()) {
            String txId = entry.getKey();
            TXOutput[] txOutputs = (TXOutput[]) SerializeUtils.deserialize(entry.getValue());

            for (int outId = 0; outId < txOutputs.length; outId++) {
                TXOutput txOutput = txOutputs[outId];
                if (txOutput.isLockedWithKey(pubKeyHash) && accumulated < amount) {
                    accumulated += txOutput.getValue();

                    int[] outIds = unspentOuts.get(txId);
                    if (outIds == null) {
                        outIds = new int[]{outId};
                    } else {
                        outIds = ArrayUtils.add(outIds, outId);
                    }
                    unspentOuts.put(txId, outIds);
                    if (accumulated >= amount) {
                        break;
                    }
                }
            }
        }
        return new SpendableOutputResult(accumulated, unspentOuts);
    }
    
    ...
    
}

realizationfindUTXOsInterface forCLI.getBalanceCall:

public class UTXOSet {
 
   ... 
 
   /**
     *Find all utxo corresponding to wallet address
     *
     *@ param pubkeyhash wallet public key hash
     * @return
     */
    public TXOutput[] findUTXOs(byte[] pubKeyHash) {
        TXOutput[] utxos = {};
        Map<String, byte[]> chainstateBucket = RocksDBUtils.getInstance().getChainstateBucket();
        if (chainstateBucket.isEmpty()) {
            return utxos;
        }
        for (byte[] value : chainstateBucket.values()) {
            TXOutput[] txOutputs = (TXOutput[]) SerializeUtils.deserialize(value);
            for (TXOutput txOutput : txOutputs) {
                if (txOutput.isLockedWithKey(pubKeyHash)) {
                    utxos = ArrayUtils.add(utxos, txOutput);
                }
            }
        }
        return utxos;
    }
    
    ...
    
}

All of these methods are previousBlockchainThe previous method will no longer be used.

With the utxo pool, it means that our transaction data is stored separately in two different buckets: the transaction data is stored in theblockData bucket, and utxo is stored inchainstateIn the data bucket. This requires a synchronization mechanism to ensure that every time a new block is generated, the utxo pool can synchronize the transaction data in the latest block in time. After all, we do not want to do it frequentlyreIndex。 Therefore, we need the following methods:

Update utxo pool:

public class UTXOSet {
 
   ... 

   /**
     *Update utxo pool
     * <p>
     *When a new block is created, two things need to be done:
     *1) remove spent transaction output from utxo pool;
     *2) save the new transaction output;
     *
     *@ param tipblock latest block
     */
    @Synchronized
    public 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()) {

            //Check the remaining unused transaction output according to the transaction input
            if (!transaction.isCoinbase()) {
                for (TXInput txInput : transaction.getInputs()) {
                    //The remaining unused trade output
                    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 there is no remaining, it will be deleted, otherwise it will be updated
                    if (remainderUTXOs.length == 0) {
                        RocksDBUtils.getInstance().deleteUTXOs(txId);
                    } else {
                        RocksDBUtils.getInstance().putUTXOs(txId, remainderUTXOs);
                    }
                }
            }

            //The new transaction output is saved to DB
            TXOutput[] txOutputs = transaction.getOutputs();
            String txId = Hex.encodeHexString(transaction.getTxId());
            RocksDBUtils.getInstance().putUTXOs(txId, txOutputs);
        }

    }
    
    ...
    
}

Let’s use utxoset where they need to be:

public class CLI {

   ...

   /**
     *Create blockchain
     *
     * @param address
     */
    private void createBlockchain(String address) {
        Blockchain blockchain = Blockchain.createBlockchain(address);
        UTXOSet utxoSet = new UTXOSet(blockchain);
        utxoSet.reIndex();
        log.info("Done ! ");
    }
    
    ...
    
}

When creating a new blockchain, we need to rebuild the utxo pool index. So far, it’s the only usereIndexAlthough it seems redundant, there is only one block and one transaction at the beginning of blockchain creation.

modifyCLI.sendInterface:

public class CLI {
    
    ...

   /**
     *Transfer
     *
     * @param from
     * @param to
     * @param amount
     */
    private void send(String from, String to, int amount) throws Exception {
        
        ...
        
        Blockchain blockchain = Blockchain.createBlockchain(from);
        Transaction transaction = Transaction.newUTXOTransaction(from, to, amount, blockchain);
        Block newBlock = blockchain.mineBlock(new Transaction[]{transaction});
        new UTXOSet(blockchain).update(newBlock);
        
        ...
    }
    
    ...
    
}

When a new block is generated, the utxo pool data needs to be updated.

Let’s check their operation:

$ java -jar blockchain-java-jar-with-dependencies.jar  createwallet
wallet address : 1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf

$ java -jar blockchain-java-jar-with-dependencies.jar  createwallet
wallet address : 1HX7bWwCjvxkjq65GUgAVRFfTZy6yKWkoG

$ java -jar blockchain-java-jar-with-dependencies.jar  createwallet
wallet address : 1L1RoFgyjCrNPCPHmSEBtNiV3h2wiF9mZV

$ java -jar blockchain-java-jar-with-dependencies.jar  createblockchain -address 1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf

Elapsed Time: 164.961 seconds 
correct hash Hex: 00225493862611bc517cb6b3610e99d26d98a6b52484c9fa745df6ceff93f445 

Done ! 

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf
Balance of '1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf': 10

$ java -jar blockchain-java-jar-with-dependencies.jar  send -from 1HX7bWwCjvxkjq65GUgAVRFfTZy6yKWkoG -to  1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf -amount 5
java.lang.Exception: ERROR: Not enough funds

$ java -jar blockchain-java-jar-with-dependencies.jar  send -from 1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf -to 1HX7bWwCjvxkjq65GUgAVRFfTZy6yKWkoG -amount 2
Elapsed Time: 54.92 seconds 
correct hash Hex: 0001ab21f71ff2d6d532bf3b3388db790c2b03e28d7bd27bd669c5f6380a4e5b 

Success!

$ java -jar blockchain-java-jar-with-dependencies.jar  send -from 1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf -to 1L1RoFgyjCrNPCPHmSEBtNiV3h2wiF9mZV -amount 2
Elapsed Time: 54.92 seconds 
correct hash Hex: 0009b925cc94e3db8bab2958b1fc2d1764aa15531e20756d92c3a93065c920f0 

Success!

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf
Balance of '1JgppX2xMshr35wHzvNWQBejUAZ3Te5Mdf': 6

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 1HX7bWwCjvxkjq65GUgAVRFfTZy6yKWkoG
Balance of '1HX7bWwCjvxkjq65GUgAVRFfTZy6yKWkoG': 2

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 1L1RoFgyjCrNPCPHmSEBtNiV3h2wiF9mZV
Balance of '1L1RoFgyjCrNPCPHmSEBtNiV3h2wiF9mZV': 2

Reward mechanism

In the previous chapter, we have omitted the reward mechanism for mining. The time is ripe for it.

Miners’ reward is actually a coinbase transaction. When a miner node starts to produce a new block, it will take some transaction data from the queue and prefabricate a coinbase transaction for them. The only transaction output in this coalbase transaction contains the miner’s public key hash.

Just updatesendCommand interface, we can easily realize the reward mechanism of miners:

public class CLI {
    
    ...

   /**
     *Transfer
     *
     * @param from
     * @param to
     * @param amount
     */
    private void send(String from, String to, int amount) throws Exception {
        
        ...
        
        Blockchain blockchain = Blockchain.createBlockchain(from);
        //New deal
        Transaction transaction = Transaction.newUTXOTransaction(from, to, amount, blockchain);
        //Reward
        Transaction rewardTx = Transaction.newCoinbaseTX(from, "");
        Block newBlock = blockchain.mineBlock(new Transaction[]{transaction, rewardTx});
        new UTXOSet(blockchain).update(newBlock);
        
        ...
    }
    
    ...
        
}

It is also necessary to modify the transaction verification method, and the direct verification of coinbase transaction passes:

public class Blockchain {
    
  /**
     *Transaction signature verification
     *
     * @param tx
     */
    private boolean verifyTransactions(Transaction tx) {
        if (tx.isCoinbase()) {
            return true;
        }
    
        ...
    }
    
    ...
    
}

In our implementation logic, the token is also the producer of the block, so the reward belongs to him.

Let’s test the reward mechanism:

$ java -jar blockchain-java-jar-with-dependencies.jar  createwallet 
wallet address : 1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUD

$ java -jar blockchain-java-jar-with-dependencies.jar  createwallet 
wallet address : 17crpQoWy7TEkY9UPjZ3Qt9Fc2rWPUt8KX

$ java -jar blockchain-java-jar-with-dependencies.jar  createwallet 
wallet address : 12L868QZW1ySYzf2oT5ha9py9M5JrSRhvT

$ java -jar blockchain-java-jar-with-dependencies.jar  createblockchain -address 1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUD

Elapsed Time: 17.973 seconds
correct hash Hex: 0000defe83a851a5db3803d5013bbc20c6234f176b2c52ae36fdb53d28b33d93 

Done ! 

$ java -jar blockchain-java-jar-with-dependencies.jar  send -from 1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUD -to 17crpQoWy7TEkY9UPjZ3Qt9Fc2rWPUt8KX -amount 6
Elapsed Time: 30.887 seconds
correct hash Hex: 00005fd36a2609b43fd940577f93b8622e88e854f5ccfd70e113f763b6df69f7 

Success!


$ java -jar blockchain-java-jar-with-dependencies.jar  send -from 1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUD -to 12L868QZW1ySYzf2oT5ha9py9M5JrSRhvT -amount 3
Elapsed Time: 45.267 seconds
correct hash Hex: 00009fd7c59b830b60ec21ade7672921d2fb0962a1b06a42c245450e47582a13 

Success!

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUD
Balance of '1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUD': 21

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 17crpQoWy7TEkY9UPjZ3Qt9Fc2rWPUt8KX
Balance of '17crpQoWy7TEkY9UPjZ3Qt9Fc2rWPUt8KX': 6

$ java -jar blockchain-java-jar-with-dependencies.jar  getbalance -address 12L868QZW1ySYzf2oT5ha9py9M5JrSRhvT
Balance of '12L868QZW1ySYzf2oT5ha9py9M5JrSRhvT': 3

1MpdtjTEsDvrkrLWmMswq4K3VPtevXXnUDThe address received three awards:

  • The first time is to exploit Chuangshi block;
  • The second mining block is 00005fd36a2609b43fd940577f93b8622e88e854f5ccfd70e113f763b6df69f7
  • The third mining block: 00009fd7c59b830b60ec21ade7672921d2fb0962a1b06a42c245450e47582a13

Merkle Tree

Merkle tree is a mechanism that we need to focus on in this article.

As I mentioned earlier, the entire bitcoin database takes up about 140g of disk space. Due to the distributed nature of bitcoin, each node in the network must be independent and self-sufficient. Each bitcoin node is a function collection of routing, blockchain database, mining and wallet services. Each node participates in the routing function of the whole network, and may also contain other functions. Each node participates in verifying and disseminating transaction and block information, discovering and maintaining the connection with peer nodes. A full node includes the following four functions:

As more and more people start using bitcoin, this rule has become increasingly difficult to follow: it’s not realistic for everyone to run a complete node. Published by NakamotoBitcoin white paperThis paper proposes a solution to this problem: simplified payment verification (SPV). SPV is the lightweight node of bitcoin. It does not need to download all the blockchain dataThere is no need to validate blocks and transaction data。 On the contrary, when SPV wants to verify the validity of a transaction, it will retrieve some required data from the whole node to which it is connected. This mechanism ensures that when there is only one full node, multiple SPV light wallet nodes can be run.

For more information on SPV, please see:Chapter 8: Mastering bitcoin (2nd Edition)

In order to make SPV possible, it is necessary to have a method to check whether a block contains a transaction without downloading block data in full. That’s where Merkle tree works.

The Merkle tree used in bitcoin is to obtain the hash value of the transaction, and then the hash value recognized by the pow system will be saved in the block header. So far, we have simply calculated the hash value of each transaction in a block, and then processed these transactions when we prepared the pow dataSHA-256calculation. Although this is a good way to get the unique representation of block transactions, it does not have the advantages of going to Merkle tree.

Take a look at the structure of Merkle tree

Building blockchain based on Java language (6) -- Merkle tree

Each block will build a Merkle tree, which starts from the bottom leaf node, and each transaction’s hash is a leaf node (double sha256 algorithm used in bitcoin). The number of leaf nodes must be even, but not every block can contain even number of transaction data. If there is an odd number of transactions, then the last one will be copied (this only happens in Merkle tree, not in blocks).

Moving from bottom to top, leaf nodes are grouped in pairs, their hash values are connected together, and then new hash values are calculated again. The new hash forms a new tree node. Until the last node is called the root. The hash of the root node is the only representative of the transaction data in the block. It will be saved in the block header and used to participate in the calculation of the pow system.

The advantage of Merkle tree is that nodes can verify the validity of a transaction without downloading the whole block. To do this, you only need to trade hash, Merkle tree root hash and Merkle paths.

The Merkle tree code is implemented as follows:

package one.wangwei.blockchain.transaction;

import com.google.common.collect.Lists;
import lombok.Data;
import one.wangwei.blockchain.util.ByteUtils;
import org.apache.commons.codec.digest.DigestUtils;

import java.util.List;

/**
 *Merkel tree
 *
 * @author wangwei
 * @date 2018/04/15
 */
@Data
public class MerkleTree {

    /**
     *Root node
     */
    private Node root;
    /**
     *Leaf node hash
     */
    private byte[][] leafHashes;

    public MerkleTree(byte[][] leafHashes) {
        constructTree(leafHashes);
    }

    /**
     *Build the entire Merkle tree from the bottom leaf node up
     *
     * @param leafHashes
     */
    private void constructTree(byte[][] leafHashes) {
        if (leafHashes == null || leafHashes.length < 1) {
            throw new RuntimeException("ERROR:Fail to construct merkle tree ! leafHashes data invalid ! ");
        }
        this.leafHashes = leafHashes;
        List<Node> parents = bottomLevel(leafHashes);
        while (parents.size() > 1) {
            parents = internalLevel(parents);
        }
        root = parents.get(0);
    }

    /**
     *Build a hierarchical node
     *
     * @param children
     * @return
     */
    private List<Node> internalLevel(List<Node> children) {
        List<Node> parents = Lists.newArrayListWithCapacity(children.size() / 2);
        for (int i = 0; i < children.size() - 1; i += 2) {
            Node child1 = children.get(i);
            Node child2 = children.get(i + 1);

            Node parent = constructInternalNode(child1, child2);
            parents.add(parent);
        }

        //The number of internal nodes is odd, only the left node is calculated
        if (children.size() % 2 != 0) {
            Node child = children.get(children.size() - 1);
            Node parent = constructInternalNode(child, null);
            parents.add(parent);
        }

        return parents;
    }

    /**
     *Bottom node construction
     *
     * @param hashes
     * @return
     */
    private List<Node> bottomLevel(byte[][] hashes) {
        List<Node> parents = Lists.newArrayListWithCapacity(hashes.length / 2);

        for (int i = 0; i < hashes.length - 1; i += 2) {
            Node leaf1 = constructLeafNode(hashes[i]);
            Node leaf2 = constructLeafNode(hashes[i + 1]);

            Node parent = constructInternalNode(leaf1, leaf2);
            parents.add(parent);
        }

        if (hashes.length % 2 != 0) {
            Node leaf = constructLeafNode(hashes[hashes.length - 1]);
            //In the case of an odd number of nodes, copy the last node
            Node parent = constructInternalNode(leaf, leaf);
            parents.add(parent);
        }

        return parents;
    }

    /**
     *Building leaf nodes
     *
     * @param hash
     * @return
     */
    private static Node constructLeafNode(byte[] hash) {
        Node leaf = new Node();
        leaf.hash = hash;
        return leaf;
    }

    /**
     *Building internal nodes
     *
     * @param leftChild
     * @param rightChild
     * @return
     */
    private Node constructInternalNode(Node leftChild, Node rightChild) {
        Node parent = new Node();
        if (rightChild == null) {
            parent.hash = leftChild.hash;
        } else {
            parent.hash = internalHash(leftChild.hash, rightChild.hash);
        }
        parent.left = leftChild;
        parent.right = rightChild;
        return parent;
    }

    /**
     *Computing internal node hash
     *
     * @param leftChildHash
     * @param rightChildHash
     * @return
     */
    private byte[] internalHash(byte[] leftChildHash, byte[] rightChildHash) {
        byte[] mergedBytes = ByteUtils.merge(leftChildHash, rightChildHash);
        return DigestUtils.sha256(mergedBytes);
    }

    /**
     *Merkle tree node
     */
    @Data
    public static class Node {
        private byte[] hash;
        private Node left;
        private Node right;
    }

}

Then modify itBlock.hashTransactionInterface:

public class Block {
    
   ... 

   /**
     *Hash calculation of transaction information in the block
     *
     * @return
     */
    public byte[] hashTransaction() {
        byte[][] txIdArrays = new byte[this.getTransactions().length][];
        for (int i = 0; i < this.getTransactions().length; i++) {
            txIdArrays[i] = this.getTransactions()[i].hash();
        }
        return new MerkleTree(txIdArrays).getRoot().getHash();
    }
    
    ...
    
}

The hash value of merkletree’s root node is the only representative of transaction information in the block.

Summary

In this section, we further optimize the previous trading mechanism by adding utxo pool and Merkle tree mechanism.

data

  1. Source code:https://github.com/wangweiX/b…
  2. The UTXO Set:_Data_Storage#The_UTXO_set_.28chainstate_leveldb.29)
  3. UTXO set statistics
  4. Merkle Tree
  5. Why every Bitcoin user should understand “SPV security”
  6. Script
  7. “Ultraprune” Bitcoin Core commit
  8. Smart contracts and Bitcoin

Building blockchain based on Java language (6) -- Merkle tree