溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

從0到1簡(jiǎn)易區(qū)塊鏈開(kāi)發(fā)手冊(cè)V0.4-實(shí)現(xiàn)轉(zhuǎn)賬交易的思路分析

發(fā)布時(shí)間:2020-07-20 15:25:43 來(lái)源:網(wǎng)絡(luò) 閱讀:12357 作者:暗黑魔君 欄目:編程語(yǔ)言

六.轉(zhuǎn)賬交易

創(chuàng)世區(qū)塊創(chuàng)建完畢之后,按照我們的正常思路,是繼續(xù)創(chuàng)建新的區(qū)塊,并加入至區(qū)塊鏈中,沒(méi)錯(cuò),這確實(shí)是學(xué)習(xí)路線,但是我們首先來(lái)了解一個(gè)區(qū)塊是如何生成的,轉(zhuǎn)賬交易 ===>打包交易 ===>工作量證明 ===>生成區(qū)塊

在上文,我們提到了錢(qián)包地址這個(gè)概念,我們一般可以簡(jiǎn)單將錢(qián)包地址理解為一個(gè)銀行賬戶,那么交易也就可以理解為是地址與地址之間的轉(zhuǎn)賬過(guò)程。

因?yàn)檫@部分內(nèi)容非常重要,設(shè)置可以說(shuō)交易就是比特幣原理的核心,所以,為了保證大家對(duì)概念有充分的了解,本章節(jié)的理論描述部分此處摘錄liuchengxu中關(guān)于對(duì)交易的翻譯。

1.概念

交易(transaction)是比特幣的核心所在,而區(qū)塊鏈唯一的目的,也正是為了能夠安全可靠地存儲(chǔ)交易。在區(qū)塊鏈中,交易一旦被創(chuàng)建,就沒(méi)有任何人能夠再去修改或是刪除它。今天,我們將會(huì)開(kāi)始實(shí)現(xiàn)交易。不過(guò),由于交易是很大的話題,我會(huì)把它分為兩部分來(lái)講:在今天這個(gè)部分,我們會(huì)實(shí)現(xiàn)交易的基本框架。在第二部分,我們會(huì)繼續(xù)討論它的一些細(xì)節(jié)。

由于比特幣采用的是 UTXO 模型,并非賬戶模型,并不直接存在“余額”這個(gè)概念,余額需要通過(guò)遍歷整個(gè)交易歷史得來(lái)。

關(guān)于UTXO模型,這在比特幣中也是非常重要的概念模型,務(wù)必熟練掌握。

點(diǎn)擊此處查看相關(guān)的交易信息

從0到1簡(jiǎn)易區(qū)塊鏈開(kāi)發(fā)手冊(cè)V0.4-實(shí)現(xiàn)轉(zhuǎn)賬交易的思路分析

圖 交易記錄
從0到1簡(jiǎn)易區(qū)塊鏈開(kāi)發(fā)手冊(cè)V0.4-實(shí)現(xiàn)轉(zhuǎn)賬交易的思路分析

圖 輸入腳本

從0到1簡(jiǎn)易區(qū)塊鏈開(kāi)發(fā)手冊(cè)V0.4-實(shí)現(xiàn)轉(zhuǎn)賬交易的思路分析

關(guān)于轉(zhuǎn)賬交易涉及到的內(nèi)容非常多,由于時(shí)間原因,目前可能無(wú)法做到非常全面的講解,姑且將自己梳理好能夠解釋清楚的地方分享出來(lái),由于比特幣世界中的交易規(guī)則會(huì)更加復(fù)雜化,所以,希望大家能夠通過(guò)本章節(jié)的閱讀,在一定程度上對(duì)某些概念有一些初步或者稍微深刻的理解,那么本章節(jié)的目的也就達(dá)到了,更深的分析筆者將會(huì)在后期的工作中根據(jù)實(shí)際的工作場(chǎng)景進(jìn)行優(yōu)化并做相關(guān)記錄。

2.結(jié)構(gòu)體定義

其實(shí)再轉(zhuǎn)賬交易這個(gè)功能里面,涉及了本文所有的結(jié)構(gòu)體對(duì)象,由于區(qū)塊與區(qū)塊鏈對(duì)象等在上文已經(jīng)有所提及,這里先列出跟轉(zhuǎn)賬交易關(guān)系最為密切的一些結(jié)構(gòu)體。
從0到1簡(jiǎn)易區(qū)塊鏈開(kāi)發(fā)手冊(cè)V0.4-實(shí)現(xiàn)轉(zhuǎn)賬交易的思路分析

2.1 交易Transaction
type Transaction struct {
    //1.交易ID
    TxID []byte
    //2.輸入
    Vins []*TxInput
    //3.輸出
    Vouts []*TxOutput
}
  • TxID : 交易ID,一般通過(guò)對(duì)交易進(jìn)行哈希后得到
  • Vins: 交易輸入數(shù)組
  • Vouts:交易輸出數(shù)組
2.2 交易輸入TxInput
type TxInput struct {
    //1.交易ID:
    TxID []byte
    //2.下標(biāo)
    Vout int
    //3.數(shù)字簽名
    Signature []byte 
    //4.原始公鑰,錢(qián)包里的公鑰
    PublicKey []byte 

}
  • TxID: 交易ID,表示該TxInput引用的TxOutput所在的交易ID
  • Vout:下標(biāo),表示該TxInput引用的TxOutput在交易中的位置
  • Signature:數(shù)字簽名,用于對(duì)引用的TxOutput交易的解鎖
  • PublicKey: 錢(qián)包的公鑰,原始公鑰
2.3 交易輸出TxOutput
type TxOutput struct {
    //金額
    Value int64  //金額
    //鎖定腳本,也叫輸出腳本,公鑰,目前先理解為用戶名,鑰花費(fèi)這筆前,必須鑰先解鎖腳本
    //ScriptPubKey string
    PubKeyHash [] byte//公鑰哈希
}
  • Value : 金額,轉(zhuǎn)賬/找零金額

  • PubKeyHash:輸出腳本,此處為公鑰哈希,用于鎖定該筆交易輸出
2.4 未花費(fèi)交易輸出UTXO
type UTXO struct {
    //1.該output所在的交易id
    TxID []byte
    //2.該output 的下標(biāo)
    Index int
    //3.output
    Output *TxOutput
}

UTXO:Unspent Transaction output

  • TxID: 該TxOutput所在的交易id
  • Index:該TxOutput 的下標(biāo)
  • Output:TxOutput對(duì)象
2.5 未花費(fèi)交易輸出集合 UTXOSet
type UTXOSet struct {
    BlockChian *BlockChain
}
const utxosettable = "utxoset"

定義一個(gè)常量用于標(biāo)識(shí)存入數(shù)據(jù)庫(kù)中的Bucket表名

3.轉(zhuǎn)賬交易流程

單筆轉(zhuǎn)賬

$ ./mybtc send -from 源地址 -to 目標(biāo)地址 -amount 轉(zhuǎn)賬金額

多筆轉(zhuǎn)賬

$ ./mybtc send \
  -from '["源地址1","源地址2","源地址N"]' \
  -to   '["目標(biāo)地址1","目標(biāo)地址2","目標(biāo)地址3"]' \
  -amount '["轉(zhuǎn)賬金額1","轉(zhuǎn)賬金額2","轉(zhuǎn)賬金額3"]'

這部分內(nèi)容理解起來(lái)有些難度,所以我做了一張圖,希望能夠幫助大家能夠理順?biāo)悸罚@樣在后面的學(xué)習(xí)以及代碼理解上面會(huì)稍微容易一些。

從0到1簡(jiǎn)易區(qū)塊鏈開(kāi)發(fā)手冊(cè)V0.4-實(shí)現(xiàn)轉(zhuǎn)賬交易的思路分析

本圖介紹了從創(chuàng)世區(qū)塊后的三次轉(zhuǎn)賬過(guò)程,分別產(chǎn)生了三個(gè)區(qū)塊,為了讓讀者有更直觀的了解,我又將該圖做成了動(dòng)態(tài)圖的方式供大家參考,通過(guò)該圖,希望大家能夠大致對(duì)轉(zhuǎn)賬交易有個(gè)印象。

動(dòng)態(tài)圖演示了新區(qū)塊中的輸入交易引用的是哪個(gè)區(qū)塊中的交易輸出,從而實(shí)現(xiàn)了區(qū)塊鏈每次轉(zhuǎn)賬的金額都有據(jù)可依,也從另外一個(gè)角度展示了比特幣中UTXO的概念模型。

4.代碼分析

由于代碼量巨大,為了讓整個(gè)過(guò)程的理解更加流程,我改變前面幾篇文章的思路,從執(zhí)行命令的代碼塊進(jìn)行一步一步的代碼分析,希望能將自己的思路理順,從而可以更好得引導(dǎo)讀者朋友。

同樣,因?yàn)楹芏喔拍钚缘臇|西,我不準(zhǔn)備在文章里面啰嗦,如果感覺(jué)閱讀難度比較大,建議先仔細(xì)閱讀這篇文章

https://github.com/liuchengxu/blockchain-tutorial

然后再回頭來(lái)看我的這篇文章,會(huì)事半功倍

func (cli *CLI) Send(from, to, amount []string) {
    bc := GetBlockChainObject()
    if bc == nil {
        fmt.Println("沒(méi)有BlockChain,無(wú)法轉(zhuǎn)賬。。")
        os.Exit(1)
    }
    defer bc.DB.Close()

    bc.MineNewBlock(from, to, amount)
    utsoSet :=&UTXOSet{bc}
    utsoSet.Update()
}
4.1 獲取blockchain對(duì)象
func GetBlockChainObject() *BlockChain {
    /*
        1.數(shù)據(jù)庫(kù)存在,讀取數(shù)據(jù)庫(kù),返回blockchain即可
        2.數(shù)據(jù)庫(kù) 不存在,返回nil
     */

    if dbExists() {
        //fmt.Println("數(shù)據(jù)庫(kù)已經(jīng)存在。。。")
        //打開(kāi)數(shù)據(jù)庫(kù)
        db, err := bolt.Open(DBName, 0600, nil)
        if err != nil {
            log.Panic(err)
        }

        var blockchain *BlockChain

        err = db.View(func(tx *bolt.Tx) error {
            //打開(kāi)bucket,讀取l對(duì)應(yīng)的最新的hash
            b := tx.Bucket([]byte(BlockBucketName))
            if b != nil {
                //讀取最新hash
                hash := b.Get([]byte("l"))
                blockchain = &BlockChain{db, hash}
            }
            return nil
        })
        if err != nil {
            log.Panic(err)
        }
        return blockchain
    } else {
        fmt.Println("數(shù)據(jù)庫(kù)不存在,無(wú)法獲取BlockChain對(duì)象。。。")
        return nil
    }
}

判斷存儲(chǔ)區(qū)塊的DB文件是否存在,如果存在,直接從數(shù)據(jù)庫(kù)Bucket中讀取"l"對(duì)應(yīng)Hash值,將db對(duì)象與獲取到hash值賦值給需要返回的區(qū)塊鏈對(duì)象,如果DB文件不存在,說(shuō)明創(chuàng)世區(qū)塊并未創(chuàng)建,沒(méi)有區(qū)塊鏈對(duì)象,直接退出程序。

4.2 生成區(qū)塊

獲取到區(qū)塊鏈對(duì)象之后,我們調(diào)用MineNewBlock方法進(jìn)行區(qū)塊的創(chuàng)建

從0到1簡(jiǎn)易區(qū)塊鏈開(kāi)發(fā)手冊(cè)V0.4-實(shí)現(xiàn)轉(zhuǎn)賬交易的思路分析

func (bc *BlockChain) MineNewBlock(from, to, amount []string) {
    /*
    1.新建交易
    2.新建區(qū)塊:
        讀取數(shù)據(jù)庫(kù),獲取最后一塊block
    3.存入到數(shù)據(jù)庫(kù)中
     */

    //1.新建交易集合
    var txs [] *Transaction

    utxoSet := &UTXOSet{bc}

    for i := 0; i < len(from); i++ {
        //amount[0]-->int
        amountInt, _ := strconv.ParseInt(amount[i], 10, 64)
        tx := NewSimpleTransaction(from[i], to[i], amountInt, utxoSet, txs)
        txs = append(txs, tx)

    }
    /*
    分析:循環(huán)第一次:i=0
        txs[transaction1, ]
        循環(huán)第二次:i=1
        txs [transaction1, transaction2]
     */

    //交易的驗(yàn)證:
    for _, tx := range txs {
        if bc.VerifityTransaction(tx, txs) == false {
            log.Panic("數(shù)字簽名驗(yàn)證失敗。。。")
        }

    }

    /*
    獎(jiǎng)勵(lì):reward:
    創(chuàng)建一個(gè)CoinBase交易--->Tx
     */
    coinBaseTransaction := NewCoinBaseTransaction(from[0])
    txs = append(txs, coinBaseTransaction)

    //2.新建區(qū)塊
    newBlock := new(Block)
    err := bc.DB.View(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(BlockBucketName))
        if b != nil {
            //讀取數(shù)據(jù)庫(kù)
            blockBytes := b.Get(bc.Tip)
            lastBlock := DeserializeBlock(blockBytes)

            newBlock = NewBlock(txs, lastBlock.Hash, lastBlock.Height+1)

        }
        return nil
    })
    if err != nil {
        log.Panic(err)
    }

    //3.存入到數(shù)據(jù)庫(kù)中
    err = bc.DB.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(BlockBucketName))
        if b != nil {
            //將新block存入到數(shù)據(jù)庫(kù)中
            b.Put(newBlock.Hash, newBlock.Serialize())
            //更新l
            b.Put([]byte("l"), newBlock.Hash)
            //tip
            bc.Tip = newBlock.Hash
        }

        return nil
    })
    if err != nil {
        log.Panic(err)
    }
}
4.2.1 新建交易

在以上的代碼中,涉及到幾個(gè)比較重要的方法,其中一個(gè)NewSimpleTransaction用于創(chuàng)建交易并打包,這里對(duì)代碼進(jìn)行了簡(jiǎn)單梳理,由于內(nèi)容實(shí)在太多,在文章末尾我會(huì)將github的源代碼地址貼出,供大家查看。

(1) 創(chuàng)建該交易的TxInput

從0到1簡(jiǎn)易區(qū)塊鏈開(kāi)發(fā)手冊(cè)V0.4-實(shí)現(xiàn)轉(zhuǎn)賬交易的思路分析

(2) 創(chuàng)建該交易的TxOutput

從0到1簡(jiǎn)易區(qū)塊鏈開(kāi)發(fā)手冊(cè)V0.4-實(shí)現(xiàn)轉(zhuǎn)賬交易的思路分析

(3) 創(chuàng)建交易

從0到1簡(jiǎn)易區(qū)塊鏈開(kāi)發(fā)手冊(cè)V0.4-實(shí)現(xiàn)轉(zhuǎn)賬交易的思路分析

NewSimpleTransaction

func NewSimpleTransaction(from, to string, amount int64, utxoSet *UTXOSet, txs []*Transaction) *Transaction {
    //1.定義Input和Output的數(shù)組
    var txInputs []*TxInput
    var txOuputs [] *TxOutput

    //2.創(chuàng)建Input
    /*
    創(chuàng)世區(qū)塊中交易ID:c16d3ad93450cd532dcd7ef53d8f396e46b2e59aa853ad44c284314c7b9db1b4
     */

    //獲取本次轉(zhuǎn)賬要使用output
    //total, spentableUTXO := bc.FindSpentableUTXOs(from, amount, txs) //map[txID]-->[]int{index}
    total, spentableUTXO := utxoSet.FindSpentableUTXOs(from, amount, txs) //map[txID]-->[]int{index}

    //獲取錢(qián)包的集合:
    wallets := GetWallets()
    wallet := wallets.WalletMap[from]

    for txID, indexArray := range spentableUTXO {
        txIDBytes, _ := hex.DecodeString(txID)
        for _, index := range indexArray {
            txInput := &TxInput{txIDBytes, index, nil, wallet.PublickKey}
            txInputs = append(txInputs, txInput)
        }
    }

    //3.創(chuàng)建Output

    //轉(zhuǎn)賬
    txOutput := NewTxOutput(amount, to)
    txOuputs = append(txOuputs, txOutput)

    //找零
    //txOutput2 := &TxOutput{total - amount, from}
    txOutput2 := NewTxOutput(total-amount, from)
    txOuputs = append(txOuputs, txOutput2)

    //4.創(chuàng)建交易
    tx := &Transaction{[]byte{}, txInputs, txOuputs}

    //設(shè)置交易的ID
    tx.SetID()

    //設(shè)置簽名
    utxoSet.BlockChian.SignTransaction(tx,wallet.PrivateKey,txs)

    return tx

}

