成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

用 Go 構(gòu)建一個(gè)區(qū)塊鏈 -- Part 4: 交易(1)

graf / 395人閱讀

摘要:引言交易是比特幣的核心所在,而區(qū)塊鏈的唯一目的,也正是為了能夠安全可靠地存儲(chǔ)交易。比特幣使用了一個(gè)更加復(fù)雜的技術(shù)它將一個(gè)塊里面包含的所有交易表示為一個(gè),然后在工作量證明系統(tǒng)中使用樹的根哈希。

翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHub 上的教程倉(cāng)庫(kù),進(jìn)入 src 目錄執(zhí)行 make 即可。


引言

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

此外,由于代碼實(shí)現(xiàn)變化很大,就不在文章中羅列所有代碼了,這里 可以看到所有的改動(dòng)。

沒(méi)有勺子

如果以前開(kāi)發(fā)過(guò) web 應(yīng)用,在支付的實(shí)現(xiàn)環(huán)節(jié),你可能會(huì)在數(shù)據(jù)庫(kù)中創(chuàng)建這樣兩張表:

accounts

transactions

account(賬戶)會(huì)存儲(chǔ)用戶信息,里面包括了個(gè)人信息和余額。transaction(交易)會(huì)存儲(chǔ)資金轉(zhuǎn)移信息,也就是資金從一個(gè)賬戶轉(zhuǎn)移到另一個(gè)賬戶這樣的內(nèi)容。在比特幣中,支付是另外一種完全不同的方式:

沒(méi)有賬戶(account)

沒(méi)有余額(balance)

沒(méi)有住址(address)

沒(méi)有貨幣(coin)

沒(méi)有發(fā)送人和接收人(sender,receiver)(這里所說(shuō)的發(fā)送人和接收人是基于目前現(xiàn)實(shí)生活場(chǎng)景,交易雙方與人是一一對(duì)應(yīng)的。而在比特幣中,“交易雙方”是地址,地址背后才是人,人與地址并不是一一對(duì)應(yīng)的關(guān)系,一個(gè)人可能有很多個(gè)地址。)

鑒于區(qū)塊鏈?zhǔn)且粋€(gè)公開(kāi)開(kāi)放的數(shù)據(jù)庫(kù),所以我們并不想要存儲(chǔ)錢包所有者的敏感信息(所以具有一定的匿名性)。資金不是通過(guò)賬戶來(lái)收集,交易也不是從一個(gè)地址將錢轉(zhuǎn)移到另一個(gè)地址,也沒(méi)有一個(gè)字段或者屬性來(lái)保存賬戶余額。交易就是區(qū)塊鏈要表達(dá)的所有內(nèi)容。那么,交易里面到底有什么內(nèi)容呢?

比特幣交易

一筆交易由一些輸入(input)和輸出(output)組合而來(lái):

type Transaction struct {
    ID   []byte
    Vin  []TXInput
    Vout []TXOutput
}

對(duì)于每一筆新的交易,它的輸入會(huì)引用(reference)之前一筆交易的輸出(這里有個(gè)例外,也就是我們待會(huì)兒要談到的 coinbase 交易)。所謂引用之前的一個(gè)輸出,也就是將之前的一個(gè)輸出包含在另一筆交易的輸入當(dāng)中。交易的輸出,也就是幣實(shí)際存儲(chǔ)的地方。下面的圖示闡釋了交易之間的互相關(guān)聯(lián):

注意:

有一些輸出并沒(méi)有被關(guān)聯(lián)到某個(gè)輸入上

一筆交易的輸入可以引用之前多筆交易的輸出

一個(gè)輸入必須引用一個(gè)輸出

貫穿本文,我們將會(huì)使用像“錢(money)”,“幣(coin)”,“花費(fèi)(spend)”,“發(fā)送(send)”,“賬戶(account)” 等等這樣的詞。但是在比特幣中,實(shí)際并不存在這樣的概念。交易僅僅是通過(guò)一個(gè)腳本(script)來(lái)鎖定(lock)一些價(jià)值(value),而這些價(jià)值只可以被鎖定它們的人解鎖(unlock)。

交易輸出

讓我們先從輸出(output)開(kāi)始:

type TXOutput struct {
    Value        int
    ScriptPubKey string
}

實(shí)際上,正是輸出里面存儲(chǔ)了“幣”(注意,也就是上面的 Value 字段)。而這里的存儲(chǔ),指的是用一個(gè)數(shù)學(xué)難題對(duì)輸出進(jìn)行鎖定,這個(gè)難題被存儲(chǔ)在 ScriptPubKey 里面。在內(nèi)部,比特幣使用了一個(gè)叫做 Script 的腳本語(yǔ)言,用它來(lái)定義鎖定和解鎖輸出的邏輯。雖然這個(gè)語(yǔ)言相當(dāng)?shù)脑迹ㄟ@是為了避免潛在的黑客攻擊和濫用而有意為之),并不復(fù)雜,但是我們并不會(huì)在這里討論它的細(xì)節(jié)。你可以在這里 找到詳細(xì)解釋。

在比特幣中,value 字段存儲(chǔ)的是 satoshi 的數(shù)量,而不是>有 BTC 的數(shù)量。一個(gè) satoshi 等于一百萬(wàn)分之一的 >BTC(0.00000001 BTC),這也是比特幣里面最小的貨幣單位>(就像是 1 分的硬幣)。

由于還沒(méi)有實(shí)現(xiàn)地址(address),所以目前我們會(huì)避免涉及邏輯相關(guān)的完整腳本。ScriptPubKey 將會(huì)存儲(chǔ)一個(gè)任意的字符串(用戶定義的錢包地址)。

順便說(shuō)一下,有了一個(gè)這樣的腳本語(yǔ)言,也意味著比特幣其實(shí)也可以作為一個(gè)智能合約平臺(tái)。

關(guān)于輸出,非常重要的一點(diǎn)是:它們是不可再分的(invisible),這也就是說(shuō),你無(wú)法僅引用它的其中某一部分。要么不用,如果要用,必須一次性用完。當(dāng)一個(gè)新的交易中引用了某個(gè)輸出,那么這個(gè)輸出必須被全部花費(fèi)。如果它的值比需要的值大,那么就會(huì)產(chǎn)生一個(gè)找零,找零會(huì)返還給發(fā)送方。這跟現(xiàn)實(shí)世界的場(chǎng)景十分類似,當(dāng)你想要支付的時(shí)候,如果一個(gè)東西值 1 美元,而你給了一個(gè) 5 美元的紙幣,那么你會(huì)得到一個(gè) 4 美元的找零。

交易輸入

這里是輸入:

type TXInput struct {
    Txid      []byte
    Vout      int
    ScriptSig string
}

正如之前所提到的,一個(gè)輸入引用了之前一筆交易的一個(gè)輸出:Txid 存儲(chǔ)的是這筆交易的 ID,Vout 存儲(chǔ)的是該輸出在這筆交易中所有輸出的索引(因?yàn)橐还P交易可能有多個(gè)輸出,需要有信息指明是具體的哪一個(gè))。ScriptSig 是一個(gè)腳本,提供了可作用于一個(gè)輸出的 ScriptPubKey 的數(shù)據(jù)。如果 ScriptSig 提供的數(shù)據(jù)是正確的,那么輸出就會(huì)被解鎖,然后被解鎖的值就可以被用于產(chǎn)生新的輸出;如果數(shù)據(jù)不正確,輸出就無(wú)法被引用在輸入中,或者說(shuō),也就是無(wú)法使用這個(gè)輸出。這種機(jī)制,保證了用戶無(wú)法花費(fèi)屬于其他人的幣。

再次強(qiáng)調(diào),由于我們還沒(méi)有實(shí)現(xiàn)地址,所以 ScriptSig 將僅僅存儲(chǔ)一個(gè)任意用戶定義的錢包地址。我們會(huì)在下一篇文章中實(shí)現(xiàn)公鑰(public key)和簽名(signature)。

來(lái)簡(jiǎn)要總結(jié)一下。輸出,就是 “幣” 存儲(chǔ)的地方。每個(gè)輸出都會(huì)帶有一個(gè)解鎖腳本,這個(gè)腳本定義了解鎖該輸出的邏輯。每筆新的交易,必須至少有一個(gè)輸入和輸出。一個(gè)輸入引用了之前一筆交易的輸出,并提供了數(shù)據(jù)(也就是 ScriptSig 字段),該數(shù)據(jù)會(huì)被用在輸出的解鎖腳本中解鎖輸出,解鎖完成后即可使用它的值去產(chǎn)生新的輸出。

也就是說(shuō),每一筆輸入都是之前一筆交易的輸出,那么從一筆交易開(kāi)始不斷往前追溯,它涉及的輸入和輸出到底是誰(shuí)先存在呢?換個(gè)說(shuō)法,這是個(gè)雞和蛋誰(shuí)先誰(shuí)后的問(wèn)題,是先有蛋還是先有雞呢?

先有蛋

在比特幣中,是先有蛋,然后才有雞。輸入引用輸出的邏輯,是經(jīng)典的“蛋還是雞”問(wèn)題:輸入先產(chǎn)生輸出,然后輸出使得輸入成為可能。在比特幣中,最先有輸出,然后才有輸入。換而言之,第一筆交易只有輸出,沒(méi)有輸入。

當(dāng)?shù)V工挖出一個(gè)新的塊時(shí),它會(huì)向新的塊中添加一個(gè) coinbase 交易。coinbase 交易是一種特殊的交易,它不需要引用之前一筆交易的輸出。它“憑空”產(chǎn)生了幣(也就是產(chǎn)生了新幣),這也是礦工獲得挖出新塊的獎(jiǎng)勵(lì),可以理解為“發(fā)行新幣”。

在區(qū)塊鏈的最初,也就是第一個(gè)塊,叫做創(chuàng)世塊。正是這個(gè)創(chuàng)世塊,產(chǎn)生了區(qū)塊鏈最開(kāi)始的輸出。對(duì)于創(chuàng)世塊,不需要引用之前交易的輸出。因?yàn)樵趧?chuàng)世塊之前根本不存在交易,也就沒(méi)有不存在有交易輸出。

來(lái)創(chuàng)建一個(gè) coinbase 交易:

func NewCoinbaseTX(to, data string) *Transaction {
    if data == "" {
        data = fmt.Sprintf("Reward to "%s"", to)
    }

    txin := TXInput{[]byte{}, -1, data}
    txout := TXOutput{subsidy, to}
    tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
    tx.SetID()

    return &tx
}

coinbase 交易只有一個(gè)輸出,沒(méi)有輸入。在我們的實(shí)現(xiàn)中,它的 Txid 為空,Vout 等于 -1。并且,在目前的視線中,coinbase 交易也沒(méi)有在 ScriptSig 中存儲(chǔ)一個(gè)腳本,而只是存儲(chǔ)了一個(gè)任意的字符串。

在比特幣中,第一筆 coinbase 交易包含了如下信息:“The Times 03/Jan/2009 Chancellor on brink of second bailout for banks”。可點(diǎn)擊這里查看.

subsidy 是獎(jiǎng)勵(lì)的數(shù)額。在比特幣中,實(shí)際并沒(méi)有存儲(chǔ)這個(gè)數(shù)字,而是基于區(qū)塊總數(shù)進(jìn)行計(jì)算而得:區(qū)塊總數(shù)除以 210000 就是 subsidy。挖出創(chuàng)世塊的獎(jiǎng)勵(lì)是 50 BTC,每挖出 210000 個(gè)塊后,獎(jiǎng)勵(lì)減半。在我們的實(shí)現(xiàn)中,這個(gè)獎(jiǎng)勵(lì)值將會(huì)是一個(gè)常量(至少目前是)。

將交易保存到區(qū)塊鏈

從現(xiàn)在開(kāi)始,每個(gè)塊必須存儲(chǔ)至少一筆交易。如果沒(méi)有交易,也就不可能挖出新的塊。這意味著我們應(yīng)該移除 BlockData 字段,取而代之的是存儲(chǔ)交易:

type Block struct {
    Timestamp     int64
    Transactions  []*Transaction
    PrevBlockHash []byte
    Hash          []byte
    Nonce         int
}

NewBlockNewGenesisBlock 也必須做出相應(yīng)改變:

func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block {
    block := &Block{time.Now().Unix(), transactions, prevBlockHash, []byte{}, 0}
    ...
}

func NewGenesisBlock(coinbase *Transaction) *Block {
    return NewBlock([]*Transaction{coinbase}, []byte{})
}

接下來(lái)修改創(chuàng)建新鏈的函數(shù):

func CreateBlockchain(address string) *Blockchain {
    ...
    err = db.Update(func(tx *bolt.Tx) error {
        cbtx := NewCoinbaseTX(address, genesisCoinbaseData)
        genesis := NewGenesisBlock(cbtx)

        b, err := tx.CreateBucket([]byte(blocksBucket))
        err = b.Put(genesis.Hash, genesis.Serialize())
        ...
    })
    ...
}

現(xiàn)在,這個(gè)函數(shù)會(huì)接受一個(gè)地址作為參數(shù),這個(gè)地址會(huì)用來(lái)接收挖出創(chuàng)世塊的獎(jiǎng)勵(lì)。

工作量證明

工作量證明算法必須要將存儲(chǔ)在區(qū)塊里面的交易考慮進(jìn)去,以此保證區(qū)塊鏈交易存儲(chǔ)的一致性和可靠性。所以,我們必須修改 ProofOfWork.prepareData 方法:

func (pow *ProofOfWork) prepareData(nonce int) []byte {
    data := bytes.Join(
        [][]byte{
            pow.block.PrevBlockHash,
            pow.block.HashTransactions(), // This line was changed
            IntToHex(pow.block.Timestamp),
            IntToHex(int64(targetBits)),
            IntToHex(int64(nonce)),
        },
        []byte{},
    )

    return data
}

不像之前使用 pow.block.Data,現(xiàn)在我們使用 pow.block.HashTransactions()

func (b *Block) HashTransactions() []byte {
    var txHashes [][]byte
    var txHash [32]byte

    for _, tx := range b.Transactions {
        txHashes = append(txHashes, tx.ID)
    }
    txHash = sha256.Sum256(bytes.Join(txHashes, []byte{}))

    return txHash[:]
}

我們使用哈希提供數(shù)據(jù)的唯一表示,這個(gè)之前也遇到過(guò)。我們想要通過(guò)僅僅一個(gè)哈希,就可以識(shí)別一個(gè)塊里面的所有交易。為此,我們獲得每筆交易的哈希,將它們關(guān)聯(lián)起來(lái),然后獲得一個(gè)連接后的組合哈希。

比特幣使用了一個(gè)更加復(fù)雜的技術(shù):它將一個(gè)塊里面包含的所有交易表示為一個(gè) ?Merkle tree ,然后在工作量證明系統(tǒng)中使用樹的根哈希(root hash)。這個(gè)方法能夠讓我們快速檢索一個(gè)塊里面是否包含了某筆交易,即只需 root hash 而無(wú)需下載所有交易即可完成判斷。

來(lái)檢查一下到目前為止是否正確:

$ blockchain_go createblockchain -address Ivan
00000093450837f8b52b78c25f8163bb6137caf43ff4d9a01d1b731fa8ddcc8a

Done!

很好!我們已經(jīng)獲得了第一筆挖礦獎(jiǎng)勵(lì),但是,我們要如何查看余額呢?

未花費(fèi)的交易輸出

我們需要找到所有的未花費(fèi)交易輸出(unspent transactions outputs, UTXO)。未花費(fèi)(unspent) 指的是這個(gè)輸出還沒(méi)有被包含在任何交易的輸入中,或者說(shuō)沒(méi)有被任何輸入引用。在上面的圖示中,未花費(fèi)的輸出是:

tx0, output 1;

tx1, output 0;

tx3, output 0;

tx4, output 0.

當(dāng)然了,當(dāng)我們檢查余額時(shí),我們并不需要知道整個(gè)區(qū)塊鏈上所有的 UTXO,只需要關(guān)注那些我們能夠解鎖的那些 UTXO(目前我們還沒(méi)有實(shí)現(xiàn)密鑰,所以我們將會(huì)使用用戶定義的地址來(lái)代替)。首先,讓我們定義在輸入和輸出上的鎖定和解鎖方法:

func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool {
    return in.ScriptSig == unlockingData
}

func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool {
    return out.ScriptPubKey == unlockingData
}

在這里,我們只是將 script 字段與 unlockingData 進(jìn)行了比較。在后續(xù)文章我們基于私鑰實(shí)現(xiàn)了地址以后,會(huì)對(duì)這部分進(jìn)行改進(jìn)。

下一步,找到包含未花費(fèi)輸出的交易,這一步相當(dāng)困難:

func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction {
  var unspentTXs []Transaction
  spentTXOs := make(map[string][]int)
  bci := bc.Iterator()

  for {
    block := bci.Next()

    for _, tx := range block.Transactions {
      txID := hex.EncodeToString(tx.ID)

    Outputs:
      for outIdx, out := range tx.Vout {
        // Was the output spent?
        if spentTXOs[txID] != nil {
          for _, spentOut := range spentTXOs[txID] {
            if spentOut == outIdx {
              continue Outputs
            }
          }
        }

        if out.CanBeUnlockedWith(address) {
          unspentTXs = append(unspentTXs, *tx)
        }
      }

      if tx.IsCoinbase() == false {
        for _, in := range tx.Vin {
          if in.CanUnlockOutputWith(address) {
            inTxID := hex.EncodeToString(in.Txid)
            spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
          }
        }
      }
    }

    if len(block.PrevBlockHash) == 0 {
      break
    }
  }

  return unspentTXs
}

由于交易被存儲(chǔ)在區(qū)塊里,所以我們不得不檢查區(qū)塊鏈里的每一筆交易。從輸出開(kāi)始:

if out.CanBeUnlockedWith(address) {
    unspentTXs = append(unspentTXs, tx)
}

如果一個(gè)輸出被一個(gè)地址鎖定,并且這個(gè)地址恰好是我們要找的未花費(fèi)交易輸出的地址,那么這個(gè)輸出就是我們想要的。不過(guò)在獲取它之前,我們需要檢查該輸出是否已經(jīng)被包含在一個(gè)輸入中,也就是檢查它是否已經(jīng)被花費(fèi)了:

if spentTXOs[txID] != nil {
    for _, spentOut := range spentTXOs[txID] {
        if spentOut == outIdx {
            continue Outputs
        }
    }
}

我們跳過(guò)那些已經(jīng)被包含在其他輸入中的輸出(被包含在輸入中,也就是說(shuō)明這個(gè)輸出已經(jīng)被花費(fèi),無(wú)法再用了)。檢查完輸出以后,我們將所有能夠解鎖給定地址鎖定的輸出的輸入聚集起來(lái)(這并不適用于 coinbase 交易,因?yàn)樗鼈儾唤怄i輸出):

if tx.IsCoinbase() == false {
    for _, in := range tx.Vin {
        if in.CanUnlockOutputWith(address) {
            inTxID := hex.EncodeToString(in.Txid)
            spentTXOs[inTxID] = append(spentTXOs[inTxID], in.Vout)
        }
    }
}

這個(gè)函數(shù)返回了一個(gè)交易列表,里面包含了未花費(fèi)輸出。為了計(jì)算余額,我們還需要一個(gè)函數(shù)將這些交易作為輸入,然后僅返回一個(gè)輸出:

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
}

就是這么多了!現(xiàn)在我們來(lái)實(shí)現(xiàn) getbalance 命令:

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
", address, balance)
}

賬戶余額就是由賬戶地址鎖定的所有未花費(fèi)交易輸出的總和。

在挖出創(chuàng)世塊以后,來(lái)檢查一下我們的余額:

$ blockchain_go getbalance -address Ivan
Balance of "Ivan": 10

這就是我們的第一筆錢!

發(fā)送幣

現(xiàn)在,我們想要給其他人發(fā)送一些幣。為此,我們需要?jiǎng)?chuàng)建一筆新的交易,將它放到一個(gè)塊里,然后挖出這個(gè)塊。之前我們只實(shí)現(xiàn)了 coinbase 交易(這是一種特殊的交易),現(xiàn)在我們需要一種通用的交易:

func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {
    var inputs []TXInput
    var outputs []TXOutput

    acc, validOutputs := bc.FindSpendableOutputs(from, amount)

    if acc < amount {
        log.Panic("ERROR: Not enough funds")
    }

    // Build a list of inputs
    for txid, outs := range validOutputs {
        txID, err := hex.DecodeString(txid)

        for _, out := range outs {
            input := TXInput{txID, out, from}
            inputs = append(inputs, input)
        }
    }

    // Build a list of outputs
    outputs = append(outputs, TXOutput{amount, to})
    if acc > amount {
        outputs = append(outputs, TXOutput{acc - amount, from}) // a change
    }

    tx := Transaction{nil, inputs, outputs}
    tx.SetID()

    return &tx
}

在創(chuàng)建新的輸出前,我們首先必須找到所有的未花費(fèi)輸出,并且確保它們存儲(chǔ)了足夠的值(value),這就是 FindSpendableOutputs 方法做的事情。隨后,對(duì)于每個(gè)找到的輸出,會(huì)創(chuàng)建一個(gè)引用該輸出的輸入。接下來(lái),我們創(chuàng)建兩個(gè)輸出:

一個(gè)由接收者地址鎖定。這是給實(shí)際給其他地址轉(zhuǎn)移的幣。

一個(gè)由發(fā)送者地址鎖定。這是一個(gè)找零。只有當(dāng)未花費(fèi)輸出超過(guò)新交易所需時(shí)產(chǎn)生。記住:輸出是不可再分的。

FindSpendableOutputs 方法基于之前定義的 FindUnspentTransactions 方法:

func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) {
    unspentOutputs := make(map[string][]int)
    unspentTXs := bc.FindUnspentTransactions(address)
    accumulated := 0

Work:
    for _, tx := range unspentTXs {
        txID := hex.EncodeToString(tx.ID)

        for outIdx, out := range tx.Vout {
            if out.CanBeUnlockedWith(address) && accumulated < amount {
                accumulated += out.Value
                unspentOutputs[txID] = append(unspentOutputs[txID], outIdx)

                if accumulated >= amount {
                    break Work
                }
            }
        }
    }

    return accumulated, unspentOutputs
}

這個(gè)方法對(duì)所有的未花費(fèi)交易進(jìn)行迭代,并對(duì)它的值進(jìn)行累加。當(dāng)累加值大于或等于我們想要傳送的值時(shí),它就會(huì)停止并返回累加值,同時(shí)返回的還有通過(guò)交易 ID 進(jìn)行分組的輸出索引。我們并不想要取出超出需要花費(fèi)的錢。

