搭建你的第一个区块链网络(四):UTXO

阅读: 评论:0

搭建你的第⼀个区块链⽹络(四):UTXO
前⼀篇⽂章:
UTXO
组成部分
UTXO是⽐特币中⼀个重要的概念,这⼀节我们来实现⼀个简单的UTXO。我们把UTXO的组成部分分为以下三点:
UTXOId: 标识该UTXO
TxInput: 交易输⼊,即coin的输⼊地址以及⾦额
TxOutput: 交易输出,即coin的输出地址以及⾦额
其中TxInput与TxOutput分别具有以下⼏个属性:
TxInput: 交易输⼊
preTxId: 指向的前⼀个UTXO的id
value:输⼊的⾦额
unLockScript: 解锁脚本
其中交易输⼊需要引⽤之前的UTXO的输出。这样很容易知道当前的交易输⼊的⾦额是由之前的哪⼀笔交易中的交易输出的⾦额传递的。
保证每⼀笔未消费的⾦额都可以到它的源地址。
解锁脚本的作⽤是⽤于解锁当前交易输⼊所引⽤的交易输出的。因为每⼀笔⾦额都有所属,被锁定在某⼀个地址上⾯。只有该⾦额的所有者才具有权限消费进⾏消费。⽽解锁脚本⼀般都是⼀个数字签名。
TxOutput 交易输出
value :输出的⾦额
lockScript: 锁定脚本
每当⼀笔coin被转移,都会被锁定在⼀个地址上⾯,因此锁定脚本⼀般都是⼀个地址。
对于每⼀笔UTXO,输⼊的⾦额⼀定是等于输出的⾦额的。另外UTXO有⼀个特点,就是不能够只花费其中⼀部分。⽽是需要全部消费,⽽多余的再返还给原地址。
⽐如⽤户a具有10个coin被锁定在⼀个UTXO中,如果a需要转账给b5个coin,那么需要将10个coin全部花费掉,其中5个coin输出到b的地址,剩余的5个coin输出到a的地址。
因此⼀笔UTXO可以有多个交易输出,同时也可以有多个输⼊。
⼤致概念介绍差不多了,我们来实现它:
#TxInput.java
//因为我们采⽤的序列化保存区块,⽽该数据需要写⼊区块,因此需要实现Serializable接⼝
public class TxInput implements Serializable{
private static final long serialVersionUID = 1L;
// 所引⽤的前⼀个交易ID
public String preTxId;
// 该输⼊中包含的coin
public int values;
// 解锁脚本通常为数字签名
public String unLockScript;
public TxInput(String txId, TxOutput top, Wallet wallet) throws Exception {
//对引⽤的Txoutput中的地址进⾏签名,⽤于解锁引⽤的TxOutPut.
this.unLockScript = wallet.LockScript());
//记录引⽤的上⼀个交易ID
this.preTxId = txId;
//coin值等于引⽤的Txoutput的coin值
this.values = top.value;
}
}
接下来是交易输出:
#TxOutput.java
@Getter
public class TxOutput implements Serializable{
//同理需要实现Serializable接⼝
private static final long serialVersionUID = 1L;
// 交易输出的coin值。
public int value;
//锁定脚本通常为地址
public String lockScript;
public TxOutput(int value,String address){
this.value = value;
this.lockScript = address;
}
}
最后是UTXO的实现: 我们使⽤Transaction进⾏表⽰。
#Transaction.java
@Getter
@Setter
public class Transaction implements Serializable{
/
/为了后期调试⽅便,引⼊了log4j的包,导⼊⽅法和之前⼀样
private transient static final Logger LOGGER = Logger(Transaction.class);
private static final long serialVersionUID = 1L;
//COINBASE之后再进⾏解释
private transient static final int COINBASE = 50;
//UTXOId
public String txId;
// 交易输⼊的集合
public ArrayList<TxInput> tips;
// 交易输出的集合 String:address
public HashMap<String, TxOutput> tops;
private Transaction() {
#这⾥只创建了保存交易输出的集合,因为涉及到Coinbase,暂时先不创建ArrayList
}
@Override
public String toString(){
硝酸铯
JSONString(this);
}
}
log4j⽇志包:
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>长江电力商务网
<version>1.2.17</version>
</dependency>
这⾥为了⽅便起见分别使⽤ArrayList和HashMap存储交易输⼊与输出.
创建UTXO
接下来是创建UTXO的核⼼⽅法,⽐较复杂,我们先来分析⼀下:
⾸先传⼊的参数需要有: 发送coin的源地址,发送coin的⽬的地址,coin的值。
返回值为⼀个Transaction实例。
接下来分析如何创建UTXO:
⾸先需要遍历整个区块链,查到所有未消费的被锁定在源地址的交易输出。
将查到的所有包含符合条件的交易输出的UTXO记录在集合中。
遍历该集合,将每⼀笔UTXO中未消费的输出相加,直到满⾜转账⾦额或者是统计完全部UTXO。
将统计的每⼀笔UTXO中交易输出创建为新的交易输⼊⽤于消费。
判断是否coin值刚好等于需要转账的coin。如果相等则创建⼀个交易输出将coin转账到⽬的地址。
如果有多余的则再创建⼀个交易输出返回多余的coin到源地址。
OK,分析完了可以开发了:
#Transaction.java
public static Transaction newUTXO(String fromAddress, String toAddress, int value)
throws NoSuchAlgorithmException, Exception {
//第⼀步遍历区块链统计UTXO
Transaction[] txs = Instance().findAllUnspendableUTXO(fromAddress);
if (txs.length == 0) {
LOGGER.info("当前地址"+fromAddress+"没有未消费的UTXO");
throw new Exception("当前地址"+fromAddress+"没有未消费的UTXO,交易失败");
}
TxOutput top;
// 记录需要使⽤的TxOutput
HashMap<String, TxOutput> tops = new HashMap<String, TxOutput>();
int maxValue = 0;
// 遍历交易集合
for (int i = 0; i < txs.length; i++) {
// 查包括地址为fromAddress的TxOutput
if (txs[i].ainsKey(fromAddress)) {
top = txs[i].(fromAddress);
// 添加进Map
tops.put(txs[i].txId, top);
// 记录该TxOutput中的value
maxValue += top.value;
// 如果⼤于需要使⽤的则退出
if (maxValue >= value) {
break;
}
}
新婚熄与翁公李钰雯}
// 是否有⾜够的coin
if (maxValue >= value) {
// 创建tx
Transaction t = new Transaction();
t.tips = new ArrayList<TxInput>(tops.size());
// 遍历所有需要⽤到的Txoutput
tops.forEach((s, to) -> {
// 变为TxInput
try {
t.tips.add(new TxInput(s, to, Instance()));
} catch (Exception e) {
e.printStackTrace();
}
});
//如果值不均等
if(maxValue>value){
//创建TxOutput返还多余的coin
top = new TxOutput(maxValue-value, Instance().getAddress());
}
//⽬的地址
top = new TxOutput(value, toAddress);
LOGGER.info("创建UTXO: "+t.toString());
return t;
}
LOGGER.info("当前地址余额不⾜!!,余额为"+maxValue);
throw new Exception("当前地址余额不⾜!!,余额为"+maxValue);
}
统计未消费的UTXO
然后是另外⼀个核⼼⽅法,统计区块链中符合条件的全部未消费的UTXO:
我们使⽤⽐较简单易理解的⽅式,先统计地址匹配的所有的交易输出。
然后统计所有的满⾜条件的交易输⼊。交易输⼊需要满⾜两个条件:
地址是⾃⼰的地址
交易输⼊中引⽤的UTXOid可以追溯到。
我们将符合条件的TxInput中引⽤的UTXOId在所有未消费的UTXO中匹配,
如果匹配到说明该UTXO已经被花费掉了,我们移除掉花费掉的UTXO,剩下的就是满⾜条件的未消费的UTXO了。#Blockchain.java
public Transaction[] findAllUnspendableUTXO(String address)
throws FileNotFoundException, ClassNotFoundException, IOException {
LOGGER.info("查所有未消费的");
HashMap<String, Transaction> txs = new HashMap<>();
Block block = this.block;
Transaction tx;
// 从当前区块向前遍历查UTXO txOutput
do{
//从区块中获取交易信息
tx = Transaction();
// 如果存在交易信息,且TxOutput地址包含address
if (tx != null && tx.getTops().containsKey(address)) {
txs.TxId(), tx);
}
//指向前⼀个区块
block = PrevBlock();
//⼀直遍历到创世区块
}while(block!=null && block.hasPrevBlock()) ;
// 再遍历⼀次查已消费的UTXO
block = this.block;
do {
tx = Transaction();
if (tx != null) {
// 如果交易中的TxInput包含的交易ID存在于txs,移除
try {
//需要满⾜两个条件,⼀是Txinput中引⽤的UTXOId存在,说明该UTXO已经被使⽤了
//⼆是需要保证地址相同,确认该TxInput是coin所有者消费的
if (Instance().verify(address,tip.unLockScript)
&& ainsKey(tip.preTxId))
//满⾜两个条件则移除该UTXO
} catch (Exception e) {
e.printStackTrace();
}
});
}
block = PrevBlock();
}while (block!=null && block.hasPrevBlock());
/
/创建UTXO数组返回
Transaction[] t = new Transaction[txs.size()];渗透系数
return txs.values().toArray(t);
}
看这⾥的代码:
if (Instance().verify(address,tip.unLockScript) && ainsKey(tip.preTxId))
...
⾸先验证TxInput的解锁脚本是否对我们钱包的地址进⾏签名得到的,即验证这⼀笔输⼊是否是⾃⼰消费的。
如果是⾃⼰消费的然后⽐对UTXO的Id,如果相同则说明这笔UTXO已经被消费掉了。那么需要移除它。
在钱包中添加⼀个新的⽅法,⽤于验证解锁脚本是否可以解锁交易输出。我们简单采⽤哈希值匹配的
⽅式模拟验证。#Wallet.java
public boolean verify(String data,String sign) throws DecoderException, Exception {
LOGGER.info("验证签名: "+data);
String[] str = sign.split("%%%");
// 原⽂    encry(hash(原⽂))
if(str.length!=2){
return false;
}
String hash2 = deHexString(this.decrypt(str[1]));
String hash3 = SHA256(data);
if(hash3.equals(hash2)){
LOGGER.info("签名验证成功!!");
return true;
}
LOGGER.info("签名验证失败!!");
return false;
}
更新区块信息
加⼊了UTXO的概念,那我们需要更新区块以及区块链的属性信息了。
#Block.java
@Getter
@Setter
public class Block implements Serializable{
...
//当前区块中的交易
public Transaction transaction;
组合营销...
//添加⼀个新的构造⽅法
public Block(int blkNum,Transaction transaction,String prevBlockHash){
this.blkNum = blkNum;
this.prevBlockHash = prevBlockHash;
this.timeStamp = TimeStamp();
}
...
}
然后是区块链,也要更新⼀个⽅法:
#Blockchain.java
public final class Blockchain {
...
public Block addBlock(Transaction tx) throws IOException {
int num = BlkNum();
Block block = new Block(num + 1, tx, this.block.curBlockHash);
// 每次将区块添加进区块链之前需要计算难度值
block.setNonce(Pow.calc(block));
// 计算区块哈希值
String hash = BlkNum() + Data() + PrevBlockHash()
+ PrevBlockHash() + Nonce());
block.setCurBlockHash(hash);
// 序列化
Storage.Serialize(block);
this.block = block;
LOGGER.info("当前区块信息为:"+String());
return this.block;
}
.
..
}
之前区块中将字符串保存为区块信息,我们更新为⼀笔交易。需要创建⼀笔交易才可以创建区块。
Coinbase
关于UTXO,我们之前讲到每⼀笔输出都会对应着⼀个输⼊,那么第⼀笔被输出的coin是哪⾥来的呢,
在⽐特币中,每产出⼀个区块将会奖励⼀定数量的BItcoin,称为Coinbase。同理,我们这⾥也实现它。
因此第⼀笔被输出的coin来⾃于coinbase。我们将coinbase固定为50,正如之前设定的属性:
#Transaction.java
private transient static final int COINBASE = 50;
所以我们还需要⼀个⽣成coinbase的交易的构造⽅法:
#Blockchain.java
public static Transaction newCoinBase() throws NoSuchAlgorithmException, Exception {
Transaction t = new Transaction();
t.tips = new ArrayList<>();
LOGGER.info("创建"+t.toString());
return t;
}
可以看到,交易输出的地址我们设置为钱包的地址。
⽐较简单,接下来修改⼀下创世区块的⽣成⽅法,将coinbase的交易添加进去。
#Blockchain.java
private Block CrtGenesisBlock() throws NoSuchAlgorithmException, Exception {
// Block block = new Block(1,"Genesis Block","00000000000000000");
Block block = new Block(1, wCoinBase(), "00000000000000000");
...
}
测试
⼀切都完成了,测试⼀下:
#Test.java
public class Test {
public static void main(String[] args) throws NoSuchAlgorithmException, Exception {
"address", "address1", 20));
}
}
分析⼀下测试⽤例:
>>>>>####
#Blockchain.java CrtGenesisBlock()
Block block = new Block(1, wCoinBase(), "00000000000000000");
#newCoinBase()
⾸先获取区块链实例,因此创建了创始区块,看上⾯的代码我们可以知道创建了⼀笔coinbase交易,50个coin被锁定在我们钱包的地址。
然后是第⼆个区块,我们创建了⼀个UTXO,从钱包的地址转移30个coin到地址address。
"address", "address1", 20));
最后是第三个区块,从地址address转移20个coin到地址address1.
测试⼀下:
...
[INFO ] 2020-05-18 14:10:19,501 method:org.wCoinBase(Transaction.java:42)
创建{"tips":[],"tops":{"R1363548f8946529dd73922b26547b6110f9159a30cc9b0474435c1c0db37842cd3c4100082182227e356c811830d6b5b56d862880b85f20355cfbc387641653a":{"lockScript":"R1363548f8946 ...
[INFO ] 2020-05-18 14:10:19,757 method:org.Blockchain.findAllUnspendableUTXO(Blockchain.java:117)
查所有未消费的
[INFO ] 2020-05-18 14:10:19,804 method:org.xd.chain.wallet.Wallet.sign(Wallet.java:86)
使⽤私钥对数据签名: R1363548f8946529dd73922b26547b6110f9159a30cc9b0474435c1c0db37842cd3c4100082182227e356c811830d6b5b56d862880b85f20355cfbc387641653a
[INFO ] 2020-05-18 14:10:20,382 method:org.wUTXO(Transaction.java:100)
创建UTXO: {"tips":[{"unLockScript":"52313336333534386638393436353239646437333932326232363534376236313130663931353961333063633962303437343433356331633064623337383432636433633431303030383 ...
[INFO ] 2020-05-18 14:10:20,468 method:org.Blockchain.addBlock(Blockchain.java:86)
当前区块信息为:{"blkNum":2,"curBlockHash":"00005f0690489e8763bd3db0ce7112592cdb118507945c65d07bfe27e0ad3031","nonce":4350,"prevBlockHash":"000011de81afdac44e08e81b9be434cbcb625808a9d0f8008275ab [INFO ] 2020-05-18 14:10:20,575 method:org.Blockchain.findAllUnspendableUTXO(Blockchain.java:117)
查所有未消费的
.
..
[INFO ] 2020-05-18 14:10:20,725 method:org.xd.chain.wallet.Wallet.verify(Wallet.java:124)
验证签名: address
...
[INFO ] 2020-05-18 14:10:20,731 method:org.xd.chain.wallet.Wallet.sign(Wallet.java:86)
使⽤私钥对数据签名: address
[INFO ] 2020-05-18 14:10:20,734 method:org.wUTXO(Transaction.java:100)
创建UTXO: {"tips":[{"unLockScript":"61646472657373%%%8ad16095a9f1947e323eb5ef3601a0cc2ad552ad3f7331406123577a1cc0c68dc614f3262505f079f7c3acfc1d681fdb432f7ba0f4ac3d69cb46dead5446b2cd","values":3 ...
[INFO ] 2020-05-18 14:10:20,990 method:org.Blockchain.addBlock(Blockchain.java:86)
当前区块信息为:{"blkNum":3,"curBlockHash":"00006e8918bba9831374f578caa3d80fa936997598072145bc0654cbca2d084e","nonce":74607,"prevBlockHash":"00005f0690489e8763bd3db0ce7112592cdb118507945c65d07b 省略掉其他⽇志信息,看起来测试是没有问题的,共⽣成了3个区块。创世区块中有⼀笔Coinbase交易。区块2中成功转移30coin到地址address,返还20coin到原地址。区块3中成功从地址address转移
20coin到地址address1,返还10coin到地址address。
还有部分未完善部分,⽐如coinbase只在创世区块中⽣成了。每个区块中只含有⼀笔交易等等,后期慢慢完善。
Github仓库地址在这⾥,随时保持更新中.....
Github地址:poser中国

本文发布于:2023-07-05 13:34:57,感谢您对本站的认可!

本文链接:https://patent.en369.cn/xueshu/173539.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:区块   交易   输出   地址   需要   创建
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 369专利查询检索平台 豫ICP备2021025688号-20 网站地图