FindSpentableUTXOs

func (utxoSet *UTXOSet) FindSpentableUTXOs(from string, amount int64, txs []*Transaction) (int64, map[string][]int) {
    var total int64
    //用于存儲(chǔ)轉(zhuǎn)賬所使用utxo
    spentableUTXOMap := make(map[string][]int)
    //1.查詢未打包可以使用的utxo:txs
    unPackageSpentableUTXOs := utxoSet.FindUnpackeSpentableUTXO(from, txs)

    for _, utxo := range unPackageSpentableUTXOs {
        total += utxo.Output.Value
        txIDStr := hex.EncodeToString(utxo.TxID)
        spentableUTXOMap[txIDStr] = append(spentableUTXOMap[txIDStr], utxo.Index)
        if total >= amount {
            return total, spentableUTXOMap
        }
    }

    //2.查詢utxotable,查詢utxo
    //已經(jīng)存儲(chǔ)的但是未花費(fèi)的utxo
    err := utxoSet.BlockChian.DB.View(func(tx *bolt.Tx) error {
        //查詢utxotable中,未花費(fèi)的utxo
        b := tx.Bucket([]byte(utxosettable))
        if b != nil {
            //查詢
            c := b.Cursor()
        dbLoop:
            for k, v := c.First(); k != nil; k, v = c.Next() {
                txOutputs := DeserializeTxOutputs(v)
                for _, utxo := range txOutputs.UTXOs {
                    if utxo.Output.UnlockWithAddress(from) {
                        total += utxo.Output.Value
                        txIDStr := hex.EncodeToString(utxo.TxID)
                        spentableUTXOMap[txIDStr] = append(spentableUTXOMap[txIDStr], utxo.Index)
                        if total >= amount {
                            break dbLoop
                            //return nil
                        }
                    }
                }

            }

        }

        return nil

    })
    if err != nil {
        log.Panic(err)
    }

    return total, spentableUTXOMap
}

FindUnpackeSpentableUTXO

func (utxoSet *UTXOSet) FindUnpackeSpentableUTXO(from string, txs []*Transaction) []*UTXO {
    //存儲(chǔ)可以使用的未花費(fèi)utxo
    var unUTXOs []*UTXO

    //存儲(chǔ)已經(jīng)花費(fèi)的input
    spentedMap := make(map[string][]int)

    for i := len(txs) - 1; i >= 0; i-- {
        //func caculate(tx *Transaction, address string, spentTxOutputMap map[string][]int, unSpentUTXOs []*UTXO) []*UTXO {
        unUTXOs = caculate(txs[i], from, spentedMap, unUTXOs)
    }

    return unUTXOs
}

caculate

func caculate(tx *Transaction, address string, spentTxOutputMap map[string][]int, unSpentUTXOs []*UTXO) []*UTXO {
   //遍歷每個(gè)tx:txID,Vins,Vouts

   //遍歷所有的TxInput
   if !tx.IsCoinBaseTransaction() { //tx不是CoinBase交易,遍歷TxInput
      for _, txInput := range tx.Vins {
         //txInput-->TxInput
         full_payload := Base58Decode([]byte(address))

         pubKeyHash := full_payload[1 : len(full_payload)-addressCheckSumLen]

         if txInput.UnlockWithAddress(pubKeyHash) {
            //txInput的解鎖腳本(用戶名) 如果和要查詢的余額的用戶名相同,
            key := hex.EncodeToString(txInput.TxID)
            spentTxOutputMap[key] = append(spentTxOutputMap[key], txInput.Vout)
            /*
            map[key]-->value
            map[key] -->[]int
             */
         }
      }
   }

   //遍歷所有的TxOutput
outputs:
   for index, txOutput := range tx.Vouts { //index= 0,txoutput.鎖定腳本
      if txOutput.UnlockWithAddress(address) {
         if len(spentTxOutputMap) != 0 {
            var isSpentOutput bool //false
            //遍歷map
            for txID, indexArray := range spentTxOutputMap { //143d,[]int{1}
               //遍歷 記錄已經(jīng)花費(fèi)的下標(biāo)的數(shù)組
               for _, i := range indexArray {
                  if i == index && hex.EncodeToString(tx.TxID) == txID {
                     isSpentOutput = true //標(biāo)記當(dāng)前的txOutput是已經(jīng)花費(fèi)
                     continue outputs
                  }
               }
            }

            if !isSpentOutput {
               //unSpentTxOutput = append(unSpentTxOutput, txOutput)
               //根據(jù)未花費(fèi)的output,創(chuàng)建utxo對(duì)象--->數(shù)組
               utxo := &UTXO{tx.TxID, index, txOutput}
               unSpentUTXOs = append(unSpentUTXOs, utxo)
            }

         } else {
            //如果map長(zhǎng)度為0,證明還沒(méi)有花費(fèi)記錄,output無(wú)需判斷
            //unSpentTxOutput = append(unSpentTxOutput, txOutput)
            utxo := &UTXO{tx.TxID, index, txOutput}
            unSpentUTXOs = append(unSpentUTXOs, utxo)
         }
      }
   }
   return unSpentUTXOs

}
(4) 設(shè)置簽名

從0到1簡(jiǎn)易區(qū)塊鏈開(kāi)發(fā)手冊(cè)V0.4-實(shí)現(xiàn)轉(zhuǎn)賬交易的思路分析

SignTransaction

func (bc *BlockChain) SignTransaction(tx *Transaction, privateKey ecdsa.PrivateKey, txs []*Transaction) {
   //1.判斷要簽名的tx,如果是coninbase交易直接返回
   if tx.IsCoinBaseTransaction() {
      return
   }

   //2.獲取該tx中的Input,引用之前的transaction中的未花費(fèi)的output
   prevTxs := make(map[string]*Transaction)
   for _, input := range tx.Vins {
      txIDStr := hex.EncodeToString(input.TxID)
      prevTxs[txIDStr] = bc.FindTransactionByTxID(input.TxID, txs)
   }

   //3.簽名
   tx.Sign(privateKey, prevTxs)

}

Sign

func (tx *Transaction) Sign(privateKey ecdsa.PrivateKey, prevTxsmap map[string]*Transaction) {
    //1.判斷當(dāng)前tx是否是coinbase交易
    if tx.IsCoinBaseTransaction() {
        return
    }

    //2.獲取input對(duì)應(yīng)的output所在的tx,如果不存在,無(wú)法進(jìn)行簽名
    for _, input := range tx.Vins {
        if prevTxsmap[hex.EncodeToString(input.TxID)] == nil {
            log.Panic("當(dāng)前的Input,沒(méi)有找到對(duì)應(yīng)的output所在的Transaction,無(wú)法簽名。。")
        }
    }

    //即將進(jìn)行簽名:私鑰,要簽名的數(shù)據(jù)
    txCopy := tx.TrimmedCopy()

    for index, input := range txCopy.Vins {

        prevTx := prevTxsmap[hex.EncodeToString(input.TxID)]

        txCopy.Vins[index].Signature = nil    
        txCopy.Vins[index].PublicKey = prevTx.Vouts[input.Vout].PubKeyHash //設(shè)置input中的publickey為對(duì)應(yīng)的output的公鑰哈希

        txCopy.TxID = txCopy.NewTxID()//產(chǎn)生要簽名的交易的TxID

        //為了方便下一個(gè)input,將數(shù)據(jù)再置為空
        txCopy.Vins[index].PublicKey = nil

        /*
        第一個(gè)參數(shù)
        第二個(gè)參數(shù):私鑰
        第三個(gè)參數(shù):要簽名的數(shù)據(jù)

        func Sign(rand io.Reader, priv *PrivateKey, hash []byte) (r, s *big.Int, err error)
        r + s--->sign
        input.Signatrue = sign
     */
        r,s,err:=ecdsa.Sign(rand.Reader, &privateKey, txCopy.TxID )
        if err != nil{
            log.Panic(err)
        }

        sign:=append(r.Bytes(),s.Bytes()...)
        tx.Vins[index].Signature = sign
    }

}

TrimmedCopy

