0%

StudyRecord-GoBlockchain_Tutorial_Part4_Read_Store_and_Manage

Pre:

Go Blockchain Tutorial Part 4: Read,Store and Manage 学习记录

交易信息池:

我们知道区块链中每个区块可以存放复数个交易信息,而不是每生成一个交易信息就创建一个区块加入区块链。

对于一个区块链节点而言,它会将自己生成的交易信息与从其它节点收集的交易信息存储于一个交易信息池Transaction Pool中。
当存储的交易信息数量达到一阈值或者等待时间超过一阈值就会将交易池中的交易信息打包为候选区块参与PoW共识机制,这一过程称为挖矿mine,是节点争取将自己的候选区块加入到区块链中的过程。

系统调试:

现在我们先创建一个区块链,这个区块链的创建者是你自己。

1
2
3
MacBook-Pro:my-blockchain chanjerry$ ./main createblockchain -address Jerry
Genesis Created
Finished creating blockchain, and the owner is: Jerry

然后我们可以使用balance命令查询你自己的余额,并使用blockchaininfo命令打印区块链。

1
2
3
4
5
6
7
8
9
10
MacBook-Pro:my-blockchain chanjerry$ ./main balance -address Jerry
Address:Jerry, Balance:1000
MacBook-Pro:my-blockchain chanjerry$ ./main blockchaininfo
--------------------------------------------------------------------------------------------------------------
Timestamp:1659666065
Previous hash:4a657272792069732066617421
Transactions:[0xc0055cc050]
hash:fe809dd8173e5309e260a9196c9ebbffae383f7f128dd05b405e725a126911d0
Pow: true
--------------------------------------------------------------------------------------------------------------

测试一下send命令

1
2
3
4
5
6
MacBook-Pro:my-blockchain chanjerry$ ./main send -from Jerry -to Tom -amount 100
Success!
MacBook-Pro:my-blockchain chanjerry$ ./main balance -address Jerry
Address:Jerry, Balance:1000
MacBook-Pro:my-blockchain chanjerry$ ./main balance -address Tom
Address:Tom, Balance:0

由Jerry转账100给Tom这个交易信息创建成功,但是我们查询Jerry的余额发现没有变动,这是因为成功创建的交易信息还保存在交易信息池中,没有被添加进区块链里。我们需要使用mine命令。

1
2
3
4
5
6
MacBook-Pro:my-blockchain chanjerry$ ./main mine
Finish Mining
MacBook-Pro:my-blockchain chanjerry$ ./main balance -address Jerry
Address:Jerry, Balance:100
MacBook-Pro:my-blockchain chanjerry$ ./main balance -address Tom
Address:Tom, Balance:100

??? 余额不对,待会Debug

此时打印区块链会得到两个区块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
MacBook-Pro:my-blockchain chanjerry$ ./main blockchaininfo
--------------------------------------------------------------------------------------------------------------
Timestamp:1659666447
Previous hash:fe809dd8173e5309e260a9196c9ebbffae383f7f128dd05b405e725a126911d0
Transactions:[0xc00007a1e0]
hash:acc3f1de20d319ebf008eb975b6ec427338e2e036f9c0083e2b9c602192a61d9
Pow: true
--------------------------------------------------------------------------------------------------------------

--------------------------------------------------------------------------------------------------------------
Timestamp:1659666065
Previous hash:4a657272792069732066617421
Transactions:[0xc00007a230]
hash:fe809dd8173e5309e260a9196c9ebbffae383f7f128dd05b405e725a126911d0
Pow: true
--------------------------------------------------------------------------------------------------------------

为了更加方便测试,我们可以创建一个脚本文件

mac脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#!/bin/bash
echo "Hello World !"
# 删除tmp目录
# avoid删除错东西,先手动删除就好
./main createblockchain -address Jerry
echo "====================================="
echo "==blockchaininfo==="
./main blockchaininfo
./main balance -address Jerry
echo "====================================="
echo "==send -from Jerry -to Tom -amount 100=="
./main send -from Jerry -to Tom -amount 100
echo "====================================="
echo "==mine==="
./main mine
echo "====================================="
echo "==balance -address Tom==="
./main balance -address Tom
echo "====================================="
echo "==blockchaininfo==="
./main blockchaininfo
echo "====================================="
echo "==balance -address Jerry==="
./main balance -address Jerry
echo "====================================="
./main balance -address Tom
echo "====================================="
./main send -from Jerry -to Gia -amount 100
echo "====================================="
./main send -from Tom -to Gia -amount 30
echo "====================================="
./main mine
echo "====================================="
#./main blockchaininfo
./main balance -address Jerry
echo "====================================="
./main balance -address Tom
echo "====================================="
./main balance -address Gia
echo "====================================="

理解UTXO:

什么是UTXO?

在比特币社区内,有一种这样的说法:其实并没有比特币,有的只是UTXO。还有一种说法是:如果理解了UTXO,你就理解了比特币。

