交易(transaction)是比特币的核心所在。
在区块链中,交易一旦被创建,就没有任何人能够再去修改或是删除它。
比特币采用的是 UTXO 模型
,并非账户模型,并不直接存在“余额”
这个概念
余额需要通过遍历整个交易历史得来
比特币交易
Bitcoin - BTC Price, Live Chart, and News | Blockchain.com
我们可以在上面网站查看比特币的区块中的交易
那么大家可以想一想,在每一笔交易中,txin都会指向之前的某一比交易的txout
在之前的交易中,txout的转账又来自txin
如此循环下去,是不是这比钱会有一个源头
这个源头就是coinbase挖矿奖励
注意:
- 有一些输出并没有被关联到某个输入上(找零)
- 一笔交易的输入可以引用之前多笔交易的输出(钱不够的话可以汇总多笔交易)
- 一个输入必须引用一个输出(txin会有txid,txid就是之前交易的hash值)(coinbase 交易除外)
这就是UTXO模型
在比特币中,其实并不存钱的概念。所有的都是交易。交易仅仅是通过一个脚本(script)来锁定(lock)一些值(value),而这些值只可以被锁定它们的人解锁(unlock)(解锁意味着可以你可以用它转账)。
交易
//交易
type Transaction struct {
ID []byte //交易hash值 = hash(vin + vout)
Vin []TXInput //输入
Vout []TXOutput //输出
}
输出
type TXOutput struct {
Value int //多少值,就是多少比特币
ScriptPubKey string //解锁的脚本,我们目前暂时就当作收货人地址就行
}
关于输出,非常重要的一点是:它们是不可再分的(indivisible)。也就是说,你无法仅引用它的其中某一部分。要么不用,如果要用,必须一次性用完。如果用不完,则产生一个新的output找零。
输入
type TXInput struct {
Txid []byte //之前的交易hash值
Vout int //是之前交易的第几个output
ScriptSig string //解锁脚本,目前暂时当作发货人的发货地址
}
所以目前 ScriptSig
将仅仅存储一个用户自定义的任意钱包地址。我们会在下一篇文章中实现公钥(public key)和签名(signature)。
钱的来源 coinbase交易
//挖矿奖励
const subsidy = 10
//新建一个挖矿奖励交易
func NewCoinbaseTX(to, data string) *Transaction {
if data == "" {
data = fmt.Sprintf("Reward to '%s'", to)
}
//Coinbas的input的hash为空,不需要
txin := TXInput{[]byte{}, -1, data}
//subsidy奖励
txout := TXOutput{subsidy, to}
//组成一个交易
tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
tx.ID = tx.Hash()
return &tx
}
//区块链第一笔钱的诞生
func CreateBlockchain() *Blockchain {
...
//创世区块
firstAddress := "zhangsan"
//产生一笔CoinbaseTX
cbtx := NewCoinbaseTX(firstAddress, "")
//放入创世区块
block := NewGenesisBlock(cbtx)
//保存区块链
...
return bc
}
找到自己的余额和未使用的output
关键代码
//找到没有花费的output集合,并返回全部余额
//返回:
//1.全部余额
//2.可用的outputIndex(key是交易的hash,value是第几个output)
//3.可用的output(key是交易的hash,value是output)
func (bc *Blockchain) FindSpendableOutputs(address string) (int, map[string][]int, map[string][]TXOutput) {
...
//未使用output
unspentOutputIndexList := make(map[string][]int)
unspentOutputMap := make(map[string][]TXOutput)
//已经使用的output
spentTXOs := make(map[string][]int)
//金额
accumulated := 0
for {
//循环所有的区块
block := bci.Next()
//伪代码
遍历区块所有的交易
遍历交易所有的output
//如果这个output没有被其他的交易的input引用,则保存
save outputIndex,output对象(key为交易hash)
accumulated += output.value
遍历交易的所有input
//缓存input,作为output是否被使用的标记
cache spentTXOs
if block 没有prevHash
break
}
return accumulated, unspentOutputIndexList, unspentOutputMap
}
创建交易
//新建交易,发送币
func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
var inputs []TXInput
var outputs []TXOutput
acc, allValidOutputIndex, unspentOutputMap := bc.FindSpendableOutputs(from)
if acc < amount {
log.Panic("ERROR: Not enough funds")
}
//找到足够的output
validOutputs := make(map[string][]int)
accumulated := 0
循环自己的所有output,找到足够量的output存到validOutputs
//构建input,把找到的input的打个包inputs
for txid, outs := range validOutputs {
for _, out := range outs {
//这里可以看到,我们的output成为了新交易的input
input := TXInput{txID, out, from}
inputs = append(inputs, input)
}
}
//构建output
outputs = append(outputs, TXOutput{amount, to})
if acc > amount {
//如果需要找零,新建一个output
outputs = append(outputs, TXOutput{acc - amount, from})
}
tx := Transaction{nil, inputs, outputs}
tx.ID = tx.Hash()
return &tx
}
发送交易,产生新的区块
//区块更新
type Block struct {
Timestamp int64 //时间戳
Transactions []Transaction //数据
TransactionHash []byte //交易的hash值
PrevBlockHash []byte //前一个区块的hash
Hash []byte //自己的hash
Nonce int
Height int
}
//给某个地址发送金额
func (bc *Blockchain) Send(from, to string, amount int) {
tx := NewUTXOTransaction(from, to, amount, bc)
//挖矿奖励
cbtx := NewCoinbaseTX(from, "")
transactions = append(transactions, cbtx)
transactions = append(transactions, tx)
bc.AddBlock(transactions)
}
//添加区块
func (bc *Blockchain) AddBlock(transactions []Transaction) {
//最新的区块
prevBlock := bc.DB[bc.L]
//新建区块,把交易放进来并运行pow
//pow -> hash(PrevBlockHash + transactionHash + Timestamp + targetBits + nonce)
newBlock := NewBlock(transactions, prevBlock.Hash, prevBlock.Height+1)
//更新区块链数据
hash := newBlock.GetHashString()
bc.DB[hash] = *newBlock
bc.L = hash
//保存到文件
bc.SaveBlockchain()
}
账户余额
//余额
func (bc *Blockchain) GetBalance(address string) int {
balance, _, _ := bc.FindSpendableOutputs(address)
fmt.Printf("balance of %s is %d\n", address, balance)
return balance
}
区块链刻字
CoinbaseTX中的input的sigscript其实是可以刻字的!!!
比特币的第一份交易的Transaction: 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b | Blockchain.com
查看第一个区块中包含的隐藏信息
func TestShow1(t *testing.T) {
//中本聪的留言
s := "04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73"
b, _ := hex.DecodeString(s)
fmt.Printf("result: %s\n", string(b))
}
程序员的浪漫
Transaction: c633a5c53b03ce188c6ba376f7e9d1428838284fdeab6d600d6dfcd14a860140 | Blockchain.com