python區塊鏈簡易版交易實現示例
說明
本文根據https://github.com/liuchengxu/blockchain-tutorial的內容,用python實現的,但根據個人的理解進行瞭一些修改,大量引用瞭原文的內容。文章末尾有"本節完整源碼實現地址"。
引言
交易(transaction)是比特幣的核心所在,而區塊鏈唯一的目的,也正是為瞭能夠安全可靠地存儲交易。在區塊鏈中,交易一旦被創建,就沒有任何人能夠再去修改或是刪除它。今天,我們將會開始實現交易。不過,由於交易是很大的話題,我會把它分為兩部分來講:在今天這個部分,我們會實現交易的基本框架。在第二部分,我們會繼續討論它的一些細節。
由於比特幣采用的是 UTXO 模型,並非賬戶模型,並不直接存在“餘額”這個概念,餘額需要通過遍歷整個交易歷史得來。
比特幣交易
點擊 這裡 在 https://www.blockchain.com/explorer 查看下圖中的交易信息。
一筆交易由一些輸入(input)和輸出(output)組合而來:
class Transaction(object): def __init__(self, vins, vouts): self.txid = '' self.vins = vins self.vouts = vouts
對於每一筆新的交易,它的輸入會引用(reference)之前一筆交易的輸出(這裡有個例外,coinbase 交易),引用就是花費的意思。所謂引用之前的一個輸出,也就是將之前的一個輸出包含在另一筆交易的輸入當中,就是花費之前的交易輸出。交易的輸出,就是幣實際存儲的地方。下面的圖示闡釋瞭交易之間的互相關聯:
註意:
- 有一些輸出並沒有被關聯到某個輸入上
- 一筆交易的輸入可以引用之前多筆交易的輸出
- 一個輸入必須引用一個輸出
貫穿本文,我們將會使用像“錢(money)”,“幣(coin)”,“花費(spend)”,“發送(send)”,“賬戶(account)” 等等這樣的詞。但是在比特幣中,其實並不存在這樣的概念。交易僅僅是通過一個腳本(script)來鎖定(lock)一些值(value),而這些值隻可以被鎖定它們的人解鎖(unlock)。
每一筆比特幣交易都會創造輸出,輸出都會被區塊鏈記錄下來。給某個人發送比特幣,實際上意味著創造新的 UTXO 並註冊到那個人的地址,可以為他所用。
交易輸出
先從輸出(output)開始:
class TXOutput(object): def __init__(self, value, script_pub_key): self.value = value self.script_pub_key = script_pub_key
輸出主要包含兩部分:
一定量的比特幣(Value)
一個鎖定腳本(script_pub_key),要花這筆錢,必須要解鎖該腳本。
實際上,正是輸出裡面存儲瞭“幣”(註意,也就是上面的 Value 字段)。而這裡的存儲,指的是用一個數學難題對輸出進行鎖定,這個難題被存儲在 script_pub_key 裡面。在內部,比特幣使用瞭一個叫做 Script 的腳本語言,用它來定義鎖定和解鎖輸出的邏輯。雖然這個語言相當的原始(這是為瞭避免潛在的黑客攻擊和濫用而有意為之),並不復雜,但是我們也並不會在這裡討論它的細節。你可以在這裡 找到詳細解釋。
在比特幣中,value 字段存儲的是 satoshi 的數量,而不是 BTC 的數量。一個 satoshi 等於一億分之一的 BTC(0.00000001 BTC),這也是比特幣裡面最小的貨幣單位(就像是 1 分的硬幣)。
由於還沒有實現地址(address),所以目前我們會避免涉及邏輯相關的完整腳本。script_pub_key 將會存儲一個任意的字符串(用戶定義的錢包地址)。
順便說一下,有瞭一個這樣的腳本語言,也意味著比特幣其實也可以作為一個智能合約平臺。
關於輸出,非常重要的一點是:它們是不可再分的(indivisible)。也就是說,你無法僅引用它的其中某一部分。要麼不用,如果要用,必須一次性用完。當一個新的交易中引用瞭某個輸出,那麼這個輸出必須被全部花費。如果它的值比需要的值大,那麼就會產生一個找零,找零會返還給發送方。這跟現實世界的場景十分類似,當你想要支付的時候,如果一個東西值 1 美元,而你給瞭一個 5 美元的紙幣,那麼你會得到一個 4 美元的找零。
發送幣
現在,我們想要給其他人發送一些幣。為此,我們需要創建一筆新的交易,將它放到一個塊裡,然後挖出這個塊。之前我們隻實現瞭 coinbase 交易(這是一種特殊的交易),現在我們需要一種通用的普通交易:
def new_transaction(self, from_addr, to_addr, amount): inputs = [] outputs = [] acc, valid_outpus = self._find_spendable_outputs(from_addr, amount) if acc < amount: raise NotEnoughAmountError(u'not enough coin') for txid, outs in valid_outpus.items(): for out in outs: out_index = out[0] input = TXInput(txid, out_index, from_addr) inputs.append(input) output = TXOutput(amount, to_addr) outputs.append(output) if acc > amount: # a change outputs.append(TXOutput(acc - amount, from_addr)) tx = Transaction(inputs, outputs) tx.set_id() return tx
在創建新的輸出前,我們首先必須找到所有的未花費輸出,並且確保它們有足夠的價值(value),這就是 _find_spendable_outputs 方法要做的事情。隨後,對於每個找到的輸出,會創建一個引用該輸出的輸入。接下來,我們創建兩個輸出:
一個由接收者地址鎖定。這是給其他地址實際轉移的幣。
一個由發送者地址鎖定。這是一個找零。隻有當未花費輸出超過新交易所需時產生。記住:輸出是不可再分的。
這個方法對所有的未花費交易進行迭代,並對它的值進行累加。當累加值大於或等於我們想要傳送的值時,它就會停止並返回累加值,同時返回的還有通過交易 ID 進行分組的輸出索引。我們隻需取出足夠支付的錢就夠瞭。
現在我們修改add_block方法:
def add_block(self, transactions): """ add a block to block_chain """ last_block = self.get_last_block() prev_hash = last_block.get_header_hash() height = last_block.block_header.height + 1 block_header = BlockHeader('', height, prev_hash) block = Block(block_header, transactions) block.mine() block.set_header_hash() self.db.create(block.block_header.hash, block.serialize()) last_hash = block.block_header.hash self.set_last_hash(last_hash)
最後,讓我們來實現 send 方法:
def send(bc, from_addr, to_addr, amount): bc = BlockChain() tx = bc.new_transaction(from_addr, to_addr, amount) bc.add_block([tx]) print('send %d from %s to %s' %(amount, from_addr, to_addr))
發送幣意味著創建新的交易,並通過挖出新塊的方式將交易打包到區塊鏈中。不過,比特幣並不是一連串立刻完成這些事情(雖然我們目前的實現是這麼做的)。相反,它會將所有新的交易放到一個內存池中(mempool),然後當礦工準備挖出一個新塊時,它就從內存池中取出所有交易,創建一個候選塊。隻有當包含這些交易的塊被挖出來,並添加到區塊鏈以後,裡面的交易才開始確認。
讓我們來檢查一下發送幣是否能工作:
首先我們要執行main.py完成創世塊的構建
$ python3 main.py Mining a new block Found nonce == 17ash_hex == 01ded3ff2872093f2eefcd7b8b5b264e96996f31f933e6636db034b4151b61aa Block(_block_header=BlockHeader(timestamp='1551086196.4749706', hash_merkle_root='', prev_block_hash='', hash='ce93f6e1a2f7dec3a538e8b6397e4b8eba59bace2e7ac08f82875447d2660173', nonce=None, height=0)) Block(_block_header=BlockHeader(timestamp='1551086196.6248493', hash_merkle_root='', prev_block_hash='', hash='d5ecad2ed10a978e2f280e62d6a25ce4def6cdfc66ac9dcd124c24c5a4b9ac07', nonce=17, height=1))
$ python3 cli.py send --from zhangsanaddr --to lisiaddr --amount 10 Found nonce == 0ash_hex == 08c67066d0c7fc8d2ef80076e91626ff05999046ae0248e1971b99a30541518b send 10 from zhangsanaddr to lisiaddr
餘額查看
def get_balance(bc, addr): balance = 0 utxos = bc.find_UTXO(addr) for utxo in utxos: balance += utxo.value print('%s balance is %d' %(addr, balance))
find_UTXO方法找出所有的UTXO,實現如下:
$ python3 cli.py balance zhangsanaddr zhangsanaddr balance is 980
先利用_find_unspent_transactions找出所有的未花費交易,並判斷是否是當前地址可以解鎖的,就找出瞭所有的UTXO。
測試一下效果:
$ python3 cli.py balance zhangsanaddr zhangsanaddr balance is 980
總結
雖然不容易,但是現在終於實現交易瞭!不過,我們依然缺少瞭一些像比特幣那樣的一些關鍵特性:
地址(address)。我們還沒有基於私鑰(private key)的真實地址。
獎勵(reward)。現在挖礦是肯定無法盈利的!
UTXO 集。獲取餘額需要掃描整個區塊鏈,而當區塊非常多的時候,這麼做就會花費很長時間。並且,如果我們想要驗證後續交易,也需要花費很長時間。而 UTXO 集就是為瞭解決這些問題,加快交易相關的操作。
內存池(mempool)。在交易被打包到塊之前,這些交易被存儲在內存池裡面。在我們目前的實現中,一個塊僅僅包含一筆交易,這是相當低效的。
參考:
[1] https://github.com/liuchengxu/blockchain-tutorial/blob/master/content/part-4/transactions-1.md
[2] 本節完整實現源碼
以上就是python區塊鏈簡易版交易實現示例的詳細內容,更多關於python區塊鏈交易的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- python區塊鏈基本原型簡版實現示例
- python區塊鏈簡易版交易完善挖礦獎勵示例
- python區塊鏈實現簡版工作量證明
- python區塊鏈持久化和命令行接口實現簡版
- python區塊鏈實現簡版網絡