全称为 Unspent Transaction output
UTXO is all those outputs that are yet to be unlocked by an input.
UTXO 是所有尚未被输入解锁的输出.
UTXO 代表未被使用的交易输出:

  • 它是某人在执行交易后剩余的数字货币数量。

  • 交易完成后,未使用的输出将作为输入存回数据库,稍后可用于另一笔交易。

UTXO是如何创建的?

  • UTXO 是通过使用现有的 UTXO 创建的.输入消耗一个现有的UTXO,而输出创建一个新的 UTXO

  • 每笔比特币交易都由输入出组成

例如直接看一笔bitcoin的真实交易记录,可以看到交易的输入输出部分:
20220810110030

  • 交易Tx形成了一条链,最近交易的输入对应以前交易的输出

交易链中一笔交易输出就是另一笔交易的输入
交易链中一笔交易输出就是另一笔交易的输入

UTXO模型:

它基于由block组成的一个个交易。
UTXO 模型是许多加密货币通用的设计,最引人注目的是比特币。

  • 使用 UTXO 模型的加密货币不使用账户余额。相反,UTXO 就像实物现金一样在用户之间转移。

  • UTXO 核心设计思路是:它记录交易事件,而不记录最终状态

比特币交易:

简单来说,一笔交易就是告知全网:比特币的持有者已授权把它转帐给其他人。而新持有者可以通过产生另一笔交易,转账给另外的人,依此类推形成一条所有权的链。

交易输入输出:

交易就像复式记账法账簿中的行。
简单来说,每一笔交易包含一个或多个输入,就像比特币账户的借方.
这笔交易的另一面,有一个或多个“输出”,就像比特币账户的贷方。
这些输入和输出的总额(借方和贷方)不需要相等。

相反,当输出加起来略少于输入数量时,两者的差额就代表了一笔隐含的“矿工费”,这笔矿工费由成功打包交易到区块链账簿的矿工收取。

如下图描述的是一笔比特币交易作为账簿中的一个条目。

交易也包含了每一笔被转账的比特币(输入)的所有权证明,它以所有者的数字签名形式存在,并可以被任何人独立验证。
在比特币术语中,“消费”指的是签名一笔交易:将以前交易的比特币转账给比特币地址所标识的新所有者。

20220810111411
交易就像复式记账

交易链:

Alice支付Bob咖啡时使用一笔之前的交易作为输入。
在以前的章节中,Alice从她朋友Joe那里用现金换了点比特币.那笔交易创建了被Alice的密钥锁定的比特币。
在她支付Bob咖啡店的新交易中使用了之前的交易作为输入,输出是支付咖啡的金额和多余部分的找零.

交易形成了一条链,最近交易的输入对应以前交易的输出.

Alice用密钥签名解锁了之前交易的输出,从而向比特币网络证明她拥有这笔钱。
她将买咖啡的这笔支付到Bob的地址上,明确指明要求是Bob签名才能消费这笔钱,否则就“阻止”那笔输出。
这就实现了在Alice和Bob之间价值转移。

下图展示了从Joe到Alice再到Bob的交易链:
20220810111452
交易链中一笔交易输出就是另一笔交易的输入

找零:

许多比特币交易都会包括新所有者的地址(买方地址)和当前所有者的地址(称为找零地址)的输出。
这是因为交易输入,就像纸币那样能够不能被再分割。

如果您在商店购买了5美元的商品,但是使用20美元的美金来支付商品,会收到15美元的找零。相同的概念适用于比特币交易输入。
如果您购买了一个价格为5比特币的商品,但是你的输入中只有20比特币这一项,那么您需要产生两个输出:

  • 一个5个比特币的输出发送给店主

  • 另一个15比特币的输出返回自己作为找零(减去任何适用的交易费用)

重要的是,出于隐私的原因,找零地址不必与输入时提供的地址相同,通常是所有者钱包中的新地址。

不同的钱包可以在合并所有输入,用来匹配自己的付款金额时使用不同的策略。
它们可能会聚合许多小输入,或者使用等于或大于所需付款的输入。
除非钱包中的输入,刚好汇总起来与所需付款(加上交易费用)完全相等,否则钱包一定会产生一些找零。

这与人们如何处理现金非常相似.如果你总是用钱包中的最大面额支付时,那么钱包中的零钱就会越来越多.如果你只使用零钱,整钱就会越来越多.
人们总是无意识地在这两个极端之间找到平衡,而比特币钱包开发商也力图实现这种平衡。

总的来讲,交易是将钱从交易输入移至输出。

输入通常是前一笔交易的输出的引用,表示价值从何而来。
交易输出将约定金额发送到新的所有者的比特币地址,并将找零输出返回原来所有者的地址。
一笔交易的输出可以被当做另一笔新交易的输入,这样随着钱从一个地址被移动到另一个地址,就形成了一条所有权链.

常见的交易形式:

简单支付/找零:

最常见的交易形式是从一个地址到另一个地址的简单支付,还包含给支付者的“找零”。
这类交易有一个输入和两个输出,如图所示:

20220810111535

归集资金:

另一种常见的交易形式是归集多个输入到一个输出的模式。
这相当于现实生活中将很多硬币和纸币零钱兑换为一个大额面钞。像这样的交易由钱包应用产生,来整理在支付过程收到的许多小额的找零。

