<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                本章源代碼地址:[https://github.com/daleboy/blockchain4] # 關于區塊鏈的交易 交易是一件復雜的事情,我們在本章中實現交易的通用部分,后面將就細節進行實現。 交易(transaction)是比特幣的核心所在,而區塊鏈的唯一目的,也正是為了能夠安全可靠地存儲交易。在區塊鏈中,交易一旦被創建,就沒有任何人能夠再去修改或是刪除它。 與現實不同,在比特幣中,支付是另外一種完全不同的方式: 1. 沒有賬戶(account) 2. 沒有余額(balance) 3. 沒有住址(address) 4. 沒有貨幣(coin) 5. 沒有發送人和接收人 # 比特幣交易 一筆交易由一些輸入(input)和輸出(output)組合而來: ![](https://img.kancloud.cn/09/06/0906055857723da1b343822fd7b4120b_700x361.png) ~~~ type Transaction struct {//交易的結構 ID []byte Vin []TXInput Vout []TXOutput } ~~~ 對于每一筆新的交易,它的輸入會引用(reference)之前一筆交易的輸出(這里有個例外,也就是我們待會兒要談到的 coinbase 交易)。所謂引用之前的一個輸出,也就是將之前的一個輸出包含在另一筆交易的輸入當中。交易的輸出,也就是幣實際存儲的地方。 注意: 1. 有一些輸出并沒有被關聯到某個輸入上 2. 一筆交易的輸入可以引用之前多筆交易的輸出 3. 一個輸入必須引用一個輸出 貫穿本文,我們將會使用像“錢(money)”,“幣(coin)”,“花費(spend)”,“發送(send)”,“賬戶(account)” 等等這樣的詞。但是在比特幣中,實際并不存在這樣的概念。交易僅僅是通過一個腳本(script)來鎖定(lock)一些價值(value),而這些價值只可以被鎖定它們的人解鎖(unlock)。 # 交易輸出 讓我們先從輸出(output)開始: ~~~ type TXOutput struct {//交易輸出的結構 Value int//存儲的幣 ScriptPubKey string//解鎖腳本,這個腳本定義了解鎖該輸出的邏輯。 } ~~~ 實際上,正是輸出里面存儲了“幣”(注意,也就是上面的`Value`字段)。而這里的存儲,指的是用一個數學難題對輸出進行鎖定,這個難題被存儲在`ScriptPubKey`里面。在內部,比特幣使用了一個叫做*Script*的腳本語言,用它來定義鎖定和解鎖輸出的邏輯。雖然這個語言相當的原始(這是為了避免潛在的黑客攻擊和濫用而有意為之),并不復雜,但是我們并不會在這里討論它的細節。你可以在[這里](https://link.jianshu.com/?t=https://en.bitcoin.it/wiki/Script)找到詳細解釋。 ~~~ 在比特幣中,`value`字段存儲的是*satoshi*的數量,而不是>有 BTC 的數量。一個*satoshi*等于一百萬分之一的 >BTC(0.00000001 BTC),這也是比特幣里面最小的貨幣單位>(就像是 1 分的硬幣)。 ~~~ 由于還沒有實現地址(address),所以目前我們會避免涉及邏輯相關的完整腳本。`ScriptPubKey`將會存儲一個任意的字符串(用戶定義的錢包地址)。 ~~~ 順便說一下,有了一個這樣的腳本語言,也意味著比特幣其實也可以作為一個智能合約平臺。 ~~~ 關于輸出,非常重要的一點是:它們是**不可再分的(invisible)**,這也就是說,你無法僅引用它的其中某一部分。要么不用,如果要用,必須一次性用完。當一個新的交易中引用了某個輸出,那么這個輸出必須被全部花費。如果它的值比需要的值大,那么就會產生一個找零,找零會返還給發送方。 # 交易輸入 這里是輸入: ~~~ type TXInput struct {//交易輸入的結構 Txid []byte//這筆交易的 ID Vout int//該輸出在這筆交易中所有輸出的索引(因為一筆交易可能有多個輸出,需要有信息指明是具體的哪一個)。 ScriptSig string } ~~~ `ScriptSig`是一個腳本,提供了可作用于一個輸出的`ScriptPubKey`的數據。如果`ScriptSig`提供的數據是正確的,那么輸出就會被解鎖,然后被解鎖的值就可以被用于產生新的輸出;如果數據不正確,輸出就無法被引用在輸入中,或者說,也就是無法使用這個輸出。這種機制,保證了用戶無法花費屬于其他人的幣。 再次強調,由于我們還沒有實現地址,所以`ScriptSig`將僅僅存儲一個任意用戶定義的錢包地址。我們會在下一篇文章中實現公鑰(public key)和簽名(signature)。 在比特幣中,每一筆輸入都是之前一筆交易的輸出,那么從一筆交易開始不斷往前追溯,它涉及的輸入和輸出到底是誰先存在呢?答案是:最先有輸出,然后才有輸入。換而言之,第一筆交易只有輸出,沒有輸入。 當礦工挖出一個新的塊時,它會向新的塊中添加一個**coinbase**交易。coinbase 交易是一種特殊的交易,它不需要引用之前一筆交易的輸出。它“憑空”產生了幣(也就是產生了新幣),這也是礦工獲得挖出新塊的獎勵,可以理解為“發行新幣”。 在區塊鏈的最初,也就是第一個塊,叫做創世塊。正是這個創世塊,產生了區塊鏈最開始的輸出。對于創世塊,不需要引用之前交易的輸出。因為在創世塊之前根本不存在交易,也就沒有不存在有交易輸出。 來創建一個 coinbase 交易: ~~~ func NewCoinbaseTX(to, data string) *Transaction { if data == "" { data = fmt.Sprintf("Reward to '%s'", to) } txin := TXInput{[]byte{}, -1, data}//輸入結構:Txid為空,Vout為-1 txout := TXOutput{subsidy, to}//subsidy為獎勵的數額 tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}//只有一個輸出,沒有輸入。 tx.SetID() return &tx } ~~~ `subsidy`是獎勵的數額。在比特幣中,實際并沒有存儲這個數字,而是基于區塊總數進行計算而得:區塊總數除以 210000 就是`subsidy`。挖出創世塊的獎勵是 50 BTC,每挖出`210000`個塊后,獎勵減半。在我們的實現中,這個獎勵值將會是一個常量(至少目前是)。 # 將交易保存到區塊鏈 從現在開始,每個塊必須存儲至少一筆交易。如果沒有交易,也就不可能挖出新的塊。這意味著我們應該移除`Block`的`Data`字段,取而代之的是存儲交易: ~~~ type Block struct { Timestamp int64 Transactions []*Transaction//交易替代data string PrevBlockHash []byte Hash []byte Nonce int } ~~~ `NewBlock`和`NewGenesisBlock`也必須做出相應改變: ~~~ func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0} ... } func NewGenesisBlock(coinbase *Transaction) *Block {//注意,參數是coinbase交易 return NewBlock([]*Transaction{coinbase}, []byte{}) } ~~~ 接下來修改創建新鏈的函數: ~~~ func CreateBlockchain(address string) *Blockchain { ... err = db.Update(func(tx *bolt.Tx) error { cbtx := NewCoinbaseTX(address, genesisCoinbaseData)//創建一個coinbase交易 genesis := NewGenesisBlock(cbtx)//包含coinbase交易挖出創始區塊 d?:=?genesis.Serialize() err?=?b.Put(genesis.Hash,?d)?//將創始區塊序列化后插入到數據庫表中 if?err?!=?nil?{ ????????????log.Panic(err) ????????} b, err := tx.CreateBucket([]byte(blocksBucket)) err = b.Put(genesis.Hash, genesis.Serialize()) ... }) ... } ~~~ 現在,這個函數會接受一個地址作為參數,這個地址會用來接收挖出創世塊的獎勵。 工作量證明算法必須要將存儲在區塊里面的交易考慮進去,以此保證區塊鏈交易存儲的一致性和可靠性。所以,我們必須修改`ProofOfWork.prepareData`方法: ~~~ func (pow *ProofOfWork) prepareData(nonce int) []byte { data := bytes.Join( [][]byte{ pow.block.PrevBlockHash, pow.block.HashTransactions(), // 計算交易的哈希,而不再是data string的哈希 IntToHex(pow.block.Timestamp), IntToHex(int64(targetBits)), IntToHex(int64(nonce)), }, []byte{}, ) return data } ~~~ 計算交易的哈希方法如下: ~~~ func (b *Block) HashTransactions() []byte { var txHashes [][]byte var txHash [32]byte for _, tx := range b.Transactions {//一個block可能有多個交易 txHashes = append(txHashes, tx.ID)//只使用交易ID,不使用交易的輸入和輸出 } /注意,Join第二個參數是一個空的byte數組,也就是說,連接時候,兩個相鄰的[]byte之間不留間隔符 txHash = sha256.Sum256(bytes.Join(txHashes, []byte{}))//將所有交易ID連接起來計算處理的哈希,作為區塊的交易哈希 return txHash[:] } ~~~ ~~~ 比特幣使用了一個更加復雜的技術:它將一個塊里面包含的所有交易表示為一個[Merkle tree](https://link.jianshu.com/?t=https://en.wikipedia.org/wiki/Merkle_tree),然后在工作量證明系統中使用樹的根哈希(root hash)。這個方法能夠讓我們快速檢索一個塊里面是否包含了某筆交易,即只需 root hash 而無需下載所有交易即可完成判斷。 ~~~ # 未花費的交易輸出 我們需要找到所有的未花費交易輸出(unspent transactions outputs, UTXO)。**未花費(unspent)**指的是這個輸出還沒有被包含在任何交易的輸入中,或者說沒有被任何輸入引用。 當然了,當我們檢查余額時,一般是指那些我們能夠解鎖的那些 UTXO(目前我們還沒有實現密鑰,所以我們將會使用用戶定義的地址來代替,所以這里查詢的是某個地址的余額)。首先,讓我們定義在輸入和輸出上的鎖定和解鎖方法: ~~~ func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool { return in.ScriptSig == unlockingData } func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool { return out.ScriptPubKey == unlockingData } ~~~ 首先,讓我們定義在輸入和輸出上的解鎖方法: ~~~ func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool { return in.ScriptSig == unlockingData } func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool { return out.ScriptPubKey == unlockingData } ~~~ 在這里,我們只是將 script 字段與`unlockingData`進行了比較(能解鎖,說明交易與其有關)。在后續文章我們基于私鑰實現了地址以后,會對這部分進行改進。 下一步,找到包含未花費輸出的交易: ~~~ //查詢address地址的未花費輸出的交易 //如果一個輸出被一個地址鎖定,并且這個地址恰好是我們要找的未花費交易輸出的地址,那么這個輸出就是我們想要的。 //不過在獲取它之前,我們需要檢查該輸出是否已經被包含在一個輸入中,也就是檢查它是否已經被花費了: //由于交易被存儲在區塊里,所以我們不得不檢查區塊鏈里的每一筆交易。從輸出開始: func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction { var unspentTXs []Transaction//未花費交易(輸出還沒包含到其它的交易的輸入之中) spentTXOs := make(map[string][]int)//已花費交易輸出(查詢所有交易輸入可獲得) //查詢獲得所有已花費輸出:spentTXOs bci := bc.Iterator() for {//查詢區塊鏈中所有區塊 block := bci.Next() for _, tx := range block.Transactions {//查詢區塊里面的所有交易 txID := hex.EncodeToString(tx.ID) if tx.IsCoinbase() == false {//如果不是創始區塊交易(創始區塊沒有輸入) for _, in := range tx.Vin {//查詢交易中的每一個輸入 if in.CanUnlockOutputWith(address) {//address地址是否可以解鎖輸入中的輸入 inTxID := hex.EncodeToString(in.Txid) spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)//可以解鎖,將當前交易加入到已花費交易輸出數組中 } } } } if len(block.PrevBlockHash) == 0 {//已經循環結束:創始區塊的PrevBlockHash=[]byte{) break } } //查詢獲得所有為花費交易:unspentTXs bci = bc.Iterator() for {//查詢區塊鏈中所有區塊 block := bci.Next() for _, tx := range block.Transactions {//查詢區塊里面的所有交易 txID := hex.EncodeToString(tx.ID) Outputs: for outIdx, out := range tx.Vout {//查詢交易中的每一個輸出 // 輸出已經花費了嗎? if spentTXOs[txID] != nil { for _, spentOut := range spentTXOs[txID] {//如果交易ID在已花費支出map中已經存在,說明已經花費了,檢查下一個輸出 if spentOut == outIdx { continue Outputs//返回到前面的Outputs位置 } } } //該輸出沒有出現在已花費支出中,加入到未花費交易輸出數組中 if out.CanBeUnlockedWith(address) {//address地址是否可以解鎖輸出中的輸出 unspentTXs = append(unspentTXs, *tx)//可以解鎖,將當前交易加入到未花費交易輸出數組中 } } if len(block.PrevBlockHash) == 0 {//已經循環結束:創始區塊的PrevBlockHash=[]byte{) break } } return unspentTXs//返回未花費交易 } ~~~ # 獲得未花費輸出 將未花費交易作為輸入,然后僅返回一個輸出: ~~~ func (bc *Blockchain) FindUTXO(address string) []TXOutput { var UTXOs []TXOutput unspentTransactions := bc.FindUnspentTransactions(address) for _, tx := range unspentTransactions { for _, out := range tx.Vout { if out.CanBeUnlockedWith(address) { UTXOs = append(UTXOs, out) } } } return UTXOs } ~~~ # 通過未花費輸出計算賬戶余額 ~~~ func (cli *CLI) getBalance(address string) { bc := NewBlockchain(address) defer bc.db.Close() balance := 0 UTXOs := bc.FindUTXO(address) for _, out := range UTXOs { balance += out.Value } fmt.Printf("Balance of '%s': %d\n", address, balance) } ~~~ #轉賬 將金錢從一個賬戶轉移到另外一個賬戶,我們需要創建一筆新的交易,將它放到一個塊里,然后挖出這個塊。之前我們只實現了 coinbase 交易(這是一種特殊的交易),現在我們需要一種通用的交易: ~~~ func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { var inputs []TXInput var outputs []TXOutput //validOutputs為sender為此交易提供的輸出,不一定是sender的全部輸出 //acc為sender發出的全部幣數 acc, validOutputs := bc.FindSpendableOutputs(from, amount) if acc < amount { log.Panic("ERROR: 沒有交易所需的足夠的錢") } // 建立輸入參數 for txid, outs := range validOutputs { txID, err := hex.DecodeString(txid) for _, out := range outs { input := TXInput{txID, out, from} inputs = append(inputs, input) } } // 建立輸出參數 outputs = append(outputs, TXOutput{amount, to})//款項發給收款人 if acc > amount { outputs = append(outputs, TXOutput{acc - amount, from}) //找零,退給轉賬人 } tx := Transaction{nil, inputs, outputs}//初始交易ID設為nil tx.SetID()//緊接著設置交易的ID return &tx } ~~~ 轉賬方法: ~~~ func (cli *CLI) send(from, to string, amount int) { bc := NewBlockchain(from) defer bc.db.Close() tx := NewUTXOTransaction(from, to, amount, bc) bc.MineBlock([]*Transaction{tx}) fmt.Println("轉賬成功!") } ~~~ # 程序執行驗證 ## 創建新的區塊鏈 ![](https://img.kancloud.cn/13/7b/137b67c95ef0b30045df44a679d7cac6_483x132.png) ## 獲得余額 ![](https://img.kancloud.cn/5f/53/5f53b239053eb74327ab6c9b4e4fb554_731x80.png) ## 轉賬 ![](https://img.kancloud.cn/f5/28/f52822da2f6c194f704d5f682ac3ea28_785x202.png) ## 打印區塊鏈 ![](https://img.kancloud.cn/c2/d9/c2d958ce1dbd76eb595eaf7da61a6f2d_1017x504.png)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看