func (tx *Transaction) TrimmedCopy() *Transaction {
    var inputs [] *TxInput
    var outputs [] *TxOutput
    for _, in := range tx.Vins {
        inputs = append(inputs, &TxInput{in.TxID, in.Vout, nil, nil})
    }

    for _, out := range tx.Vouts {
        outputs = append(outputs, &TxOutput{out.Value, out.PubKeyHash})
    }

    txCopy := &Transaction{tx.TxID, inputs, outputs}
    return txCopy

}
4.2.2 驗(yàn)證交易
func (tx *Transaction) Verifity(prevTxs map[string]*Transaction)bool{
    //1.如果是coinbase交易,不需要驗(yàn)證
    if tx.IsCoinBaseTransaction(){
        return true
    }

    //prevTxs
    for _,input:=range prevTxs{
        if prevTxs[hex.EncodeToString(input.TxID)] == nil{
            log.Panic("當(dāng)前的input沒(méi)有找到對(duì)應(yīng)的Transaction,無(wú)法驗(yàn)證。。")
        }
    }

    //驗(yàn)證
    txCopy:= tx.TrimmedCopy()

    curev:= elliptic.P256() //曲線

    for index,input:=range tx.Vins{
        //原理:再次獲取 要簽名的數(shù)據(jù)  + 公鑰哈希 + 簽名
        /*
        驗(yàn)證簽名的有效性:
        第一個(gè)參數(shù):公鑰
        第二個(gè)參數(shù):簽名的數(shù)據(jù)
        第三、四個(gè)參數(shù):簽名:r,s
        func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool
         */
        //ecdsa.Verify()

    //獲取要簽名的數(shù)據(jù)
        prevTx:=prevTxs[hex.EncodeToString(input.TxID)]

        txCopy.Vins[index].Signature = nil
        txCopy.Vins[index].PublicKey = prevTx.Vouts[input.Vout].PubKeyHash
        txCopy.TxID = txCopy.NewTxID() //要簽名的數(shù)據(jù)

        txCopy.Vins[index].PublicKey = nil

        //獲取公鑰
        /*
        type PublicKey struct {
            elliptic.Curve
            X, Y *big.Int
        }
         */

        x:=big.Int{}
        y:=big.Int{}
        keyLen:=len(input.PublicKey)
        x.SetBytes(input.PublicKey[:keyLen/2])
        y.SetBytes(input.PublicKey[keyLen/2:])

        rawPublicKey:=ecdsa.PublicKey{curev,&x,&y}

        //獲取簽名:

        r :=big.Int{}
        s :=big.Int{}

        signLen:=len(input.Signature)
        r.SetBytes(input.Signature[:signLen/2])
        s.SetBytes(input.Signature[signLen/2:])

        if ecdsa.Verify(&rawPublicKey,txCopy.TxID,&r,&s) == false{
            return false
        }

    }
    return true
}
4.2.3 創(chuàng)建CoinBase交易
func NewCoinBaseTransaction(address string) *Transaction {
    txInput := &TxInput{[]byte{}, -1, nil, nil}
    //txOutput := &TxOutput{10, address}
    txOutput := NewTxOutput(10, address)
    txCoinBaseTransaction := &Transaction{[]byte{}, []*TxInput{txInput}, []*TxOutput{txOutput}}
    //設(shè)置交易ID
    txCoinBaseTransaction.SetID()
    return txCoinBaseTransaction
}

在每個(gè)區(qū)塊中創(chuàng)建一個(gè)CoinBase交易作為獎(jiǎng)勵(lì)機(jī)制。

4.2.4 新建區(qū)塊
func NewBlock(txs []*Transaction, prevBlockHash [] byte, height int64) *Block {
    //創(chuàng)建區(qū)塊
    block := &Block{height, prevBlockHash, txs, time.Now().Unix(), nil,0}
    //設(shè)置hash
    //block.SetHash()
    pow:=NewProofOfWork(block)
    hash,nonce:=pow.Run()
    block.Hash = hash
    block.Nonce = nonce

    return block
}
4.2.5 持久化存儲(chǔ)
    err = bc.DB.Update(func(tx *bolt.Tx) error {
        b := tx.Bucket([]byte(BlockBucketName))
        if b != nil {
            //將新block存入到數(shù)據(jù)庫(kù)中
            b.Put(newBlock.Hash, newBlock.Serialize())
            //更新l
            b.Put([]byte("l"), newBlock.Hash)
            //tip
            bc.Tip = newBlock.Hash
        }

        return nil
    })
    if err != nil {
        log.Panic(err)
    }
