在以太坊上得到一個基本的智能合約是一個很簡單的事,只需google查詢「ERC20代幣教程」,你會發現有關如何作到這一點的大量信息。以編程方式與合約交互徹底是另外一回事,若是你是一個Python程序員,那麼教程就不多。因此寫這個Python中的以太坊智能合約開發指南。php
按個人統計對咱們來講幸運的是,2017年Web3.py的第4版發佈,這意味着如今比以往更容易運行python腳本並觀察區塊鏈上發生的神奇事情。像幽靈般的。java
Piper Merriam,Jason Carver以及其餘全部在Web3.py上努力工做以使咱們其餘人生活更輕鬆的人大聲呼喊,在Sempo,咱們正在使用以太坊來使災難般的響應更加透明,並且它是隻有Web3.py
才能真正實現。node
首先咱們進行設置,確保咱們安裝了相關的python庫。python
Python庫無處不在,但它們的用途是什麼?android
有不少與以太坊相關的python庫,可是當人們談論以太坊時,有兩個會出現不少:Web3.py和Pyethereum。乍一看,你應該使用哪個並不明顯。程序員
以太坊虛擬機(EVM)的Python實現。反過來,EVM是以太坊協議的一部分,它實際運行智能合約中的代碼並肯定其輸出。所以,若是你想在Python中運行以太坊節點,那麼Pyethereum是一個很好的起點。web
即便你很是高興在不運行本身的節點的狀況下運行智能合約,Pyethereum仍然是一個很好的庫,它包含許多功能,能夠執行有用的功能,例如從私鑰計算用戶的地址等等。mongodb
用於實際與以太坊區塊鏈交互的庫。咱們談論的事情包括在帳戶之間轉移以太網,發佈智能合約以及觸發附加現有智能合約的功能。它受到流行的JavaScript庫Web3.js的啓發,它將成爲咱們在本教程中使用的主庫。編程
起初我嘗試使用Python3.5版本,但在運行時我遇到了問題,顯然是由Python的類型提示形成的。基於Python3.6建立虛擬環境解決了這個問題,因此我建議你作一樣的事情。瀏覽器
繼續並pip-install web3 (確保你得到版本4)。
除非你喜歡花錢,不然你須要在以太坊測試網上使用錢包,例如Ropsten和其餘大量以太玩法。一個簡單的方法是下載Chrome的Metamask擴展,並從那裏建立一個新賬戶。
確保你還選擇左側的'Ropsten Test Net'。
即便你的現有錢包中包含真正的以太幣,我也強烈建議你爲開發目的建立一個新的錢包。咱們將使用私鑰作一些相對沒法預測的事,因此若是它們不當心變成公共主網絡的話就不會有問題(公私鑰?)
爲新建立的錢包獲取測試Ether很是簡單:只需訪問faucet.metamask.io並點擊「請求來自faucet的1個 以太」。對於咱們將要作的事情,這應該是充足的。
最後,由於咱們將在沒有託管咱們本身的節點的狀況下使用Ropsten TestNet
,咱們須要一個能夠鏈接Blockchain的供應商。Infura.io適用於此,因此去那裏建立一個免費賬戶。記下Ropsten TestNet的提供者URL(看起來像https://ropsten.infura.io/FE2...)。
使用Python來部署智能合約而不運行本身的節點是很是困難的,因此咱們將在這一步上作點兒手腳。對於許多智能合約用例,你只須要執行一次。
正如我以前提到的,有關如何部署ERC20合約的百萬條指南,所以咱們將部署一些不一樣的(而且更方便地更短)。
問:誰喜歡在互聯網上分享他們的意見?
你們都喜歡?
好答案。如下我稱之爲「Soap Box」肥皂盒的智能合約容許任何人向區塊鏈廣播他們想要的任何意見,在永恆的剩餘時間(給予或接受)能夠看到它。
可是有一個問題:只有支付了必要的0.02以太網費用的地址才能播出他們的意見。聽起來不太公平,但就這樣。
Remix,以太坊的在線代碼編輯器很是出色,所以在那裏建立一個新文件並粘貼如下代碼。它是用Solidity(Smart Contracts的編程語言)編寫的。若是代碼沒有太多意義並不重要,咱們將在稍後詳細介紹相關部分,但最終這是一個Python教程。
pragma solidity ^0.4.0; contract SoapBox { // Our 'dict' of addresses that are approved to share opinions //咱們批准分享意見的地址的「字典」 mapping (address => bool) approvedSoapboxer; string opinion; // Our event to announce an opinion on the blockchain //咱們的事件發佈對區塊鏈的意見 event OpinionBroadcast(address _soapboxer, string _opinion); // This is a constructor function, so its name has to match the contract //這是一個構造函數,因此它的名字必須與合約相匹配 function SoapBox() public { } // Because this function is 'payable' it will be called when ether is sent to the contract address. //由於這個函數是「支付」,因此當以太網被髮送到合約地址時將被調用。 function() public payable{ // msg is a special variable that contains information about the transaction // msg是一個特殊變量,包含有關交易的信息 if (msg.value > 20000000000000000) { //if the value sent greater than 0.02 ether (in Wei) //若是發送的值大於0.02 ether(在Wei中) // then add the sender's address to approvedSoapboxer //而後將發件人的地址添加到approvedSoapboxer approvedSoapboxer[msg.sender] = true; } } // Our read-only function that checks whether the specified address is approved to post opinions. //咱們的只讀函數,用於檢查指定地址是否被批准發佈意見。 function isApproved(address _soapboxer) public view returns (bool approved) { return approvedSoapboxer[_soapboxer]; } // Read-only function that returns the current opinion //返回當前意見的只讀函數 function getCurrentOpinion() public view returns(string) { return opinion; } //Our function that modifies the state on the blockchain //咱們的函數修改了區塊鏈上的狀態 function broadcastOpinion(string _opinion) public returns (bool success) { // Looking up the address of the sender will return false if the sender isn't approved //若是發件人未獲批准,查找發件人的地址將返回false if (approvedSoapboxer[msg.sender]) { opinion = _opinion; emit OpinionBroadcast(msg.sender, opinion); return true; } else { return false; } } }
如下是Metamask變得很是有用的地方:若是你點擊從新混音窗口右上角的「run」運行標籤並在「Environment」環境下拉列表中選擇「Injected Web3」注入的Web3,則「Account」賬戶下拉列表中應填充你的賬戶地址早在MetaMask中建立。若是沒有,只需刷新瀏覽器便可。
而後單擊「create」建立。Metamask應該彈出一個彈出窗口,要求你確認交易。若是沒有,只需打開Metamask擴展並在那裏執行:
你將在Remix控制檯底部收到一條消息,告知你合約的建立正在等待處理。單擊連接以在Etherscan上查看其狀態。若是刷新而且「To」收件人字段填充了合約地址,則合約已成功部署。
一旦你記下了合約地址,咱們就該開始經過Web3.py與合約進行交互了。
在我看來,有四種(半)方式能夠與以太坊智能合約進行互動。最後兩個(一半)常常混在一塊兒,但差別很重要。咱們已經看到了第一個:在區塊鏈上部署智能合約。如今咱們將介紹其他的python:
一些(但不是所有)智能合約包括「payable」應付功能。若是你將Ether發送到合約的地址,則會觸發這些功能。一個典型的用例就是ICO:將以太送到合約中,而後返回給你的是代幣。
首先,咱們將從導入開始,建立一個新的web3對象,經過Infura.io鏈接到Ropsten TestNet。
import time from web3 import Web3, HTTPProvider contract_address = [YOUR CONTRACT ADDRESS] wallet_private_key = [YOUR TEST WALLET PRIVATE KEY] wallet_address = [YOUR WALLET ADDRESS] w3 = Web3(HTTPProvider([YOUR INFURA URL])) w3.eth.enable_unaudited_features()
你能夠在Metamask中的賬戶名稱旁邊的菜單中找到你的錢包私鑰。由於咱們使用的Web3.py的某些功能還沒有通過徹底審覈以確保安全性,因此咱們須要調用w3.eth.enable_unaudited_features()
來確認咱們知道可能會發生問題的狀況。我告訴過你咱們用私鑰作了一些危險的事情!
如今咱們將編寫一個函數,將以太幣從咱們的錢包發送到合約:
def send_ether_to_contract(amount_in_ether): amount_in_wei = w3.toWei(amount_in_ether,'ether'); nonce = w3.eth.getTransactionCount(wallet_address) txn_dict = { 'to': contract_address, 'value': amount_in_wei, 'gas': 2000000, 'gasPrice': w3.toWei('40', 'gwei'), 'nonce': nonce, 'chainId': 3 } signed_txn = w3.eth.account.signTransaction(txn_dict, wallet_private_key) txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction) txn_receipt = None count = 0 while txn_receipt is None and (count < 30): txn_receipt = w3.eth.getTransactionReceipt(txn_hash) print(txn_receipt) time.sleep(10) if txn_receipt is None: return {'status': 'failed', 'error': 'timeout'} return {'status': 'added', 'txn_receipt': txn_receipt}
首先讓咱們回顧一下交易字典txn_dict
:它包含了定義咱們發送給智能合約的交易所需的大部分信息。
to
:咱們將以太送到哪裏(在這種狀況下是智能合約)。Vaule
:咱們送多少錢單位wei。gas
:gas是衡量在以太坊上執行交易的計算工做量度。在這種狀況下,咱們指定了咱們願意執行此交易的自然氣量的上限。gasPrice
:咱們願意爲每單位gas支付多少錢(以wei爲單位)。Nonce
:這是一個地址nonce而不是更常見的工做證實。它只是發送地址所作的先前交易次數的計數,用於防止雙重花費。Chain ID
:每一個以太坊網絡都有本身的鏈ID:主網的ID爲1,而Ropsten爲3。你能夠在這裏找到更長的列表。關於gas限制的快速說明:有一些功能可讓你估算交易將使用多少gas。可是,我發現選擇限制的最佳方法是計算出你願意支付多少錢,而後再讓交易失敗,而後再去作。
一旦咱們定義了交易的重要部分,咱們就會使用咱們錢包的私鑰對其進行簽名。而後它就能夠發送到網絡了,咱們將使用sendRawTransaction
方法。
在礦工決定將其包含在一個區塊中以前,咱們的交易實際上不會完成。通常而言,你爲每一個單位支付的費用Gas(記住咱們的自然氣價格參數)決定了一個節點決定將你的交易包含在一個區塊中的速度(若是有的話)。
https://ethgasstation.info/是...,能夠肯定你將等待你的交易包含在一個區塊中的時間。
此時間延遲意味着交易是異步的。當咱們調用sendRawTransaction時,咱們會當即得到交易的惟一哈希值。你能夠隨時使用此哈希來查詢你的交易是否已包含在塊中。咱們知道,當且僅當咱們可以得到交易收據時纔將交易添加到區塊鏈中(由於全部好的購買都帶有收據嗎?)。這就是爲何咱們建立循環來按期檢查咱們是否有收據:
txn_receipt = None count = 0 while txn_receipt is None and (count < 30): txn_receipt = w3.eth.getTransactionReceipt(txn_hash) print(txn_receipt) time.sleep(10)
值得注意的是,交易能夠添加到區塊鏈中,但仍然因各類緣由而失敗,例如沒有足夠的gas。
這就是將以太符號發送給合約的Python代碼。讓咱們快速回顧一下咱們在Solidity中寫的應付函數:
function() public payable{ if (msg.value >= 20000000000000000) { approvedSoapboxer[msg.sender] = true; } }
Msg是智能合約中的一個特殊變量,其中包含有關發送到智能合約的交易的信息。在這種狀況下,咱們使用msg.value
,它給出了交易中發送的Ether數量(在Wei而不是raw Ether中)。一樣,msg.sender
給出了進行交易的錢包的地址:若是已經發送了足夠的以太幣,咱們會將其添加到已批准賬戶的字典中。
繼續運行send_ether_to_contract
函數。但願你能收到回執。你還能夠經過在Etherscan的Ropsten Network部分查找你的錢包地址來檢查交易是否完成。咱們將在下一節中得到Python中的更多信息。
咱們剛剛向咱們的智能合約發送了一些以太幣,所以咱們想檢查咱們的錢包地址是否已被批准分享意見是有意義的。爲此,咱們在智能合約中定義瞭如下功能:
function isApproved(address _soapboxer) public view returns (bool approved) { return approvedSoapboxer[_soapboxer]; }
與python相比,這個函數附帶了不少額外的東西,好比聲明類型(地址和bool)。儘管如此,這個函數只須要一個地址(_soapboxer參數),在有效(但不徹底)的哈希表/python dict中查找相應的批准布爾值並返回該值。
你調用的時候一個智能合約函數,以太坊節點將計算結果,並將其返回給你。這裏的事情變得有點複雜:調用是隻讀的,這意味着它們不會對區塊鏈進行任何更改。若是上述函數包含一行代碼來記錄數字時間,則檢查地址是否已批准:
approvedCheckedCount[_soapboxer] = approvedCheckedCount[_soapboxer] + 1
而後,當調用該函數時,該節點將計算approvedCheckedCount
的新值,但一旦返回結果就丟棄它。
做爲只讀的交換,函數調用不會花費你運行任何以太,所以你能夠愉快地檢查賬戶是否已被批准而沒必要擔憂成本。
讓咱們跳回到咱們的python文件的頂部並添加一些更多的設置代碼。
import contract_abi contract = w3.eth.contract(address = contract_address, abi = contract_abi.abi)
你須要建立另外一個名爲contract_abi
的python文件。這將包含一個大的JSON信息字符串,Python須要與咱們在智能合約中定義的函數進行交互,稱爲應用程序二進制接口(ABI)。你能夠在Remix中找到智能合約的ABI的JSON字符串:
將複製的字符串粘貼到contract_abi.py
文件中,該文件應以下所示:
abi = """[ { A BIG LIST OF ABI INFO SPREAD ACROSS MULTIPLE DICTS } ]""
咱們添加到主python文件的另外一行如今使用此ABI JSON字符串並使用它來設置合約對象。若是你瀏覽合約,你會注意到它包含一個函數屬性,其中包含咱們在智能合約中建立的三個函數。
如今咱們將建立一個python函數,該函數調用Smart Contract
智能合約的isApproved
函數來檢查指定的地址是否被批准分享意見。
def check_whether_address_is_approved(address): return contract.functions.isApproved(address).call()
那很短暫。
你如今可使用它來檢查你的錢包地址是否已獲批准。若是你以前運行了send_ether_to_contract
函數併發送了足夠數量的以太,那麼但願你能回到true
。
咱們正在與智能合約進行最後的主要互動:廣播意見。再一次,咱們來看看咱們的Solidity Code:
function broadcastOpinion(string _opinion) public returns (bool success) { if (approvedSoapboxer[msg.sender]) { opinion = _opinion; emit OpinionBroadcast(msg.sender, opinion); return true; } else { return false; } }
這裏沒有什麼新東西:咱們採用傳入的_opinion
參數並使用它來設置全局變量意見。(若是你願意,能夠經過getter函數查詢實習生)。有一條線有點不一樣:
emit OpinionBroadcast(msg.sender, opinion)
咱們很快就會介紹。
當你經過交易與智能合約的功能進行交互時,功能對智能合約狀態所作的任何更改都會在區塊鏈上發佈。爲了換取這種特權,你必須向礦工支付一些(但願很小)的以太量。Python時間:
def broadcast_an_opinion(covfefe): nonce = w3.eth.getTransactionCount(wallet_address) txn_dict = contract.functions.broadcastOpinion(covfefe).buildTransaction({ 'chainId': 3, 'gas': 140000, 'gasPrice': w3.toWei('40', 'gwei'), 'nonce': nonce, }) signed_txn = w3.eth.account.signTransaction(txn_dict, private_key=wallet_private_key) result = w3.eth.sendRawTransaction(signed_txn.rawTransaction) tx_receipt = w3.eth.getTransactionReceipt(result) count = 0 while tx_receipt is None and (count < 30): time.sleep(10) tx_receipt = w3.eth.getTransactionReceipt(result) print(tx_receipt) if tx_receipt is None: return {'status': 'failed', 'error': 'timeout'} processed_receipt = contract.events.OpinionBroadcast().processReceipt(tx_receipt) print(processed_receipt) output = "Address {} broadcasted the opinion: {}"\ .format(processed_receipt[0].args._soapboxer, processed_receipt[0].args._opinion) print(output) return {'status': 'added', 'processed_receipt': processed_receipt}
這實際上與將Ether發送到智能合約時使用的過程相同。咱們將建立並簽署一個交易,而後將其發送到網絡。再一次,交易是異步的,這意味着不管函數被告知在Solidity代碼中返回什麼,你實際獲得的東西老是交易的哈希。
鑑於交易自己並無返回任何有用的信息,咱們須要其餘東西。這致使咱們採用最後(半)方式與智能合約進行互動。
我將事件稱爲與智能合約交互的「一半」方式,由於從技術上講,它們是由交易發出的。 事件是智能合約以易於閱讀的形式在區塊鏈上記錄事物的方式,它們基本上只是一組可使用特定交易的收據查找的值。咱們在智能合約的最頂層定義了一個:
event OpinionBroadcast(address _soapboxer, string _opinion);
而後,當咱們使用broadcastOpinion函數時,咱們使用它向區塊鏈發出信息。
將交易添加到塊後,你可使用交易哈希查詢區塊鏈以查找OpinionBroadcast
事件發出的特定值。這是咱們在函數broadcast_an_opinion
中的最後一點python代碼。你會注意到咱們要求事件發出的信息存儲在'args'屬性中。
這個事件很是公開。實際上,任何人均可以輕鬆使用Etherscan或相似工具來查看智能合約發出的全部事件的日誌。
Etherscan會自動檢測「Transfer」轉移事件並列出全部事件。Nifty
若是你已經作到這一點,你就有權發表意見。繼續用你選擇的意見運行broadcast_an_opinion
。
若是一切順利進行,你應該很快就會收到已處理的收據,以及已放入區塊鏈的OpinionBroadcast
事件的打印輸出。
Nice。
這是完整的python代碼:
import time from web3 import Web3, HTTPProvider contract_address = [YOUR CONTRACT ADDRESS] wallet_private_key = [YOUR TEST WALLET PRIVATE KEY] wallet_address = [YOUR WALLET ADDRESS] w3 = Web3(HTTPProvider([YOUR INFURA URL])) w3.eth.enable_unaudited_features() contract = w3.eth.contract(address = contract_address, abi = contract_abi.abi) def send_ether_to_contract(amount_in_ether): amount_in_wei = w3.toWei(amount_in_ether,'ether'); nonce = w3.eth.getTransactionCount(wallet_address) txn_dict = { 'to': contract_address, 'value': amount_in_wei, 'gas': 2000000, 'gasPrice': w3.toWei('40', 'gwei'), 'nonce': nonce, 'chainId': 3 } signed_txn = w3.eth.account.signTransaction(txn_dict, wallet_private_key) txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction) txn_receipt = None count = 0 while txn_receipt is None and (count < 30): txn_receipt = w3.eth.getTransactionReceipt(txn_hash) print(txn_receipt) time.sleep(10) if txn_receipt is None: return {'status': 'failed', 'error': 'timeout'} return {'status': 'added', 'txn_receipt': txn_receipt} def check_whether_address_is_approved(address): return contract.functions.isApproved(address).call() def broadcast_an_opinion(covfefe): nonce = w3.eth.getTransactionCount(wallet_address) txn_dict = contract.functions.broadcastOpinion(covfefe).buildTransaction({ 'chainId': 3, 'gas': 140000, 'gasPrice': w3.toWei('40', 'gwei'), 'nonce': nonce, }) signed_txn = w3.eth.account.signTransaction(txn_dict, private_key=wallet_private_key) result = w3.eth.sendRawTransaction(signed_txn.rawTransaction) tx_receipt = w3.eth.getTransactionReceipt(result) count = 0 while tx_receipt is None and (count < 30): time.sleep(10) tx_receipt = w3.eth.getTransactionReceipt(result) print(tx_receipt) if tx_receipt is None: return {'status': 'failed', 'error': 'timeout'} processed_receipt = contract.events.OpinionBroadcast().processReceipt(tx_receipt) print(processed_receipt) output = "Address {} broadcasted the opinion: {}"\ .format(processed_receipt[0].args._soapboxer, processed_receipt[0].args._opinion) print(output) return {'status': 'added', 'processed_receipt': processed_receipt} if __name__ == "__main__": send_ether_to_contract(0.03) is_approved = check_whether_address_is_approved(wallet_address) print(is_approved) broadcast_an_opinion('Despite the Constant Negative Press')
因此關於它。正如我所提到的,咱們尚未達到使用python實際部署智能合約很容易的地步,但其餘一切都在那裏。在Sempo,咱們正在使用上面提到的全部技術來使問題響應更加透明。
感謝Sebastian Dirman指出w3.toWei(value, ‘ether’)是一種更好的方式在Ether和Wei之間進行轉換——只需將以太量乘以1000000000000000000便可致使類型錯誤!
======================================================================
分享一些以太坊、EOS、比特幣等區塊鏈相關的交互式在線編程實戰教程:
- java以太坊開發教程,主要是針對java和android程序員進行區塊鏈以太坊開發的web3j詳解。
- python以太坊,主要是針對python工程師使用web3.py進行區塊鏈以太坊開發的詳解。
- php以太坊,主要是介紹使用php進行智能合約開發交互,進行帳號建立、交易、轉帳、代幣開發以及過濾器和交易等內容。
- 以太坊入門教程,主要介紹智能合約與dapp應用開發,適合入門。
- 以太坊開發進階教程,主要是介紹使用node.js、mongodb、區塊鏈、ipfs實現去中心化電商DApp實戰,適合進階。
- C#以太坊,主要講解如何使用C#開發基於.Net的以太坊應用,包括帳戶管理、狀態與交易、智能合約開發與交互、過濾器和交易等。
- EOS教程,本課程幫助你快速入門EOS區塊鏈去中心化應用的開發,內容涵蓋EOS工具鏈、帳戶與錢包、發行代幣、智能合約開發與部署、使用代碼與智能合約交互等核心知識點,最後綜合運用各知識點完成一個便籤DApp的開發。
- java比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Java代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Java工程師不可多得的比特幣開發學習課程。
- php比特幣開發教程,本課程面向初學者,內容即涵蓋比特幣的核心概念,例如區塊鏈存儲、去中心化共識機制、密鑰與腳本、交易與UTXO等,同時也詳細講解如何在Php代碼中集成比特幣支持功能,例如建立地址、管理錢包、構造裸交易等,是Php工程師不可多得的比特幣開發學習課程。
- tendermint區塊鏈開發詳解,本課程適合但願使用tendermint進行區塊鏈開發的工程師,課程內容即包括tendermint應用開發模型中的核心概念,例如ABCI接口、默克爾樹、多版本狀態庫等,也包括代幣發行等豐富的實操代碼,是go語言工程師快速入門區塊鏈開發的最佳選擇。
匯智網原創翻譯,轉載請標明出處。這裏是原文Python以太坊智能合約開發指南