現(xiàn)在,我們可以修改 Blockchain.MineBlock 方法:

func (bc *Blockchain) MineBlock(transactions []*Transaction) {
    ...
    newBlock := NewBlock(transactions, lastHash)
    ...
}

最后,讓我們來(lái)實(shí)現(xiàn) send 方法:

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("Success!")
}

發(fā)送幣意味著創(chuàng)建新的交易,并通過(guò)挖出新塊的方式將交易打包到區(qū)塊鏈中。不過(guò),比特幣并不是一連串立刻完成這些事情(不過(guò)我們的實(shí)現(xiàn)是這么做的)。相反,它會(huì)將所有新的交易放到一個(gè)內(nèi)存池中(mempool),然后當(dāng)一個(gè)礦工準(zhǔn)備挖出一個(gè)新塊時(shí),它就從內(nèi)存池中取出所有的交易,創(chuàng)建一個(gè)候選塊。只有當(dāng)包含這些交易的塊被挖出來(lái),并添加到區(qū)塊鏈以后,里面的交易才開(kāi)始確認(rèn)。

讓我們來(lái)檢查一下發(fā)送幣是否能工作:

$ blockchain_go send -from Ivan -to Pedro -amount 6
00000001b56d60f86f72ab2a59fadb197d767b97d4873732be505e0a65cc1e37

Success!

$ blockchain_go getbalance -address Ivan
Balance of "Ivan": 4

$ blockchain_go getbalance -address Pedro
Balance of "Pedro": 6

很好!現(xiàn)在,讓我們創(chuàng)建更多的交易,確保從多個(gè)輸出中發(fā)送幣也正常工作:

$ blockchain_go send -from Pedro -to Helen -amount 2
00000099938725eb2c7730844b3cd40209d46bce2c2af9d87c2b7611fe9d5bdf

Success!

$ blockchain_go send -from Ivan -to Helen -amount 2
000000a2edf94334b1d94f98d22d7e4c973261660397dc7340464f7959a7a9aa

Success!

現(xiàn)在,Helen 的幣被鎖定在了兩個(gè)輸出中:一個(gè)來(lái)自 Pedro,一個(gè)來(lái)自 Ivan。讓我們把它們發(fā)送給其他人:

$ blockchain_go send -from Helen -to Rachel -amount 3
000000c58136cffa669e767b8f881d16e2ede3974d71df43058baaf8c069f1a0

Success!

$ blockchain_go getbalance -address Ivan
Balance of "Ivan": 2

$ blockchain_go getbalance -address Pedro
Balance of "Pedro": 4

$ blockchain_go getbalance -address Helen
Balance of "Helen": 1

$ blockchain_go getbalance -address Rachel
Balance of "Rachel": 3

看起來(lái)沒(méi)問(wèn)題!現(xiàn)在,來(lái)測(cè)試一些失敗的情況:

$ blockchain_go send -from Pedro -to Ivan -amount 5
panic: ERROR: Not enough funds

$ blockchain_go getbalance -address Pedro
Balance of "Pedro": 4

$ blockchain_go getbalance -address Ivan
Balance of "Ivan": 2
總結(jié)

雖然不容易,但是現(xiàn)在終于實(shí)現(xiàn)交易了!不過(guò),我們依然缺少了一些像比特幣那樣的一些關(guān)鍵特性:

地址(address)。我們還沒(méi)有基于私鑰(private key)的真實(shí)地址。

獎(jiǎng)勵(lì)(reward)?,F(xiàn)在挖礦是肯定無(wú)法盈利的!

UTXO 集。獲取余額需要掃描整個(gè)區(qū)塊鏈,而當(dāng)區(qū)塊非常多的時(shí)候,這么做就會(huì)花費(fèi)很長(zhǎng)時(shí)間。并且,如果我們想要驗(yàn)證后續(xù)交易,也需要花費(fèi)很長(zhǎng)時(shí)間。而 UTXO 集就是為了解決這些問(wèn)題,加快交易相關(guān)的操作。

內(nèi)存池(mempool)。在交易被打包到塊之前,這些交易被存儲(chǔ)在內(nèi)存池里面。在我們目前的實(shí)現(xiàn)中,一個(gè)塊僅僅包含一筆交易,這是相當(dāng)?shù)托У摹?/p>

鏈接:

Full source codes

Transaction

Merkle tree

Coinbase

本文源代碼:part_4

原文鏈接:Building Blockchain in Go. Part 4: Transactions 1

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/23940.html

相關(guān)文章

  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 7: 網(wǎng)絡(luò)

    摘要:盡管我們不會(huì)實(shí)現(xiàn)一個(gè)真實(shí)的網(wǎng)絡(luò),但是我們會(huì)實(shí)現(xiàn)一個(gè)真是,也是比特幣最常見(jiàn)最重要的用戶場(chǎng)景。不過(guò),這并不是處于禮貌用于找到一個(gè)更長(zhǎng)的區(qū)塊鏈。意為給我看一下你有什么區(qū)塊在比特幣中,這會(huì)更加復(fù)雜。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHu...

    lingdududu 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 6: 交易(2)

    摘要:到目前為止,我們幾乎已經(jīng)實(shí)現(xiàn)了一個(gè)區(qū)塊鏈數(shù)據(jù)庫(kù)的所有元素。使用根據(jù)在區(qū)塊鏈中找到一筆交易。是一個(gè)比特幣輕節(jié)點(diǎn),它不需要下載整個(gè)區(qū)塊鏈,也不需要驗(yàn)證區(qū)塊和交易。到目前為止,我們只是將一個(gè)塊里面的每筆交易哈希連接了起來(lái),將在上面應(yīng)用了算法。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果...

    spacewander 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 ---- Part 1: 基本原型

    摘要:在區(qū)塊鏈中,存儲(chǔ)有效信息的是區(qū)塊。存儲(chǔ)的是前一個(gè)塊的哈希。正是由于這個(gè)特性,才使得區(qū)塊鏈?zhǔn)前踩?。這樣的結(jié)構(gòu),能夠讓我們快速地獲取鏈上的最新塊,并且高效地通過(guò)哈希來(lái)檢索一個(gè)塊。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,也可以 clone GitHub 上的教程倉(cāng)...

    ZoomQuiet 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 3: 持久化和命令行接口

    摘要:引言到目前為止,我們已經(jīng)構(gòu)建了一個(gè)有工作量證明機(jī)制的區(qū)塊鏈。在今天的內(nèi)容中,我們會(huì)將區(qū)塊鏈持久化到一個(gè)數(shù)據(jù)庫(kù)中,然后會(huì)提供一個(gè)簡(jiǎn)單的命令行接口,用來(lái)完成一些與區(qū)塊鏈的交互操作。這同樣也意味著,一個(gè)也就是區(qū)塊鏈的一種標(biāo)識(shí)符。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼...

    felix0913 評(píng)論0 收藏0
  • Go 構(gòu)建一個(gè)區(qū)塊 -- Part 5: 地址

    摘要:比特幣地址這就是一個(gè)真實(shí)的比特幣地址。這是史上第一個(gè)比特幣地址,據(jù)說(shuō)屬于中本聰。當(dāng)你安裝一個(gè)錢包應(yīng)用,或是使用一個(gè)比特幣客戶端來(lái)生成一個(gè)新地址時(shí),它就會(huì)為你生成一對(duì)密鑰。在被放入到一個(gè)塊之前,必須要對(duì)每一筆交易進(jìn)行驗(yàn)證。 翻譯的系列文章我已經(jīng)放到了 GitHub 上:blockchain-tutorial,后續(xù)如有更新都會(huì)在 GitHub 上,可能就不在這里同步了。如果想直接運(yùn)行代碼,...

    KunMinX 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<