Build your first blockchain network

Keywords: Blockchain Java log4j Attribute

Previous article: Build your first blockchain network (3)

UTXO

component

UTXO is an important concept in bitcoin. In this section, we will implement a simple UTXO. We divide the components of UTXO into the following three points:

  • UTXOId: identify the UTXO
  • TxInput: transaction input, i.e. input address and amount of coin
  • TxOutput: transaction output, i.e. the output address and amount of coin

Where TxInput and TxOutput have the following properties respectively:

TxInput: transaction input

  • preTxId: id of the previous UTXO pointed to
  • value: amount entered
  • unLockScript: Unlock script

The transaction input needs to refer to the output of the previous UTXO. In this way, it is easy to know that the input amount of the current transaction is transferred by the output amount of the previous transaction.
Ensure that the source address of each unused amount can be found.
The unlocking script is used to unlock the transaction output referenced by the current transaction input. Because each amount belongs to a certain address. Only the owner of the amount has
Authority consumption. The unlocking script is usually a digital signature.


TxOutput transaction output

  • value: output amount
  • lockScript: Lock script

Every time a coin is transferred, it will be locked on an address, so the locking script is usually an address.

For each UTXO, the input amount must be equal to the output amount. In addition, UTXO has a feature that it cannot only spend a part of it. It needs to be consumed completely, and the surplus is returned to the original address.
For example, user a has 10 coins locked in a UTXO. If a needs to transfer to b5 coins, it needs to spend all 10 coins. Five of them are output to the address of b, and the remaining five are output to the address of A.
Therefore, a UTXO can have multiple transaction outputs as well as multiple inputs.

The general concept is almost introduced. Let's implement it:

#TxInput.java
//Because we use serialization to save the block, and the data needs to be written to the block, we need to implement the Serializable interface
public class TxInput implements Serializable{
    private static final long serialVersionUID = 1L;
    //Previous transaction ID referenced
    public String preTxId;
    //The coin contained in this input
    public int values;
    //Unlock script is usually digitally signed
    public String unLockScript;
    public TxInput(String txId, TxOutput top, Wallet wallet) throws Exception {
        //Sign the address in the referenced TxOutPut to unlock the referenced TxOutPut
        this.unLockScript = wallet.sign(top.getLockScript());
        //Last transaction ID referenced by the record
        this.preTxId = txId;
        //The coin value is equal to the coin value of the referenced Txoutput
        this.values = top.value;
    }
}

Next is the transaction output:

#TxOutput.java

@Getter
public class TxOutput implements Serializable{
    //Similarly, we need to implement the Serializable interface
    private static final long serialVersionUID = 1L;
    //The coin value of the transaction output.
    public int value;
    //Lock script is usually address
    public String lockScript;
    public TxOutput(int value,String address){
        this.value = value;
        this.lockScript = address;
    }
}

Finally, the implementation of UTXO: we use Transaction for representation.

#Transaction.java

@Getter
@Setter
public class Transaction implements Serializable{
    //For the convenience of later debugging, the package of log4j is introduced. The import method is the same as before
    private transient static final Logger LOGGER = Logger.getLogger(Transaction.class);
    private static final long serialVersionUID = 1L;
    //COINBASE to explain later
    private transient static final int COINBASE = 50;
    //UTXOId
    public String txId;
    //Set of transaction inputs
    public ArrayList<TxInput> tips;
    //Set of transaction outputs String:address
    public HashMap<String, TxOutput> tops;

    private Transaction() {
        #Only the set to save the transaction output is created here, because the Coinbase is involved, the ArrayList will not be created for now
        this.tops = new HashMap<>(4);
    }
    @Override
    public String toString(){
        return JSONObject.toJSONString(this);
    }
}

log4j log package:

        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>

For convenience, ArrayList and HashMap are used to store transaction input and output

Create UTXO

Next is the core method of creating UTXO, which is more complex. Let's analyze it first:
The first parameters passed in need to be: the source address of the send coin, the destination address of the send coin, and the value of the coin.
The return value is a Transaction instance.

Next, we analyze how to create UTXO:

  1. First, we need to traverse the entire blockchain to find all the unused transaction output locked in the source address.
  2. Record all found utxos that contain eligible transaction output in the collection.
  3. Traverse the collection, and add up the unused output in each UTXO until the transfer amount is met or all utxos are counted.
  4. Create the transaction output in each UTXO of the statistics as a new transaction input for consumption.
  5. Determine whether the value of the coin is exactly equal to the coin to be transferred. If equal, create a transaction output to transfer the coin to the destination address.
  6. If there is any extra, create another transaction output to return the extra coin to the source address.

OK, after analysis, we can develop:

#Transaction.java
    public static Transaction newUTXO(String fromAddress, String toAddress, int value)
            throws NoSuchAlgorithmException, Exception {
        //Step 1 traverse the blockchain to count UTXO
        Transaction[] txs = Blockchain.getInstance().findAllUnspendableUTXO(fromAddress);
        if (txs.length == 0) {
            LOGGER.info("Current address"+fromAddress+"No unused UTXO!!!");
            throw new Exception("Current address"+fromAddress+"No unused UTXO,Transaction failed!!!");
        }
        TxOutput top;
        //Record the TxOutput to be used
        HashMap<String, TxOutput> tops = new HashMap<String, TxOutput>();
        int maxValue = 0;
        //Traversal transaction set
        for (int i = 0; i < txs.length; i++) {
            //Find TxOutput including address fromAddress
            if (txs[i].tops.containsKey(fromAddress)) {
                top = txs[i].tops.get(fromAddress);
                //Add to Map
                tops.put(txs[i].txId, top);
                //Record the value in the TxOutput
                maxValue += top.value;
                //Exit if larger than needed
                if (maxValue >= value) {
                    break;
                }
            }
        }
        //Is there enough coin
        if (maxValue >= value) {
            //Create tx
            Transaction t = new Transaction();
            t.tips = new ArrayList<TxInput>(tops.size());
            //Traverse all the Txoutput needed
            tops.forEach((s, to) -> {
                //Change to TxInput
                try {
                    t.tips.add(new TxInput(s, to, Wallet.getInstance()));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            //If the values are not equal
            if(maxValue>value){
                //Create TxOutput to return extra coin
                top = new TxOutput(maxValue-value, Wallet.getInstance().getAddress());
                t.tops.put(top.getLockScript(), top);
            }
            //Destination address
            top = new TxOutput(value, toAddress);
            t.tops.put(top.getLockScript(), top);
            LOGGER.info("establish UTXO: "+t.toString());
            return t;
        }
        LOGGER.info("Insufficient balance of current address!!,The balance is"+maxValue);
        throw new Exception("Insufficient balance of current address!!,The balance is"+maxValue);
    }

Count unused UTXO

Then there is another core method, which is to count all qualified but not consumed utxos in the blockchain:
We use a relatively simple and easy to understand way to count all transaction output of address matching first.
Then count all the transaction inputs that meet the conditions. Transaction input needs to meet two conditions:

  1. Address is your own address
  2. The UTXOid referenced in the transaction input can be traced back to.

We will match the UTXOId referenced in the qualified TxInput in all the unused utxos,
If the match indicates that the UTXO has been spent, we remove the spent UTXO, and the rest is the unused UTXO that meets the conditions.

#Blockchain.java
public Transaction[] findAllUnspendableUTXO(String address)
            throws FileNotFoundException, ClassNotFoundException, IOException {
        LOGGER.info("Find all not consumed UTXO...............");
        HashMap<String, Transaction> txs = new HashMap<>();
        Block block = this.block;
        Transaction tx;
        //Traversing forward from the current block to find UTXO txOutput
        do{
            //Get transaction information from blocks
            tx = block.getTransaction();
            //If transaction information exists and TxOutput address contains address
            if (tx != null && tx.getTops().containsKey(address)) {
                txs.put(tx.getTxId(), tx);
            }
            //Point to previous block
            block = block.getPrevBlock();
            //All the way to Chuangshi block
        }while(block!=null && block.hasPrevBlock()) ;
        //Traverse again to find the consumed UTXO
        block = this.block;
        do {
            tx = block.getTransaction();
            if (tx != null) {
                //If the TxInput in the transaction contains a transaction ID that exists in txs, remove
                tx.getTips().forEach(tip -> {
                    try {
                        //Two conditions need to be met. One is that the UTXOId referenced in Txinput exists, indicating that the UTXO has been used
                        //The second is to ensure that the address is the same, and confirm that the TxInput is consumed by the coin owner
                        if (Wallet.getInstance().verify(address,tip.unLockScript) 
                                && txs.containsKey(tip.preTxId))
                            //Remove the UTXO if two conditions are met
                            txs.remove(tip.preTxId);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                });
            }
            block = block.getPrevBlock();
        }while (block!=null && block.hasPrevBlock());
        //Create UTXO array return
        Transaction[] t = new Transaction[txs.size()];
        return txs.values().toArray(t);
    }

Look at the code here:

 if (Wallet.getInstance().verify(address,tip.unLockScript) && txs.containsKey(tip.preTxId))
    ...

First, verify whether the unlocking script of TxInput has signed the address of our wallet, that is, verify whether this input is consumed by ourselves.
If it is consumed by itself and then compared with the Id of UTXO, if it is the same, it means that this UTXO has been consumed. Then it needs to be removed.
Add a new method to the wallet to verify whether the unlocking script can unlock the transaction output. We simply use hash matching to simulate the verification.

#Wallet.java
    public boolean verify(String data,String sign) throws DecoderException, Exception {
        LOGGER.info("Verify signature: "+data);
        String[] str = data.split("%%%");
        //Original (hash)
        if(str.length!=2){
            return false;
        }
        String hash2 = Hex.encodeHexString(this.decrypt(str[1]));
        String hash3 = Util.getSHA256(data);
        if(hash3.equals(hash2)){
            LOGGER.info("Signature verification succeeded!!");
            return true;
        }
        LOGGER.info("Signature verification failed!!");
        return false;
    }

Update block information

With the concept of UTXO added, we need to update the attribute information of blocks and blockchains.

#Block.java
@Getter
@Setter
public class Block implements Serializable{
    ...
    //Transactions in the current block
    public Transaction transaction;
    ...
    //Add a new construction method
    public Block(int blkNum,Transaction transaction,String prevBlockHash){
        this.blkNum = blkNum;
        this.transaction = transaction;
        this.prevBlockHash = prevBlockHash;
        this.timeStamp = Util.getTimeStamp();
    }
    ...
}

Then there is the blockchain, which also needs to update a method:

#Blockchain.java
public final class Blockchain {
    ...
    public Block addBlock(Transaction tx) throws IOException {
        int num = this.block.getBlkNum();
        Block block = new Block(num + 1, tx, this.block.curBlockHash);
        //The difficulty value needs to be calculated before each block is added to the blockchain
        block.setNonce(Pow.calc(block));
        //Calculate block hash value
        String hash = Util.getSHA256(block.getBlkNum() + block.getData() + block.getPrevBlockHash()
                + block.getPrevBlockHash() + block.getNonce());
        block.setCurBlockHash(hash);
        //Serialization
        Storage.Serialize(block);
        this.block = block;
        LOGGER.info("The current block information is:"+block.toString());
        return this.block;
    }
    ...
}

In the previous block, the string is saved as block information, and we update it as a transaction. A transaction needs to be created before a block can be created.

Coinbase

As for UTXO, we mentioned before that each output corresponds to an input, so where is the first output coin,
In BItcoin, a certain amount of BItcoin, called Coinbase, will be rewarded for each block produced. In the same way, we have achieved it here.
So the first coin output comes from coinbase. We fixed the coinbase to 50, just like the attribute set before:

#Transaction.java
    private transient static final int COINBASE = 50;

So we also need a construction method to generate a coinbase transaction:

#Blockchain.java
    public static Transaction newCoinBase() throws NoSuchAlgorithmException, Exception {
        Transaction t = new Transaction();
        t.tips = new ArrayList<>();
        t.tops.put(Wallet.getInstance().getAddress(), new TxOutput(COINBASE, Wallet.getInstance().getAddress()));
        LOGGER.info("establish Coinbase....."+t.toString());
        return t;
    }

As you can see, the address of transaction output is set as the address of wallet.
It's relatively simple. Next, modify the generation method of Chuangshi block to add the coinbase transaction.

#Blockchain.java
    private Block CrtGenesisBlock() throws NoSuchAlgorithmException, Exception {
        // Block block = new Block(1,"Genesis Block","00000000000000000");
        Block block = new Block(1, Transaction.newCoinBase(), "00000000000000000");
        ...
    }

test

It's all done. Test it out:

#Test.java
public class Test {
    public static void main(String[] args) throws NoSuchAlgorithmException, Exception {
        Blockchain.getInstance().addBlock(
            Transaction.newUTXO(
                Wallet.getInstance().getAddress(), "address", 30));
        Blockchain.getInstance().addBlock(
            Transaction.newUTXO(
                "address", "address1", 20));
    }
}

Analyze the following test cases:

Blockchain.getInstance()
#############################
#Blockchain.java CrtGenesisBlock()
Block block = new Block(1, Transaction.newCoinBase(), "00000000000000000");
#newCoinBase()
t.tops.put(Wallet.getInstance().getAddress(), new TxOutput(COINBASE, Wallet.getInstance().getAddress()));

First, get the blockchain instance, so create the creation block. Look at the code above, we can see that a coinbase transaction has been created, and 50 coins are locked in the address of our wallet.

Blockchain.getInstance().addBlock(
            Transaction.newUTXO(
                Wallet.getInstance().getAddress(), "address", 30));

Then there is the second block. We create a UTXO, which transfers 30 coin s from the address of the wallet to the address.

        Blockchain.getInstance().addBlock(
            Transaction.newUTXO(
                "address", "address1", 20));

Finally, the third block transfers 20 coin from address to address 1
Test it:

...
[INFO ] 2020-05-18 14:10:19,501 method:org.xd.chain.transaction.Transaction.newCoinBase(Transaction.java:42)
//82227e356c811830d6b5b56d862880b85f20355cfbc387641653a "," value ": 50}}}}
...
[INFO ] 2020-05-18 14:10:19,757 method:org.xd.chain.core.Blockchain.findAllUnspendableUTXO(Blockchain.java:117)
//Find all unused utxos
[INFO ] 2020-05-18 14:10:19,804 method:org.xd.chain.wallet.Wallet.sign(Wallet.java:86)
//Use private key to sign data: r1363548f89465529dd73922b26547b6110f9159a30cc9b047435c1c0db37842cd3c410082227e356c811830d6b5b56d862880b85f20355cfbc387641653a
[INFO ] 2020-05-18 14:10:20,382 method:org.xd.chain.transaction.Transaction.newUTXO(Transaction.java:100)
//Create utxo: {"tips":[{"unLockScript":"523133363335343866383934363532396464373339323262323635343762363131306639313539613330636339623034373434333563316330646233373834326364336334313030303832313832323237653335366338313138333064366235623536643836323838306238356632303335356366626333383736343136353361%%%4251d1ae7091f422bef3a95b29867ebeaeeedb0e20239a4edf96967d1a1f15 a8f061873acc01aa86c726e1ad128aefeaaf5c447aed27e5729bdd24f0026d6c23","values":50}], "tops":{"R1363548f8946529dd73922b26547b6110f9159a30cc9b0474435c1c0db37842cd3c4100082182227e356c811830d6b5b56d862880b85f20355cfbc387641653a":{"lockScript":"R1363548f8946529dd73922b26547b6110f9159a30cc9b0474435c1c0db37842cd3c4100082182227e356c811830d6b5b56d862880b85f20355cfbc387641653a","value":20},"address":{"lockScript":"address","value":30}}}
...
[INFO ] 2020-05-18 14:10:20,468 method:org.xd.chain.core.Blockchain.addBlock(Blockchain.java:86)
//The current block information is: {"blknum": 2, "curblockhash": "00005f0690489e8763bd3db0ce7112592cdb118507945c65d07bfe27e0ad3031", "nonce": 4350, "prevblockhash": "000011de81afdac44e08e81b9be434cb625808a9d0f8008275ab9a6ffb809f", "timestamp": "2020-05-18 14:10:20", "transaction":{"tips":[{"unLockScript":"523133363335343866383934363532396464373339323262323635343762363131306639313539613330636339623034373434333563316330646233373834326364336334313030303832313832323237653335366338313138333064366235623536643836323838306238356632303335356366626333383736343136353361%%%4251d1ae7091f422bef3a95b29867ebeaeeedb0e20239a4e df96967d1a1f15a8f061873acc01aa86c726e1ad128aefeaaf5c447aed27e5729bdd24f0026d6c23","values":50}], "tops":{"R1363548f8946529dd73922b26547b6110f9159a30cc9b0474435c1c0db37842cd3c4100082182227e356c811830d6b5b56d862880b85f20355cfbc387641653a":{"lockScript":"R1363548f8946529dd73922b26547b6110f9159a30cc9b0474435c1c0db37842cd3c4100082182227e356c811830d6b5b56d862880b85f20355cfbc387641653a","value":20},"address":{"lockScript":"address","value":30}}}}
[INFO ] 2020-05-18 14:10:20,575 method:org.xd.chain.core.Blockchain.findAllUnspendableUTXO(Blockchain.java:117)
//Find all unused utxos
...
[INFO ] 2020-05-18 14:10:20,725 method:org.xd.chain.wallet.Wallet.verify(Wallet.java:124)
//Verify signature: address
...
[INFO ] 2020-05-18 14:10:20,731 method:org.xd.chain.wallet.Wallet.sign(Wallet.java:86)
//Use private key to sign data: address
[INFO ] 2020-05-18 14:10:20,734 method:org.xd.chain.transaction.Transaction.newUTXO(Transaction.java:100)
//Create utxo: {"tips": [{"unlockscript": "616472657373%%% 8ad16095a9f1947e323eb5ef3601a0cc2ad552ad3f7331406123577a1cc0c68dc614f3262505f079f73acfc1d681fdb432f7ba0f4ac3d69cb46dead5446b2cd", "values: 30}], "tops":{"R1363548f8946529dd73922b26547b6110f9159a30cc9b0474435c1c0db37842cd3c4100082182227e356c811830d6b5b56d862880b85f20355cfbc387641653a":{"lockScript":"R1363548f8946529dd73922b26547b6110f9159a30cc9b0474435c1c0db37842cd3c4100082182227e356c811830d6b5b56d862880b85f20355cfbc387641653a","value":10},"address1":{"lockScript":"address1","value":20}}}
...
[INFO ] 2020-05-18 14:10:20,990 method:org.xd.chain.core.Blockchain.addBlock(Blockchain.java:86)
//The current block information is: {"blknum": 3, "curblockhash": "00006e8918bba9831374f578caa3d80fa9369975972145bc0654cbca2d084e", "nonce": 74607, "prevblockhash": "00005f0690489e8763bd3db0ce7112592cdb118507945c65d07bfe27e0ad3031", "timestamp": "2020-05-18 14:10:20", "transaction":{"tips":[{"unLockScript":"61646472657373%%%8ad16095a9f1947e323eb5ef3601a0cc2ad552ad3f7331406123577a1cc0c68dc614f3262505f079f7c3acfc1d681fdb432f7ba0f4ac3d69cb46dead5446b2cd","values":30}], "tops":{"R1363548f8946529dd73922b26547b6110f9159a30cc9b0474435c1c0db37842cd3c4100082182227e356c811830d6b5b56d862880b85f20355cfbc387641653a":{"lockScript":"R1363548f8946529dd73922b26547b6110f9159a30cc9b0474435c1c0db37842cd3c4100082182227e356c811830d6b5b56d862880b85f20355cfbc387641653a","value":10},"address1":{"lockScript":"address1","value":20}}}}

Omitting other log information, it seems that there is no problem in the test, and a total of 3 blocks have been generated. There is a Coinbase transaction in the genesis block. Block 2 successfully transfers 30coin to the address, and returns 20coin to the original address. Block 3 successfully transfers 20coin from address address to address address1, and returns 10coin to address address.

For example, coinbase is only generated in the genesis block. Each block contains only one transaction and so on, which will be gradually improved later.

Github warehouse address is here, keep it updated

Github address: Jchain

Posted by tomtimms on Tue, 19 May 2020 05:33:23 -0700