python區(qū)塊鏈簡易版交易實(shí)現(xiàn)示例
說明
本文根據(jù)https://github.com/liuchengxu/blockchain-tutorial的內(nèi)容,用python實(shí)現(xiàn)的,但根據(jù)個(gè)人的理解進(jìn)行了一些修改,大量引用了原文的內(nèi)容。文章末尾有"本節(jié)完整源碼實(shí)現(xiàn)地址"。
引言
交易(transaction)是比特幣的核心所在,而區(qū)塊鏈唯一的目的,也正是為了能夠安全可靠地存儲(chǔ)交易。在區(qū)塊鏈中,交易一旦被創(chuàng)建,就沒有任何人能夠再去修改或是刪除它。今天,我們將會(huì)開始實(shí)現(xiàn)交易。不過,由于交易是很大的話題,我會(huì)把它分為兩部分來講:在今天這個(gè)部分,我們會(huì)實(shí)現(xiàn)交易的基本框架。在第二部分,我們會(huì)繼續(xù)討論它的一些細(xì)節(jié)。
由于比特幣采用的是 UTXO 模型,并非賬戶模型,并不直接存在“余額”這個(gè)概念,余額需要通過遍歷整個(gè)交易歷史得來。
比特幣交易
點(diǎn)擊 這里 在 https://www.blockchain.com/explorer 查看下圖中的交易信息。
一筆交易由一些輸入(input)和輸出(output)組合而來:
class Transaction(object): def __init__(self, vins, vouts): self.txid = '' self.vins = vins self.vouts = vouts
對(duì)于每一筆新的交易,它的輸入會(huì)引用(reference)之前一筆交易的輸出(這里有個(gè)例外,coinbase 交易),引用就是花費(fèi)的意思。所謂引用之前的一個(gè)輸出,也就是將之前的一個(gè)輸出包含在另一筆交易的輸入當(dāng)中,就是花費(fèi)之前的交易輸出。交易的輸出,就是幣實(shí)際存儲(chǔ)的地方。下面的圖示闡釋了交易之間的互相關(guān)聯(lián):
注意:
- 有一些輸出并沒有被關(guān)聯(lián)到某個(gè)輸入上
- 一筆交易的輸入可以引用之前多筆交易的輸出
- 一個(gè)輸入必須引用一個(gè)輸出
貫穿本文,我們將會(huì)使用像“錢(money)”,“幣(coin)”,“花費(fèi)(spend)”,“發(fā)送(send)”,“賬戶(account)” 等等這樣的詞。但是在比特幣中,其實(shí)并不存在這樣的概念。交易僅僅是通過一個(gè)腳本(script)來鎖定(lock)一些值(value),而這些值只可以被鎖定它們的人解鎖(unlock)。
每一筆比特幣交易都會(huì)創(chuàng)造輸出,輸出都會(huì)被區(qū)塊鏈記錄下來。給某個(gè)人發(fā)送比特幣,實(shí)際上意味著創(chuàng)造新的 UTXO 并注冊(cè)到那個(gè)人的地址,可以為他所用。
交易輸出
先從輸出(output)開始:
class TXOutput(object): def __init__(self, value, script_pub_key): self.value = value self.script_pub_key = script_pub_key
輸出主要包含兩部分:
一定量的比特幣(Value)
一個(gè)鎖定腳本(script_pub_key),要花這筆錢,必須要解鎖該腳本。
實(shí)際上,正是輸出里面存儲(chǔ)了“幣”(注意,也就是上面的 Value 字段)。而這里的存儲(chǔ),指的是用一個(gè)數(shù)學(xué)難題對(duì)輸出進(jìn)行鎖定,這個(gè)難題被存儲(chǔ)在 script_pub_key 里面。在內(nèi)部,比特幣使用了一個(gè)叫做 Script 的腳本語言,用它來定義鎖定和解鎖輸出的邏輯。雖然這個(gè)語言相當(dāng)?shù)脑迹ㄟ@是為了避免潛在的黑客攻擊和濫用而有意為之),并不復(fù)雜,但是我們也并不會(huì)在這里討論它的細(xì)節(jié)。你可以在這里 找到詳細(xì)解釋。
在比特幣中,value 字段存儲(chǔ)的是 satoshi 的數(shù)量,而不是 BTC 的數(shù)量。一個(gè) satoshi 等于一億分之一的 BTC(0.00000001 BTC),這也是比特幣里面最小的貨幣單位(就像是 1 分的硬幣)。
由于還沒有實(shí)現(xiàn)地址(address),所以目前我們會(huì)避免涉及邏輯相關(guān)的完整腳本。script_pub_key 將會(huì)存儲(chǔ)一個(gè)任意的字符串(用戶定義的錢包地址)。
順便說一下,有了一個(gè)這樣的腳本語言,也意味著比特幣其實(shí)也可以作為一個(gè)智能合約平臺(tái)。
關(guān)于輸出,非常重要的一點(diǎn)是:它們是不可再分的(indivisible)。也就是說,你無法僅引用它的其中某一部分。要么不用,如果要用,必須一次性用完。當(dāng)一個(gè)新的交易中引用了某個(gè)輸出,那么這個(gè)輸出必須被全部花費(fèi)。如果它的值比需要的值大,那么就會(huì)產(chǎn)生一個(gè)找零,找零會(huì)返還給發(fā)送方。這跟現(xiàn)實(shí)世界的場景十分類似,當(dāng)你想要支付的時(shí)候,如果一個(gè)東西值 1 美元,而你給了一個(gè) 5 美元的紙幣,那么你會(huì)得到一個(gè) 4 美元的找零。
發(fā)送幣
現(xiàn)在,我們想要給其他人發(fā)送一些幣。為此,我們需要?jiǎng)?chuàng)建一筆新的交易,將它放到一個(gè)塊里,然后挖出這個(gè)塊。之前我們只實(shí)現(xiàn)了 coinbase 交易(這是一種特殊的交易),現(xiàn)在我們需要一種通用的普通交易:
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
在創(chuàng)建新的輸出前,我們首先必須找到所有的未花費(fèi)輸出,并且確保它們有足夠的價(jià)值(value),這就是 _find_spendable_outputs 方法要做的事情。隨后,對(duì)于每個(gè)找到的輸出,會(huì)創(chuàng)建一個(gè)引用該輸出的輸入。接下來,我們創(chuàng)建兩個(gè)輸出:
一個(gè)由接收者地址鎖定。這是給其他地址實(shí)際轉(zhuǎn)移的幣。
一個(gè)由發(fā)送者地址鎖定。這是一個(gè)找零。只有當(dāng)未花費(fèi)輸出超過新交易所需時(shí)產(chǎn)生。記住:輸出是不可再分的。
這個(gè)方法對(duì)所有的未花費(fèi)交易進(jìn)行迭代,并對(duì)它的值進(jìn)行累加。當(dāng)累加值大于或等于我們想要傳送的值時(shí),它就會(huì)停止并返回累加值,同時(shí)返回的還有通過交易 ID 進(jìn)行分組的輸出索引。我們只需取出足夠支付的錢就夠了。
現(xiàn)在我們修改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)
最后,讓我們來實(shí)現(xiàn) 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))
發(fā)送幣意味著創(chuàng)建新的交易,并通過挖出新塊的方式將交易打包到區(qū)塊鏈中。不過,比特幣并不是一連串立刻完成這些事情(雖然我們目前的實(shí)現(xiàn)是這么做的)。相反,它會(huì)將所有新的交易放到一個(gè)內(nèi)存池中(mempool),然后當(dāng)?shù)V工準(zhǔn)備挖出一個(gè)新塊時(shí),它就從內(nèi)存池中取出所有交易,創(chuàng)建一個(gè)候選塊。只有當(dāng)包含這些交易的塊被挖出來,并添加到區(qū)塊鏈以后,里面的交易才開始確認(rèn)。
讓我們來檢查一下發(fā)送幣是否能工作:
首先我們要執(zhí)行main.py完成創(chuàng)世塊的構(gòu)建
$ 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,實(shí)現(xiàn)如下:
$ python3 cli.py balance zhangsanaddr zhangsanaddr balance is 980
先利用_find_unspent_transactions找出所有的未花費(fèi)交易,并判斷是否是當(dāng)前地址可以解鎖的,就找出了所有的UTXO。
測(cè)試一下效果:
$ python3 cli.py balance zhangsanaddr zhangsanaddr balance is 980
總結(jié)
雖然不容易,但是現(xiàn)在終于實(shí)現(xiàn)交易了!不過,我們依然缺少了一些像比特幣那樣的一些關(guān)鍵特性:
地址(address)。我們還沒有基于私鑰(private key)的真實(shí)地址。
獎(jiǎng)勵(lì)(reward)?,F(xiàn)在挖礦是肯定無法盈利的!
UTXO 集。獲取余額需要掃描整個(gè)區(qū)塊鏈,而當(dāng)區(qū)塊非常多的時(shí)候,這么做就會(huì)花費(fèi)很長時(shí)間。并且,如果我們想要驗(yàn)證后續(xù)交易,也需要花費(fèi)很長時(shí)間。而 UTXO 集就是為了解決這些問題,加快交易相關(guān)的操作。
內(nèi)存池(mempool)。在交易被打包到塊之前,這些交易被存儲(chǔ)在內(nèi)存池里面。在我們目前的實(shí)現(xiàn)中,一個(gè)塊僅僅包含一筆交易,這是相當(dāng)?shù)托У摹?/p>
參考:
[1] https://github.com/liuchengxu/blockchain-tutorial/blob/master/content/part-4/transactions-1.md
以上就是python區(qū)塊鏈簡易版交易實(shí)現(xiàn)示例的詳細(xì)內(nèi)容,更多關(guān)于python區(qū)塊鏈交易的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python數(shù)據(jù)標(biāo)準(zhǔn)化的實(shí)例分析
在本篇文章里小編給大家整理了關(guān)于Python數(shù)據(jù)標(biāo)準(zhǔn)化的實(shí)例內(nèi)容,有需要的朋友們可以測(cè)試學(xué)習(xí)下。2021-08-08Python自動(dòng)化實(shí)戰(zhàn)之接口請(qǐng)求的實(shí)現(xiàn)
本文為大家重點(diǎn)介紹如何通過 python 編碼來實(shí)現(xiàn)我們的接口測(cè)試以及通過Pycharm的實(shí)際應(yīng)用編寫一個(gè)簡單接口測(cè)試,感興趣的可以了解一下2022-05-05使用Python對(duì)MySQL數(shù)據(jù)操作
本文介紹Python3使用PyMySQL連接數(shù)據(jù)庫,并實(shí)現(xiàn)簡單的增刪改查。具有很好的參考價(jià)值。下面跟著小編一起來看下吧2017-04-04使用BeeWare實(shí)現(xiàn)iOS調(diào)用Python方式
這篇文章主要介紹了使用BeeWare實(shí)現(xiàn)iOS調(diào)用Python方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Django與AJAX實(shí)現(xiàn)網(wǎng)頁動(dòng)態(tài)數(shù)據(jù)顯示的示例代碼
這篇文章主要介紹了Django與AJAX實(shí)現(xiàn)網(wǎng)頁動(dòng)態(tài)數(shù)據(jù)顯示的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02