20220810111716

分散资金:

最后,另一种在比特币账簿中常见的交易形式是将一个输入分配给多个输出,即多个接收者的交易。
这类交易有时被商业机构用作分配资金,例如给多个雇员发工资的情形。

20220810111756

开始Debug:

前面在系统调试的时候,出现Bug: jerry一开始余额为1000,转账给Tom 100之后,Jerry自己的余额变成了100。
通过Debug,搞清楚以下问题:

  • 如何去构造一个交易?

  • 交易数据的内容具体有什么?

  • 查询余额的流程是什么?

交易数据的内容:

交易的数据结构如下:

1
2
3
4
5
type Transaction struct {
ID []byte
Inputs []TxInput // 标记支持我们本次转账的前置的交易信息的TxOutput
Outputs []TxOutput // 记录我们本次转账的amount和Reciever
}

重点在于两个数组: InputsOutputs

1
2
3
4
5
6
7
8
9
10
type TxOutput struct {
Value int // 转出的资产值
ToAddress []byte // 资产的接收者的地址
}

type TxInput struct {
TxID []byte // 指明支持本次交易的前置交易信息
OutIdx int // 具体指明是前置交易信息中的第几个Output
FromAddress []byte // 资产转出者的地址
}

创始区块开始,相当于0地址直接转给Jerry,会创建第一个Tx,我们看看第一个tx里面的内容:

1
2
3
4
5
6
7
8
9
10
11
tx.ID:This is the Base Transaction!                                  
=======遍历tx.Inputs数组===========================
指明支持本次交易的前置交易信息 input.TxID:
资产转出者的地址 input.FromAddress:
具体指明是前置交易信息中的第几个Output input.OutIdx:-1
=======遍历tx.Outputs数组==========
转出的资产值 output.Value:1000
资产的接收者的地址 output.ToAddress:Jerry
=================
hash:de1a4d1e5cfd9bc6442cacc565309e42fc65767fd86d355ae23fd51139511448
Pow: true

第二个区块,Jerry转账100给Tom,tx里面的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Previous hash:de1a4d1e5cfd9bc6442cacc565309e42fc65767fd86d355ae23fd51139511448     
Transactions:[0xc00007a140]
=======遍历tx.Inputs数组==========
指明支持本次交易的前置交易信息 input.TxID:This is the Base Transaction!
资产转出者的地址 input.FromAddress:Jerry
具体指明是前置交易信息中的第几个Output input.OutIdx:0
=======遍历tx.Outputs数组==========
转出的资产值 output.Value:100
资产的接收者的地址 output.ToAddress:Tom
=================
转出的资产值 output.Value:900
资产的接收者的地址 output.ToAddress:Jerry
=================
hash:5b4f1b2a52f1ae62368c5472e932ae85e3b6bedb451ace8fa6436c858d8e3359
Pow: true

第三个区块,Tom转账30给Gia,tx里面的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Timestamp:1660028211                  
Previous hash:5b4f1b2a52f1ae62368c5472e932ae85e3b6bedb451ace8fa6436c858d8e3359
Transactions:[0xc00007a190]
tx.ID:
=======遍历tx.Inputs数组==========
指明支持本次交易的前置交易信息 input.TxID:
资产转出者的地址 input.FromAddress:Tom
具体指明是前置交易信息中的第几个Output input.OutIdx:0
=======遍历tx.Outputs数组==========
转出的资产值 output.Value:30
资产的接收者的地址 output.ToAddress:Gia
=================
转出的资产值 output.Value:70
资产的接收者的地址 output.ToAddress:Tom
=================
hash:dc72eea25324af0be865d25b8c2289ec7da1a4600f69776a1ac608160d773c6a
Pow: true

测试所做的操作:

  • Block1: 创世区块 分配1000给Jerry

  • Block2: Jerry转账100给Tom

  • Block3: Tom转账30给Gia

  • Block4: Jerry转账50给Gia

  • Block5: Gia转账5给Jane

20220810103306
例如Jerry转账50给Gia时,创建的交易:

  1. Inputs: 指定一个之前的Output作为Inputs:

    • 指向之前的交易Tx2
    • 找到Jerry的未花费交易输出为900(同样是Jerry的余额)
  2. Outputs: 产生两个输出

    • Output1: 50块的输出发送给Gia
    • Output2: 另一个850的输出返回给自己作为找零

查询余额原理:

查询某地址余额:
从当前区块直到创世区块,开始扫描每一笔交易,如果:

  • 遇到某笔交易的某个Output是钱包管理的地址之一,则钱包余额增加;

  • 遇到某笔交易的某个Input是钱包管理的地址之一,则钱包余额减少。

钱包的当前余额总是钱包地址关联的所有UTXO金额之和。
20220809140957
上图中所有绿色的交易输出才是 UTXO,红色的交易输出已经被当前『账户』使用了,所以在计算当前账户的余额时只会考虑绿色的交易输出,也就是 UTXO。

Refs: