創世區塊建立完畢以後,按照咱們的正常思路,是繼續建立新的區塊,並加入至區塊鏈中,沒錯,這確實是學習路線,可是咱們首先來了解一個區塊是如何生成的,轉帳交易 ===>打包交易 ===>工做量證實 ===>生成區塊html
在上文,咱們提到了錢包地址這個概念,咱們通常能夠簡單將錢包地址理解爲一個銀行帳戶,那麼交易也就能夠理解爲是地址與地址之間的轉帳過程。git
由於這部份內容很是重要,設置能夠說交易就是比特幣原理的核心,因此,爲了保證你們對概念有充分的瞭解,本章節的理論描述部分此處摘錄liuchengxu中關於對交易的翻譯。github
交易(transaction)是比特幣的核心所在,而區塊鏈惟一的目的,也正是爲了可以安全可靠地存儲交易。在區塊鏈中,交易一旦被建立,就沒有任何人可以再去修改或是刪除它。今天,咱們將會開始實現交易。不過,因爲交易是很大的話題,我會把它分爲兩部分來說:在今天這個部分,咱們會實現交易的基本框架。在第二部分,咱們會繼續討論它的一些細節。數據庫
因爲比特幣採用的是 UTXO 模型,並不是帳戶模型,並不直接存在「餘額」這個概念,餘額須要經過遍歷整個交易歷史得來。數組
關於UTXO模型,這在比特幣中也是很是重要的概念模型,務必熟練掌握。安全
點擊此處查看相關的交易信息app
圖 交易記錄
框架
圖 輸入腳本ide
關於轉帳交易涉及到的內容很是多,因爲時間緣由,目前可能沒法作到很是全面的講解,姑且將本身梳理好可以解釋清楚的地方分享出來,因爲比特幣世界中的交易規則會更加複雜化,因此,但願你們可以經過本章節的閱讀,在必定程度上對某些概念有一些初步或者稍微深入的理解,那麼本章節的目的也就達到了,更深的分析筆者將會在後期的工做中根據實際的工做場景進行優化並作相關記錄。oop
其實再轉帳交易這個功能裏面,涉及了本文全部的結構體對象,因爲區塊與區塊鏈對象等在上文已經有所說起,這裏先列出跟轉帳交易關係最爲密切的一些結構體。
type Transaction struct { //1.交易ID TxID []byte //2.輸入 Vins []*TxInput //3.輸出 Vouts []*TxOutput }
type TxInput struct { //1.交易ID: TxID []byte //2.下標 Vout int //3.數字簽名 Signature []byte //4.原始公鑰,錢包裏的公鑰 PublicKey []byte }
type TxOutput struct { //金額 Value int64 //金額 //鎖定腳本,也叫輸出腳本,公鑰,目前先理解爲用戶名,鑰花費這筆前,必須鑰先解鎖腳本 //ScriptPubKey string PubKeyHash [] byte//公鑰哈希 }
Value : 金額,轉帳/找零金額
type UTXO struct { //1.該output所在的交易id TxID []byte //2.該output 的下標 Index int //3.output Output *TxOutput }
UTXO:Unspent Transaction output
type UTXOSet struct { BlockChian *BlockChain }
const utxosettable = "utxoset"
定義一個常量用於標識存入數據庫中的Bucket表名
單筆轉帳
$ ./mybtc send -from 源地址 -to 目標地址 -amount 轉帳金額
多筆轉帳
$ ./mybtc send \ -from '["源地址1","源地址2","源地址N"]' \ -to '["目標地址1","目標地址2","目標地址3"]' \ -amount '["轉帳金額1","轉帳金額2","轉帳金額3"]'
這部份內容理解起來有些難度,因此我作了一張圖,但願可以幫助你們可以理順思路,這樣在後面的學習以及代碼理解上面會稍微容易一些。
本圖介紹了從創世區塊後的三次轉帳過程,分別產生了三個區塊,爲了讓讀者有更直觀的瞭解,我又將該圖作成了動態圖的方式供你們參考,經過該圖,但願你們可以大體對轉帳交易有個印象。
動態圖演示了新區塊中的輸入交易引用的是哪一個區塊中的交易輸出,從而實現了區塊鏈每次轉帳的金額都有據可依,也從另一個角度展現了比特幣中UTXO的概念模型。
因爲代碼量巨大,爲了讓整個過程的理解更加流程,我改變前面幾篇文章的思路,從執行命令的代碼塊進行一步一步的代碼分析,但願能將本身的思路理順,從而能夠更好得引導讀者朋友。
一樣,由於不少概念性的東西,我不許備在文章裏面囉嗦,若是感受閱讀難度比較大,建議先仔細閱讀這篇文章
https://github.com/liuchengxu/blockchain-tutorial
而後再回頭來看個人這篇文章,會事半功倍
func (cli *CLI) Send(from, to, amount []string) { bc := GetBlockChainObject() if bc == nil { fmt.Println("沒有BlockChain,沒法轉帳。。") os.Exit(1) } defer bc.DB.Close() bc.MineNewBlock(from, to, amount) utsoSet :=&UTXOSet{bc} utsoSet.Update() }
func GetBlockChainObject() *BlockChain { /* 1.數據庫存在,讀取數據庫,返回blockchain便可 2.數據庫 不存在,返回nil */ if dbExists() { //fmt.Println("數據庫已經存在。。。") //打開數據庫 db, err := bolt.Open(DBName, 0600, nil) if err != nil { log.Panic(err) } var blockchain *BlockChain err = db.View(func(tx *bolt.Tx) error { //打開bucket,讀取l對應的最新的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("數據庫不存在,沒法獲取BlockChain對象。。。") return nil } }
判斷存儲區塊的DB文件是否存在,若是存在,直接從數據庫Bucket中讀取"l"對應Hash值,將db對象與獲取到hash值賦值給須要返回的區塊鏈對象,若是DB文件不存在,說明創世區塊並未建立,沒有區塊鏈對象,直接退出程序。
獲取到區塊鏈對象以後,咱們調用MineNewBlock方法進行區塊的建立
func (bc *BlockChain) MineNewBlock(from, to, amount []string) { /* 1.新建交易 2.新建區塊: 讀取數據庫,獲取最後一塊block 3.存入到數據庫中 */ //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) } /* 分析:循環第一次:i=0 txs[transaction1, ] 循環第二次:i=1 txs [transaction1, transaction2] */ //交易的驗證: for _, tx := range txs { if bc.VerifityTransaction(tx, txs) == false { log.Panic("數字簽名驗證失敗。。。") } } /* 獎勵:reward: 建立一個CoinBase交易--->Tx */ coinBaseTransaction := NewCoinBaseTransaction(from[0]) txs = append(txs, coinBaseTransaction) //2.新建區塊 newBlock := new(Block) err := bc.DB.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BlockBucketName)) if b != nil { //讀取數據庫 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.存入到數據庫中 err = bc.DB.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BlockBucketName)) if b != nil { //將新block存入到數據庫中 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) } }
在以上的代碼中,涉及到幾個比較重要的方法,其中一個NewSimpleTransaction用於建立交易並打包,這裏對代碼進行了簡單梳理,因爲內容實在太多,在文章末尾我會將github的源代碼地址貼出,供你們查看。
NewSimpleTransaction
func NewSimpleTransaction(from, to string, amount int64, utxoSet *UTXOSet, txs []*Transaction) *Transaction { //1.定義Input和Output的數組 var txInputs []*TxInput var txOuputs [] *TxOutput //2.建立Input /* 創世區塊中交易ID:c16d3ad93450cd532dcd7ef53d8f396e46b2e59aa853ad44c284314c7b9db1b4 */ //獲取本次轉帳要使用output //total, spentableUTXO := bc.FindSpentableUTXOs(from, amount, txs) //map[txID]-->[]int{index} total, spentableUTXO := utxoSet.FindSpentableUTXOs(from, amount, txs) //map[txID]-->[]int{index} //獲取錢包的集合: 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.建立Output //轉帳 txOutput := NewTxOutput(amount, to) txOuputs = append(txOuputs, txOutput) //找零 //txOutput2 := &TxOutput{total - amount, from} txOutput2 := NewTxOutput(total-amount, from) txOuputs = append(txOuputs, txOutput2) //4.建立交易 tx := &Transaction{[]byte{}, txInputs, txOuputs} //設置交易的ID tx.SetID() //設置簽名 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 //用於存儲轉帳所使用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 //已經存儲的可是未花費的utxo err := utxoSet.BlockChian.DB.View(func(tx *bolt.Tx) error { //查詢utxotable中,未花費的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 { //存儲可使用的未花費utxo var unUTXOs []*UTXO //存儲已經花費的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 { //遍歷每一個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} //遍歷 記錄已經花費的下標的數組 for _, i := range indexArray { if i == index && hex.EncodeToString(tx.TxID) == txID { isSpentOutput = true //標記當前的txOutput是已經花費 continue outputs } } } if !isSpentOutput { //unSpentTxOutput = append(unSpentTxOutput, txOutput) //根據未花費的output,建立utxo對象--->數組 utxo := &UTXO{tx.TxID, index, txOutput} unSpentUTXOs = append(unSpentUTXOs, utxo) } } else { //若是map長度爲0,證實尚未花費記錄,output無需判斷 //unSpentTxOutput = append(unSpentTxOutput, txOutput) utxo := &UTXO{tx.TxID, index, txOutput} unSpentUTXOs = append(unSpentUTXOs, utxo) } } } return unSpentUTXOs }
SignTransaction
func (bc *BlockChain) SignTransaction(tx *Transaction, privateKey ecdsa.PrivateKey, txs []*Transaction) { //1.判斷要簽名的tx,若是是coninbase交易直接返回 if tx.IsCoinBaseTransaction() { return } //2.獲取該tx中的Input,引用以前的transaction中的未花費的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.判斷當前tx是不是coinbase交易 if tx.IsCoinBaseTransaction() { return } //2.獲取input對應的output所在的tx,若是不存在,沒法進行簽名 for _, input := range tx.Vins { if prevTxsmap[hex.EncodeToString(input.TxID)] == nil { log.Panic("當前的Input,沒有找到對應的output所在的Transaction,沒法簽名。。") } } //即將進行簽名:私鑰,要簽名的數據 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 //設置input中的publickey爲對應的output的公鑰哈希 txCopy.TxID = txCopy.NewTxID()//產生要簽名的交易的TxID //爲了方便下一個input,將數據再置爲空 txCopy.Vins[index].PublicKey = nil /* 第一個參數 第二個參數:私鑰 第三個參數:要簽名的數據 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 }
func (tx *Transaction) Verifity(prevTxs map[string]*Transaction)bool{ //1.若是是coinbase交易,不須要驗證 if tx.IsCoinBaseTransaction(){ return true } //prevTxs for _,input:=range prevTxs{ if prevTxs[hex.EncodeToString(input.TxID)] == nil{ log.Panic("當前的input沒有找到對應的Transaction,沒法驗證。。") } } //驗證 txCopy:= tx.TrimmedCopy() curev:= elliptic.P256() //曲線 for index,input:=range tx.Vins{ //原理:再次獲取 要簽名的數據 + 公鑰哈希 + 簽名 /* 驗證簽名的有效性: 第一個參數:公鑰 第二個參數:簽名的數據 第3、四個參數:簽名:r,s func Verify(pub *PublicKey, hash []byte, r, s *big.Int) bool */ //ecdsa.Verify() //獲取要簽名的數據 prevTx:=prevTxs[hex.EncodeToString(input.TxID)] txCopy.Vins[index].Signature = nil txCopy.Vins[index].PublicKey = prevTx.Vouts[input.Vout].PubKeyHash txCopy.TxID = txCopy.NewTxID() //要簽名的數據 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 }
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}} //設置交易ID txCoinBaseTransaction.SetID() return txCoinBaseTransaction }
在每一個區塊中建立一個CoinBase交易做爲獎勵機制。
func NewBlock(txs []*Transaction, prevBlockHash [] byte, height int64) *Block { //建立區塊 block := &Block{height, prevBlockHash, txs, time.Now().Unix(), nil,0} //設置hash //block.SetHash() pow:=NewProofOfWork(block) hash,nonce:=pow.Run() block.Hash = hash block.Nonce = nonce return block }
err = bc.DB.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BlockBucketName)) if b != nil { //將新block存入到數據庫中 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) }
func (utxoSet *UTXOSet) Update() { /* 表:key:txID value:TxOutputs UTXOs []UTXO */ //1.獲取最後(從後超前遍歷)一個區塊,遍歷該區塊中的全部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) } } } //存儲該區塊中的,tx中的未花費 outsMap := make(map[string]*TxOutputs) //3.獲取全部的output for _, tx := range newBlock.Txs { utxos := []*UTXO{} //找出交易中的未花費 for index, output := range tx.Vouts { isSpent := false //遍歷inputs的數組,比較是否有intput和該output對應,若是知足,表示花費了 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} } } //刪除花費了數據,添加未花費 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 //存儲該txoutout中未花費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) } }
因爲轉帳交易這一塊的內容代碼量特別大,腦圖跟交易流程圖我也是花費了大量的時間進行整理,可是要一項一項進行代碼分析,時間成本仍是太大了,因此,將github的代碼共享給你們,能夠照着文章思路與思惟導圖中的路線進行適當的分析,https://github.com/DiaboFong/MyPublicChain