比特币原理及其代码实践4-交易(1)


交易(transaction)是比特币的核心所在。

在区块链中,交易一旦被创建,就没有任何人能够再去修改或是删除它。

比特币采用的是 UTXO 模型,并非账户模型,并不直接存在“余额”这个概念

余额需要通过遍历整个交易历史得来

比特币交易

Bitcoin - BTC Price, Live Chart, and News | Blockchain.com

我们可以在上面网站查看比特币的区块中的交易

那么大家可以想一想,在每一笔交易中,txin都会指向之前的某一比交易txout

在之前的交易中,txout的转账又来自txin

如此循环下去,是不是这比钱会有一个源头

这个源头就是coinbase挖矿奖励

注意:

  1. 有一些输出并没有被关联到某个输入上(找零)
  2. 一笔交易的输入可以引用之前多笔交易的输出(钱不够的话可以汇总多笔交易)
  3. 一个输入必须引用一个输出(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是交易的hashvalue是第几个output)
//3.可用的output(key是交易的hashvalue是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
}

Transaction - Bitcoin Wiki

区块链刻字

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