4.3 更新UTXO集
func (utxoSet *UTXOSet) Update() {
    /*
    表:key:txID
        value:TxOutputs
            UTXOs []UTXO
     */

    //1.獲取最后(從后超前遍歷)一個(gè)區(qū)塊,遍歷該區(qū)塊中的所有tx
    newBlock := utxoSet.BlockChian.Iterator().Next()
    //2.獲取所有的input
    inputs := [] *TxInput{}
    //遍歷交易,獲取所有的input
    for _, tx := range newBlock.Txs {
        if !tx.IsCoinBaseTransaction() {
            for _, in := range tx.Vins {
                inputs = append(inputs, in)
            }
        }
    }

    //存儲(chǔ)該區(qū)塊中的,tx中的未花費(fèi)
    outsMap := make(map[string]*TxOutputs)

    //3.獲取所有的output
    for _, tx := range newBlock.Txs {
        utxos := []*UTXO{}
        //找出交易中的未花費(fèi)
        for index, output := range tx.Vouts {
            isSpent := false
            //遍歷inputs的數(shù)組,比較是否有intput和該output對(duì)應(yīng),如果滿足,表示花費(fèi)了
            for _, input := range inputs {
                if bytes.Compare(tx.TxID, input.TxID) == 0 && index == input.Vout {
                    if bytes.Compare(output.PubKeyHash, PubKeyHash(input.PublicKey)) == 0 {
                        isSpent = true
                    }
                }
            }
            if isSpent == false {
                //output未花
                utxo := &UTXO{tx.TxID, index, output}
                utxos = append(utxos, utxo)
            }
        }

        //utxos,
        if len(utxos) > 0 {
            txIDStr := hex.EncodeToString(tx.TxID)
            outsMap[txIDStr] = &TxOutputs{utxos}
        }

    }

    //刪除花費(fèi)了數(shù)據(jù),添加未花費(fèi)
    err := utxoSet.BlockChian.DB.Update(func(tx *bolt.Tx) error {

        b := tx.Bucket([]byte(utxosettable))
        if b != nil {
            //遍歷inputs,刪除
            for _, input := range inputs {
                txOutputsBytes := b.Get(input.TxID)
                if len(txOutputsBytes) == 0 {
                    continue
                }

                //反序列化
                txOutputs := DeserializeTxOutputs(txOutputsBytes)
                //是否需要被刪除
                isNeedDelete := false

                //存儲(chǔ)該txoutout中未花費(fèi)utxo
                utxos := []*UTXO{}

                for _, utxo := range txOutputs.UTXOs {
                    if bytes.Compare(utxo.Output.PubKeyHash, PubKeyHash(input.PublicKey)) == 0 && input.Vout == utxo.Index {
                        isNeedDelete = true
                    } else {
                        utxos = append(utxos, utxo)
                    }
                }

                if isNeedDelete == true {
                    b.Delete(input.TxID)
                    if len(utxos) > 0 {
                        txOutputs := &TxOutputs{utxos}
                        b.Put(input.TxID, txOutputs.Serialize())
                    }
                }
            }

            //遍歷map,添加
            for txIDStr, txOutputs := range outsMap {
                txID, _ := hex.DecodeString(txIDStr)
                b.Put(txID, txOutputs.Serialize())

            }

        }

        return nil
    })

    if err != nil {
        log.Panic(err)
    }
}
  • 刪除本次交易產(chǎn)生的input對(duì)應(yīng)的utxo
  • 添加本次交易產(chǎn)生的新utxo

5.代碼共享

由于轉(zhuǎn)賬交易這一塊的內(nèi)容代碼量特別大,腦圖跟交易流程圖我也是花費(fèi)了大量的時(shí)間進(jìn)行整理,但是要一項(xiàng)一項(xiàng)進(jìn)行代碼分析,時(shí)間成本還是太大了,所以,將github的代碼共享給大家,可以照著文章思路與思維導(dǎo)圖中的路線進(jìn)行適當(dāng)?shù)姆治?https://github.com/DiaboFong/MyPublicChain

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI