1.前言
上一篇我知道了區(qū)塊鏈如何持久化存儲(chǔ),接下來我們將開始實(shí)現(xiàn)區(qū)塊鏈中交易是如何產(chǎn)生的如何防止被串改物舒,如何在網(wǎng)絡(luò)中分布式記賬。我們將交易分成兩部分:交易實(shí)現(xiàn)一般機(jī)制,后面將實(shí)現(xiàn)網(wǎng)絡(luò)坊萝、獎(jiǎng)勵(lì)機(jī)制等隶症。
2.知識(shí)準(zhǔn)備
知識(shí)點(diǎn) | 學(xué)習(xí)網(wǎng)頁 | 特性 |
---|---|---|
bitcoin交易 | 交易 | 不可串改 |
Coinbase | 創(chuàng)世塊交易信息 | 創(chuàng)世塊交易 |
3.基本交易過程
在區(qū)塊鏈中每次發(fā)生交易,用戶需要將新的交易記錄寫到比特幣區(qū)塊鏈網(wǎng)絡(luò)中龙屉,等待網(wǎng)絡(luò)確認(rèn)為交易完成呐粘。每個(gè)交易包括了一些輸入和一些輸出,未經(jīng)使用的交易的輸出(Transaction Outputs,UTXO)可以被新的交易引用作為合法輸入转捕,被使用過的交易的輸出(Spent Transaction Outputs作岖,STO)則無法被引用作為合法輸入。
注意:這里的比特幣交易與我們傳統(tǒng)的金錢付款交易是不同的五芝,并沒有賬號痘儡、沒有余額等。詳情參考
4.代碼實(shí)現(xiàn)
交易:
//交易事物
type Transaction struct {
ID []byte //交易hash
Vin []TXInput //事物輸入
Vout []TXOutput //事物輸出
}
流程圖:
注意:
- 有些輸出和輸入無關(guān)枢步。
- 在一個(gè)交易中沉删,投入可以參考多個(gè)交易的輸出。
- 輸入必須引用輸出醉途。
比特幣中沒有這樣的概念矾瑰。事務(wù)只是用腳本鎖定值,只能由鎖定它的人解鎖隘擎。
交易輸出:
//一個(gè)事物輸出
type TXOutput struct {
Value int //值
ScriptPubKey string //解鎖腳本key
}
實(shí)際上殴穴,它是存儲(chǔ)“硬幣”的輸出(注意Value上面的字段)。而存儲(chǔ)意味著用一個(gè)拼圖鎖定它們嵌屎,這是存儲(chǔ)在ScriptPubKey推正。在內(nèi)部,比特幣使用稱為腳本的腳本語言宝惰,用于定義輸出鎖定和解鎖邏輯。這個(gè)語言很原始(這是故意的再沧,以避免可能的黑客和濫用)尼夺,但我們不會(huì)詳細(xì)討論它。你可以在本章知識(shí)點(diǎn)bitcoin交易詳細(xì)解釋。
在比特幣中淤堵,價(jià)值領(lǐng)域存儲(chǔ)satoshis的數(shù)量寝衫,而不是BTC的數(shù)量。甲聰是100000000分之1一個(gè)比特幣(0.00000001 BTC)的拐邪,因此慰毅,這是貨幣的比特幣的最小單位(如百分比)。
由于我們沒有實(shí)現(xiàn)地址扎阶,現(xiàn)在我們將避免整個(gè)腳本相關(guān)的邏輯汹胃。ScriptPubKey將存儲(chǔ)任意字符串(用戶定義的錢包地址)。
交易輸入:
//一個(gè)事物輸入
type TXInput struct {
Txid []byte //交易ID的hash
Vout int //交易輸出
ScriptSig string //解鎖腳本
}
如前所述东臀,輸入引用前一個(gè)輸出:Txid存儲(chǔ)此類事務(wù)的ID着饥,并Vout在事務(wù)中存儲(chǔ)輸出的索引。ScriptSig是一個(gè)提供數(shù)據(jù)以在輸出中使用的腳本ScriptPubKey惰赋。如果數(shù)據(jù)是正確的宰掉,輸出可以被解鎖,并且它的值可以被用來產(chǎn)生新的輸出; 如果不正確赁濒,則輸出中不能引用輸出轨奄。這是保證用戶不能花錢屬于其他人的硬幣的機(jī)制。
同樣拒炎,由于我們還沒有實(shí)現(xiàn)地址戚绕,ScriptSig因此將只存儲(chǔ)任意用戶定義的錢包地址。我們將在下一篇文章中實(shí)現(xiàn)公鑰和簽名檢查枝冀。
我們總結(jié)一下舞丛。產(chǎn)出是儲(chǔ)存“硬幣”的地方。每個(gè)輸出都帶有一個(gè)解鎖腳本果漾,它決定了解鎖輸出的邏輯球切。每個(gè)新事務(wù)都必須至少有一個(gè)輸入和輸出。輸入引用前一個(gè)事務(wù)的輸出绒障,并提供ScriptSig輸出的解鎖腳本中使用的數(shù)據(jù)(字段)吨凑,以解除鎖定并使用其值創(chuàng)建新的輸出。
coinbase交易:
上面我們知道輸入?yún)⒖驾敵鲞壿嫽瑁敵鲇謪⒖剂溯斎胪叶邸_@樣就產(chǎn)生了我們常見的一個(gè)問題:先有雞還是先有蛋呢?
當(dāng)?shù)V工開始挖礦時(shí),它會(huì)添加一個(gè)coinbase交易庐镐。coinbase交易是一種特殊類型的交易恩商,不需要以前存在的輸出。它無處不在地創(chuàng)造產(chǎn)出(即“硬幣”)必逆。沒有雞的雞蛋怠堪。這是礦工獲得開采新礦區(qū)的獎(jiǎng)勵(lì)揽乱。
如您所知,區(qū)塊鏈開始處有起始塊粟矿。這個(gè)區(qū)塊在區(qū)塊鏈中產(chǎn)生了第一個(gè)輸出凰棉。由于沒有以前的交易并且沒有這樣的輸出,因此不需要先前的輸出陌粹。
我們來創(chuàng)建一個(gè)coinbase事務(wù):
//創(chuàng)建Coinbase事物
func NewCoinbaseTX(to,data string) *Transaction {
if data==""{
data=fmt.Sprintf("Reward to '%s'",to)
}
//這里Vout-1 data:const genesisCoinbaseData = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"
txin :=TXInput{[]byte{},-1,data}
//subsidy是獎(jiǎng)勵(lì)的金額
txout := TXOutput{subsidy, to}
tx := Transaction{nil, []TXInput{txin}, []TXOutput{txout}}
//設(shè)置32位交易hash
tx.SetID()
return &tx
}
//設(shè)置交易ID hash
func (tx *Transaction) SetID(){
var encoded bytes.Buffer
var hash [32]byte //32位的hash字節(jié)
enc := gob.NewEncoder(&encoded)
err := enc.Encode(tx)
if err != nil {
log.Panic(err)
}
//將交易信息sha256
hash = sha256.Sum256(encoded.Bytes())
//生成hash
tx.ID = hash[:]
}
一個(gè)coinbase交易只有一個(gè)輸入撒犀。在我們的實(shí)現(xiàn)中它Txid是空的,Vout等于-1掏秩。另外或舞,coinbase事務(wù)不會(huì)存儲(chǔ)腳本ScriptSig。相反哗讥,任意數(shù)據(jù)存儲(chǔ)在那里嚷那。
在比特幣中,第一個(gè)coinbase交易包含以下信息:“The Times 03/Jan/2009 Chancellor on brink of second bailout for banks”杆煞。查看知識(shí)點(diǎn)Coinbase可以知道魏宽。
subsidy是獎(jiǎng)勵(lì)的金額。在比特幣中决乎,這個(gè)數(shù)字沒有存儲(chǔ)在任何地方队询,只根據(jù)塊的總數(shù)進(jìn)行計(jì)算:塊的數(shù)量除以210000。挖掘創(chuàng)世紀(jì)塊產(chǎn)生50 BTC构诚,每210000塊獎(jiǎng)勵(lì)減半蚌斩。在我們的實(shí)現(xiàn)中,我們會(huì)將獎(jiǎng)勵(lì)作為常量存儲(chǔ)(至少現(xiàn)在是??)范嘱。
在區(qū)塊鏈中存儲(chǔ)交易:
我們將開始區(qū)塊里面的data改成transactions
//區(qū)塊結(jié)構(gòu)
type Block struct {
Hash []byte //hase值
Transactions []*Transaction//交易數(shù)據(jù)
PrevBlockHash []byte //存儲(chǔ)前一個(gè)區(qū)塊的Hase值
Timestamp int64 //生成區(qū)塊的時(shí)間
Nonce int //工作量證明算法的計(jì)數(shù)器
}
對應(yīng)的NewBlock送膳,NewGensisBlock也應(yīng)該修改:
//生成一個(gè)新的區(qū)塊方法
func NewBlock(transactions []*Transaction, prevBlockHash []byte) *Block{
//GO語言給Block賦值{}里面屬性順序可以打亂,但必須制定元素 如{Timestamp:time.Now().Unix()...}
block := &Block{Timestamp:time.Now().Unix(), Transactions:transactions, PrevBlockHash:prevBlockHash, Hash:[]byte{},Nonce:0}
//工作證明
pow :=NewProofOfWork(block)
//工作量證明返回計(jì)數(shù)器和hash
nonce, hash := pow.Run()
block.Hash = hash[:]
block.Nonce = nonce
return block
}
//創(chuàng)建并返回創(chuàng)世紀(jì)Block
func NewGenesisBlock(coinbase *Transaction) *Block {
return NewBlock([]*Transaction{coinbase}, []byte{})
}
blockchain:
/ 創(chuàng)新一個(gè)新的區(qū)塊數(shù)據(jù)
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))
if err != nil {
log.Panic(err)
}
err = b.Put(genesis.Hash, genesis.Serialize())
...
}
這里,函數(shù)將獲得一個(gè)地址丑蛤,該地址將獲得挖掘創(chuàng)世塊的獎(jiǎng)勵(lì)叠聋。(我們這里獎(jiǎng)勵(lì)為10)
工作量證明:
Proof-of-Work算法必須考慮存儲(chǔ)在塊中的事務(wù),以保證區(qū)塊鏈作為事務(wù)存儲(chǔ)的一致性和可靠性受裹。所以現(xiàn)在我們必須修改ProofOfWork.prepareData方法:
//將區(qū)塊體里面的數(shù)據(jù)轉(zhuǎn)換成一個(gè)字節(jié)碼數(shù)組碌补,為下一個(gè)區(qū)塊準(zhǔn)備數(shù)據(jù)
func (pow *ProofOfWork) prepareData(nonce int) []byte {
//注意一定要將原始數(shù)據(jù)轉(zhuǎn)換成[]byte,不能直接從字符串轉(zhuǎn)
data := bytes.Join(
[][]byte{
pow.block.PrevBlockHash,
pow.block.HashTransactions(),
utils.IntToHex(pow.block.Timestamp),
utils.IntToHex(int64(targetBits)),
utils.IntToHex(int64(nonce)),
},
[]byte{},
)
return data
}
將data改成hashTransactions:
//返回塊狀事務(wù)的hash
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ù)的唯一表示的機(jī)制棉饶。我們希望塊中的所有事務(wù)都由一個(gè)散列唯一標(biāo)識(shí)厦章。為了達(dá)到這個(gè)目的,我們得到每個(gè)事務(wù)的哈希值照藻,連接它們袜啃,并獲得連接組合的哈希值。
比特幣使用更復(fù)雜的技術(shù):它將所有包含在塊中的事務(wù)表示為Merkle樹岩梳,并在Proof-of-Work系統(tǒng)中使用樹的根散列囊骤。這種方法允許快速檢查塊是否包含某個(gè)事務(wù)晃择,只有根散列并且不下載所有事務(wù)冀值。(后續(xù)會(huì)詳細(xì)講解Merkle算法)
好了我們現(xiàn)在嘗試一下CreateBlockchain:
編譯:
C:\go-worke\src\github.com\study-bitcoin-go>go build github.com/study-bitcoin-go
執(zhí)行createblockchain命令:
C:\go-worke\src\github.com\study-bitcoin-go>study-bitcoin-go createblockchain -address even
輸出:
Dig into mine 00000860adb64e2ca9c83d0f665f7ec3148ec9d32b64cb97f2481712c4d94d79
Done!
目前為止我們實(shí)現(xiàn)開采創(chuàng)世塊獎(jiǎng)勵(lì)也物。但我們要如何實(shí)現(xiàn)查詢余額呢?
未使用交易輸出
我們需要找到所有未使用的交易輸出(UTXO)列疗。未使用意味著這些輸出在任何輸入中都未被引用滑蚯。在上圖中,這些是:
- tx0抵栈,輸出1;
- tx1告材,輸出0;
- tx3,輸出0;
- tx4古劲,輸出0斥赋。
當(dāng)然,當(dāng)我們檢查余額時(shí)产艾,我們不需要所有這些疤剑,但只有那些可以用我們擁有的密鑰解鎖的(當(dāng)前我們沒有實(shí)現(xiàn)密鑰并且將使用用戶定義的地址)。首先闷堡,我們來定義輸入和輸出上的鎖定 - 解鎖方法:
//通過檢查地址是否啟動(dòng)了事務(wù)
func (in *TXInput) CanUnlockOutputWith(unlockingData string) bool {
return in.ScriptSig == unlockingData
}
//檢查輸出是否可以使用所提供的數(shù)據(jù)進(jìn)行解鎖
func (out *TXOutput) CanBeUnlockedWith(unlockingData string) bool {
return out.ScriptPubKey == unlockingData
}
這里我們只是比較腳本字段unlockingData隘膘。在我們實(shí)現(xiàn)基于私鑰的地址后,這些作品將在未來的文章中得到改進(jìn)杠览。
下一步 - 查找包含未使用產(chǎn)出的交易 - 相當(dāng)困難:
//查詢未處理的事務(wù)
func (bc *Blockchain) FindUnspentTransactions(address string) []Transaction {
var unspentTXs []Transaction //未處理的事務(wù)
spentTXOs := make(map[string][]int)
bci := bc.Iterator()
for {
block := bci.Next()
for _,tx := range block.Transactions {
txID := hex.EncodeToString(tx.ID) //交易ID轉(zhuǎn)換成string
Outputs:
for outIdx, out := range tx.Vout {
// Was the output spent?
if spentTXOs[txID] != nil {
//檢查一個(gè)輸出是否已經(jīng)在輸入中被引用
for _, spentOut := range spentTXOs[txID] {
if spentOut == outIdx {
continue Outputs
}
}
}
//由于交易存儲(chǔ)在塊中弯菊,因此我們必須檢查區(qū)塊鏈中的每個(gè)塊。我們從輸出開始:
if out.CanBeUnlockedWith(address) {
unspentTXs = append(unspentTXs, *tx)
}
}
//我們跳過輸入中引用的那些(它們的值被移到其他輸出踱阿,因此我們不能計(jì)數(shù)它們)管钳。
// 在檢查輸出之后,我們收集所有可能解鎖輸出的輸入软舌,并鎖定提供的地址(這不適用于coinbase事務(wù)才漆,因?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)
}
}
}
}
if len(block.PrevBlockHash) == 0 {
break
}
}
return unspentTXs
}
該函數(shù)返回一個(gè)包含未使用輸出的事務(wù)列表。為了計(jì)算余額葫隙,我們需要一個(gè)函數(shù)來處理事務(wù)并僅返回輸出:
//發(fā)現(xiàn)并返回所有未使用的事務(wù)輸出
func (bc *Blockchain) FindUTXO(address string) []TXOutput {
var UTXOs []TXOutput
//未使用輸出的事務(wù)列表
unspentTransactions := bc.FindUnspentTransactions(address)
//查找
for _, tx := range unspentTransactions {
for _, out := range tx.Vout {
///檢查輸出是否可以使用所提供的數(shù)據(jù)進(jìn)行解鎖
if out.CanBeUnlockedWith(address) {
UTXOs = append(UTXOs, out)
}
}
}
return UTXOs
}
客戶端cli getbalance命令:
//查詢余額
func (cli *CLI) getBalance(address string) {
bc := block.NewBlockchain(address)
defer block.Close(bc)
balance := 0
//查詢所有未經(jīng)使用的交易地址
UTXOs := bc.FindUTXO(address)
//算出未使用的交易地址的value和
for _, out := range UTXOs {
balance += out.Value
}
fmt.Printf("Balance of '%s': %d\n", address, balance)
}
賬戶余額是由賬戶地址鎖定的所有未使用的交易輸出值的總和栽烂。
現(xiàn)在我們檢查一下地址為even的錢:
重新編譯后
C:\go-worke\src\github.com\study-bitcoin-go>study-bitcoin-go getbalance -address even
Balance of 'even': 10
這就是第一筆錢,接下來我們需要實(shí)現(xiàn)給一個(gè)地址轉(zhuǎn)幣
現(xiàn)在,我們要發(fā)送一些硬幣給別人恋脚。為此腺办,我們需要?jiǎng)?chuàng)建一個(gè)新的事務(wù),將它放在一個(gè)塊中糟描,然后挖掘塊怀喉。到目前為止,我們只實(shí)現(xiàn)了coinbase交易(這是一種特殊類型的交易)船响,現(xiàn)在我們需要一個(gè)一般交易:
//創(chuàng)建一個(gè)新的未經(jīng)使用的交易輸出
func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction{
var inputs []TXInput
var outputs []TXOutput
//查詢發(fā)幣地址所未經(jīng)使用的交易輸出
acc, validOutputs := bc.FindSpendableOutputs(from, amount)
//判斷是否有那么多可花費(fèi)的幣
if acc < amount {
log.Panic("ERROR: Not enough funds")
}
// Build a list of inputs
for txid, outs := range validOutputs {
txID, err := hex.DecodeString(txid)
if err != nil {
log.Panic(err)
}
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)建新的輸出之前躬拢,我們首先必須找到所有未使用的輸出并確保它們存儲(chǔ)足夠的值躲履。這是什么FindSpendableOutputs方法。之后聊闯,為每個(gè)找到的輸出創(chuàng)建一個(gè)引用它的輸入工猜。接下來,我們創(chuàng)建兩個(gè)輸出:
- 一個(gè)與接收器地址鎖定的菱蔬。這是硬幣實(shí)際轉(zhuǎn)移到其他地址篷帅。
- 一個(gè)與發(fā)件人地址鎖定在一起。這是一個(gè)變化拴泌。只有在未使用的輸出持有比新事務(wù)所需的更多價(jià)值時(shí)才會(huì)創(chuàng)建魏身。記住:輸出是不可分割的蚪腐。
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
}
該方法迭代所有未使用的事務(wù)并累積其值箭昵。當(dāng)累計(jì)值大于或等于我們要轉(zhuǎn)移的金額時(shí),它停止并返回按事務(wù)ID分組的累計(jì)值和輸出索引回季。我們不想花更多的錢家制。
現(xiàn)在我們可以修改該Blockchain.MineBlock方法:
//開采區(qū)塊
func (bc *Blockchain) MineBlock(transactions []*Transaction) {
var lastHash []byte//最新一個(gè)hash
err := bc.db.View(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
lastHash = b.Get([]byte("l"))
return nil
})
if err != nil {
log.Panic(err)
}
//創(chuàng)造一個(gè)新區(qū)塊
newBlock := NewBlock(transactions, lastHash)
//修改"l"的hash
err = bc.db.Update(func(tx *bolt.Tx) error {
b := tx.Bucket([]byte(blocksBucket))
err := b.Put(newBlock.Hash, newBlock.Serialize())
if err != nil {
log.Panic(err)
}
err = b.Put([]byte("l"), newBlock.Hash)
if err != nil {
log.Panic(err)
}
bc.tip = newBlock.Hash
return nil
})
}
cli添加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)建一個(gè)交易并通過挖掘一個(gè)塊將其添加到區(qū)塊鏈。但比特幣不會(huì)立即做到這一點(diǎn)(就像我們一樣)茧跋。相反慰丛,它將所有新事務(wù)放入內(nèi)存池(或mempool)中,并且當(dāng)?shù)V工準(zhǔn)備開采塊時(shí)瘾杭,它將從mempool獲取所有事務(wù)并創(chuàng)建候選塊诅病。交易只有在包含它們的區(qū)塊被挖掘并添加到區(qū)塊鏈時(shí)才會(huì)被確認(rèn)。
現(xiàn)在我們來試試發(fā)幣:
C:\go-worke\src\github.com\study-bitcoin-go>study-bitcoin-go send -from even -to jim -amount 3
Mining the block containing "S4t?.U?????H??vWE???[?P?╔???"
Dig into mine 00000cde90398b754eebe6d7820dab6e6260ae724712b72706846ec6d331fe2c
Success!
分別查詢even粥烁、tom錢包:
C:\go-worke\src\github.com\study-bitcoin-go>study-bitcoin-go getbalance -address even
Balance of 'even': 7
C:\go-worke\src\github.com\study-bitcoin-go>study-bitcoin-go getbalance -address jim
Balance of 'jim': 3
好了目前我們實(shí)現(xiàn)了交易功能贤笆。缺少:
- 地址。我們還沒有真實(shí)的讨阻,基于私鑰的地址芥永。
- 獎(jiǎng)勵(lì)。采礦塊絕對沒有利潤钝吮!
- UTXO設(shè)置埋涧。達(dá)到平衡需要掃描整個(gè)區(qū)塊鏈,當(dāng)區(qū)塊數(shù)量很多時(shí)可能需要很長時(shí)間奇瘦。此外棘催,如果我們想驗(yàn)證以后的交易,可能需要很長時(shí)間耳标。UTXO集旨在解決這些問題并快速處理交易醇坝。
- 內(nèi)存池。這是交易在打包成塊之前存儲(chǔ)的地方次坡。在我們當(dāng)前的實(shí)現(xiàn)中呼猪,一個(gè)塊只包含一個(gè)事務(wù)扣泊,而且效率很低逞泄。
后續(xù)降會(huì)實(shí)現(xiàn)地址年枕、錢包崩泡、挖礦獎(jiǎng)勵(lì)蔗喂、網(wǎng)絡(luò)等颠蕴。
5.比特幣交易示例總結(jié)
交易 | 目的 | 輸入 | 輸出 | 簽名 | 差額 |
---|---|---|---|---|---|
T0 | A轉(zhuǎn)給B | 他人向A交易的輸出 | B賬號可以使用該交易 | A簽確認(rèn) | 輸入減去輸出哨免,為交易服務(wù)費(fèi) |
T1 | B轉(zhuǎn)給C | T0的輸出 | C賬戶可以使用該交易 | B簽名確認(rèn) | 輸入減去輸出亚情,為交易服務(wù)費(fèi) |
··· | X轉(zhuǎn)給Y | 他人向X交易的輸出 | Y賬戶可以使用該交易 | X簽名確認(rèn) | 輸入減去輸出摊腋,為交易服務(wù)費(fèi) |
這就是簡單交易流程沸版,我們以上代碼并沒有實(shí)現(xiàn)挖礦獎(jiǎng)勵(lì)。后續(xù)將實(shí)現(xiàn)錢包兴蒸,優(yōu)化查詢交易视粮,挖礦獎(jiǎng)勵(lì)、網(wǎng)絡(luò)橙凳。
資料
- 原文來源:https://jeiwan.cc/posts/building-blockchain-in-go-part-4/
- 本文源碼:https://github.com/Even521/study-bitcion-go/tree/part4
- java學(xué)習(xí):http://www.reibang.com/p/66c065018c7a
- 區(qū)塊鏈基礎(chǔ)視頻學(xué)習(xí):https://www.bilibili.com/video/av19620321/
- 區(qū)塊鏈測試demo:https://anders.com/blockchain/blockchain.html
- 區(qū)塊鏈Q(jìng)Q交流群:489512556