Python開發以太坊智能合約指南(web3.py)

在以太坊上得到一個基本的智能合約是一個很簡單的事,只需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。乍一看,你應該使用哪個並不明顯。程序員

Pyethereum

以太坊虛擬機(EVM)的Python實現。反過來,EVM是以太坊協議的一部分,它實際運行智能合約中的代碼並肯定其輸出。所以,若是你想在Python中運行以太坊節點,那麼Pyethereum是一個很好的起點。web

即便你很是高興在不運行本身的節點的狀況下運行智能合約,Pyethereum仍然是一個很好的庫,它包含許多功能,能夠執行有用的功能,例如從私鑰計算用戶的地址等等。mongodb

Web3.py

用於實際與以太坊區塊鏈交互的庫。咱們談論的事情包括在帳戶之間轉移以太網,發佈智能合約以及觸發附加現有智能合約的功能。它受到流行的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字符串:

  • 單擊編譯「Compile」選項卡。
  • 單擊詳細信息「Details」——應顯示包含大量信息的模式。
  • 向下滾動到ABI部分,而後單擊複製到剪貼板「Copy to clipboard」圖標。

將複製的字符串粘貼到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代碼中返回什麼,你實際獲得的東西老是交易的哈希。

鑑於交易自己並無返回任何有用的信息,咱們須要其餘東西。這致使咱們採用最後(半)方式與智能合約進行互動。

事件events

我將事件稱爲與智能合約交互的「一半」方式,由於從技術上講,它們是由交易發出的。 事件是智能合約以易於閱讀的形式在區塊鏈上記錄事物的方式,它們基本上只是一組可使用特定交易的收據查找的值。咱們在智能合約的最頂層定義了一個:

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以太坊智能合約開發指南

相關文章
相關標籤/搜索