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:
- First, we need to traverse the entire blockchain to find all the unused transaction output locked in the source address.
- Record all found utxos that contain eligible transaction output in the collection.
- Traverse the collection, and add up the unused output in each UTXO until the transfer amount is met or all utxos are counted.
- Create the transaction output in each UTXO of the statistics as a new transaction input for consumption.
- 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.
- 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:
- Address is your own address
- 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