Python通過JSON-RPC對以太坊智能合約部署交易

本文探討了如何將JSON-RPC請求發(fā)送到Geth節(jié)點以創(chuàng)建原生的交易。目標(biāo)是在使用高級庫(如web3py或web3js)時了解并查看后臺發(fā)生的情況。

另外,對處理錯誤和異常不是本文的重點。如果出現(xiàn)任何問題,它將只是顯示失敗。這篇文章主要是學(xué)習(xí)。對于生產(chǎn)環(huán)境,還是考慮使用web3.py。

我們將僅使用HTTP請求在私有鏈上使用智能合約部署和交互(調(diào)用函數(shù)和讀取公共變量)。交易是離線簽名的,然后才發(fā)送到geth節(jié)點進(jìn)行處理。

對于本指南,我們使用的是私有的Proof-of-Authority網(wǎng)絡(luò)。如果想創(chuàng)建這樣一個網(wǎng)絡(luò),可以閱讀我們以前的帖子。本文假設(shè)使用Ganache(以前稱為TestRPC)或任何以太坊網(wǎng)絡(luò)都完全沒問題。因此,不會介紹有關(guān)在網(wǎng)絡(luò)設(shè)置的任何內(nèi)容,重點是使用python將HTTP請求發(fā)送到Geth節(jié)點。

條件

  • 1.通過IPC或RPC訪問以太坊網(wǎng)絡(luò)(可能是公有,私有或像Ganache這樣的模擬器)。
  • 2.安裝了python 3。 我個人喜歡Anaconda發(fā)行版
  • 3.安裝最新版本的web3py。

1.向Geth發(fā)送一個簡單的請求

讓我們通過向Geth發(fā)送一個非常簡單的請求來熱個身。查詢下網(wǎng)絡(luò)ID。 第一步是閱讀文檔。 我們需要的方法稱為net_version,在此處進(jìn)行描述。

我的Geth節(jié)點URL和端口是:http://localhost:8501。如果你使用的是具有默認(rèn)值的Ganache,則URL可能是http://localhost:7545。

我正在使用Requests python library來發(fā)出我的HTTP請求。

import requests
# create persistent HTTP connection
session = requests.Session()
# as defined in https://github.com/ethereum/wiki/wiki/JSON-RPC#net_version
method = 'net_version'
params = []
payload= {"jsonrpc":"2.0",
           "method":method,
           "params":params,
           "id":1}
headers = {'Content-type': 'application/json'}
response = session.post('http://localhost:8501', json=payload, headers=headers)
print('raw json response: {}'.format(response.json()))
print('network id: {}'.format(response.json()['result']))

結(jié)果是:

raw json response: {'id': 1, 'jsonrpc': '2.0', 'result': '1515'}
network id: 1515

不錯,從那里我們準(zhǔn)備好與合約一起部署和交易,這建立了一個良好的基礎(chǔ)。1515是我的私有區(qū)塊鏈的網(wǎng)絡(luò)ID,如創(chuàng)世文件中所定義。目前看起來都很棒。 使用Ganache,應(yīng)該獲得5777的網(wǎng)絡(luò)ID。

但在能夠簽署和發(fā)送交易之前,我們需要一個地址,一個私鑰和一些以太幣。

2.創(chuàng)建公鑰私鑰對并獲取一些以太幣

web3py(release 4)庫將幫助我們創(chuàng)建密鑰對。

import web3
w3 = web3.Web3()
myAccount = w3.eth.account.create('put some extra entropy here')
myAddress = myAccount.address
myPrivateKey = myAccount.privateKey
print('my address is     : {}'.format(myAccount.address))
print('my private key is : {}'.format(myAccount.privateKey.hex()))

在這個示例中,我得到:

my address is    : 0xF464A67CA59606f0fFE159092FF2F474d69FD675
my private key is: 0x94cb9f766ef067eb229da85213439cf4cbbcd0dc97ede9479be5ee4b7a93b96f

請永遠(yuǎn)不要分享你的私鑰!我這樣做是因為它是一個本地私有鏈,我每天都要銷毀并重啟幾次。我沒有在任何公共網(wǎng)絡(luò)上使用這個密鑰對。

現(xiàn)在為了獲得這個地址,有多種方法:

1.一種非常簡單的方法是在genesis.json文件中添加此地址并啟動新網(wǎng)絡(luò)。下面是之前我的創(chuàng)世紀(jì)文件,其中包括我們剛剛創(chuàng)建的地址(刪除0x)。

{
    "config": {
        "chainId": 1515,
        "homesteadBlock": 1,
        "eip150Block": 2,
        "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
        "eip155Block": 3,
        "eip158Block": 3,
        "byzantiumBlock": 4,
        "clique": {
            "period": 5,
            "epoch": 30000
        }
    },
    "nonce": "0x0",
    "timestamp": "0x5a722c92",
    "extraData": "0x000000000000000000000000000000000000000000000000000000000000000008a58f09194e403d02a1928a7bf78646cfc260b087366ef81db496edd0ea2055ca605e8686eec1e60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
    "gasLimit": "0x8000000",
    "difficulty": "0x1",
    "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "coinbase": "0x0000000000000000000000000000000000000000",
    "alloc": {
        "08a58f09194e403d02a1928a7bf78646cfc260b0": {
            "balance": "0x200000000000000000000000000000000000000000000000000000000000000"
        },
        "87366ef81db496edd0ea2055ca605e8686eec1e6": {
            "balance": "0x200000000000000000000000000000000000000000000000000000000000000"
        },
        "F464A67CA59606f0fFE159092FF2F474d69FD675": {
            "balance": "0x200000000000000000000000000000000000000000000000000000000000000"
        }
    },
    "number": "0x0",
    "gasUsed": "0x0",
    "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}

2.如果你有可以挖礦的節(jié)點或ganache,請打開Geth Javascript控制臺并手動創(chuàng)建交易:

$ geth attach ipc:'http://localhost:8501' // 7545 for ganache
Welcome to the Geth JavaScript console!
instance: Geth/v1.7.3-stable-4bb3c89d/linux-amd64/go1.9
coinbase: 0x87366ef81db496edd0ea2055ca605e8686eec1e6
at block: 1585 (Wed, 14 Feb 2018 11:46:04 CET)
 modules: eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0
> eth.sendTransaction({'from':eth.coinbase, 'to':'0xF464A67CA59606f0fFE159092FF2F474d69FD675', 'value':1000000000000000000000})
"0xdbc86acbe3644ac2cdb68132bbeecda40733c10f07ca16d87a2e5001e50eec4c"
> exit

這里我從0x87366...發(fā)送1000以太幣到我的地址0xF464A...,1個以太坊是10的18次方wei(1個后跟18個零)。值的單位是wei。

3.在公共測試鏈上,使用faucet。

3.使用智能合約部署和交易

太好了,既然我們有一個帶有一些以太網(wǎng)的地址(為了支付gas費用),我們可以離線創(chuàng)建我們的交易,簽名并將其發(fā)送到具有原生JSON-RPC的HTTP請求節(jié)點。

我們將使用send_rawTransaction方法,該方法將交易的簽名作為輸入?yún)?shù)。

python代碼正在查詢truffle在編譯智能合約時創(chuàng)建的包含合約abi和字節(jié)碼的json文件。在測試python代碼之前,創(chuàng)建一個truffle工作區(qū)并編譯虛擬合約AdditionContract.sol。

$ truffle init
// add the smart contract in contracts/
$ truffle compile

然后更新python代碼,用geth節(jié)點的URL以及truffle工作空間和genesis文件的路徑(不要忘記在路徑中用你的userName替換我的userName)。

其他一切都在代碼中,應(yīng)該是不言自明的。

pragma solidity ^0.4.18;

contract AdditionContract {
  uint public state = 0;

  function add(uint value1, uint value2) public {
    state = value1 + value2;
  }

  function getState() public constant returns (uint) {
      return state;
  }
}

文末附完整代碼。

我們讓一切都變得簡單易于修改和測試。 玩的開心 :)

python用web3.py庫開發(fā)以太坊來說非常的方便,有興趣的用戶可以關(guān)注我們的python以太坊教程,主要是針對python工程師使用web3.py進(jìn)行區(qū)塊鏈以太坊開發(fā)的詳解。

另外其他語言可以學(xué)習(xí)的以太坊教程如下:

  • web3j教程,主要是針對java和android程序員進(jìn)行區(qū)塊鏈以太坊開發(fā)的web3j詳解。
  • 以太坊教程,主要介紹智能合約與dapp應(yīng)用開發(fā),適合入門。
  • 以太坊開發(fā),主要是介紹使用node.js、mongodb、區(qū)塊鏈、ipfs實現(xiàn)去中心化電商DApp實戰(zhàn),適合進(jìn)階。
  • php以太坊,主要是介紹使用php進(jìn)行智能合約開發(fā)交互,進(jìn)行賬號創(chuàng)建、交易、轉(zhuǎn)賬、代幣開發(fā)以及過濾器和事件等內(nèi)容。
  • C#以太坊,主要講解如何使用C#開發(fā)基于.Net的以太坊應(yīng)用,包括賬戶管理、狀態(tài)與交易、智能合約開發(fā)與交互、過濾器和事件等。

匯智網(wǎng)原創(chuàng)翻譯,轉(zhuǎn)載請標(biāo)明出處。這里是原文

raw_JSON_RPC_requests_to_smart_contract.py

# associated medium post: https://medium.com/@ethervolution/ethereum-create-raw-json-rpc-requests-with-python-for-deploying-and-transacting-with-a-smart-7ceafd6790d9
import requests
import json
import web3 # Release 4.0.0-beta.8
import pprint
import time

# create persistent HTTP connection
session = requests.Session()
w3 = web3.Web3()
pp = pprint.PrettyPrinter(indent=2)

requestId = 0 # is automatically incremented at each request

URL = 'http://localhost:8501' # url of my geth node
PATH_GENESIS = '/home/salanfe/privateNetworks/geth_PoA/genesis.json'
PATH_SC_TRUFFLE = '/home/salanfe/Projects/AdditionContract/' # smart contract path

# extracting data from the genesis file
genesisFile = json.load(open(PATH_GENESIS))
CHAINID = genesisFile['config']['chainId']
PERIOD  = genesisFile['config']['clique']['period']
GASLIMIT = int(genesisFile['gasLimit'],0)

# compile your smart contract with truffle first
truffleFile = json.load(open(PATH_SC_TRUFFLE + '/build/contracts/AdditionContract.json'))
abi = truffleFile['abi']
bytecode = truffleFile['bytecode']

# Don't share your private key !
myAddress = '0xF464A67CA59606f0fFE159092FF2F474d69FD675' # address funded in genesis file
myPrivateKey = '0x94cb9f766ef067eb229da85213439cf4cbbcd0dc97ede9479be5ee4b7a93b96f'


''' =========================== SOME FUNCTIONS ============================ '''
# see http://www.jsonrpc.org/specification
# and https://github.com/ethereum/wiki/wiki/JSON-RPC

def createJSONRPCRequestObject(_method, _params, _requestId):
    return {"jsonrpc":"2.0",
            "method":_method,
            "params":_params, # must be an array [value1, value2, ..., valueN]
            "id":_requestId}, _requestId+1
    
def postJSONRPCRequestObject(_HTTPEnpoint, _jsonRPCRequestObject):
    response = session.post(_HTTPEnpoint,
                            json=_jsonRPCRequestObject,
                            headers={'Content-type': 'application/json'})

    return response.json()

  
''' ======================= DEPLOY A SMART CONTRACT ======================= '''
### get your nonce
requestObject, requestId = createJSONRPCRequestObject('eth_getTransactionCount', [myAddress, 'latest'], requestId)
responseObject = postJSONRPCRequestObject(URL, requestObject)
myNonce = w3.toInt(hexstr=responseObject['result'])
print('nonce of address {} is {}'.format(myAddress, myNonce))

### create your transaction
transaction_dict = {'from':myAddress,
                    'to':'', # empty address for deploying a new contract
                    'chainId':CHAINID,
                    'gasPrice':1, # careful with gas price, gas price below the --gasprice option of Geth CLI will cause problems. I am running my node with --gasprice '1'
                    'gas':2000000, # rule of thumb / guess work
                    'nonce':myNonce,
                    'data':bytecode} # no constrctor in my smart contract so bytecode is enough

### sign the transaction
signed_transaction_dict = w3.eth.account.signTransaction(transaction_dict, myPrivateKey)
params = [signed_transaction_dict.rawTransaction.hex()]

### send the transacton to your node
requestObject, requestId = createJSONRPCRequestObject('eth_sendRawTransaction', params, requestId)
responseObject = postJSONRPCRequestObject(URL, requestObject)
transactionHash = responseObject['result']
print('contract submission hash {}'.format(transactionHash))

### wait for the transaction to be mined and get the address of the new contract
while(True):
    requestObject, requestId = createJSONRPCRequestObject('eth_getTransactionReceipt', [transactionHash], requestId)
    responseObject = postJSONRPCRequestObject(URL, requestObject)
    receipt = responseObject['result']
    if(receipt is not None):
        if(receipt['status'] == '0x1'):
            contractAddress = receipt['contractAddress']
            print('newly deployed contract at address {}'.format(contractAddress))
        else:
            pp.pprint(responseObject)
            raise ValueError('transacation status is "0x0", failed to deploy contract. Check gas, gasPrice first')
        break
    time.sleep(PERIOD/10)


''' ================= SEND A TRANSACTION TO SMART CONTRACT  ================'''
### get your nonce
requestObject, requestId = createJSONRPCRequestObject('eth_getTransactionCount', [myAddress, 'latest'], requestId)
responseObject = postJSONRPCRequestObject(URL, requestObject)
myNonce = w3.toInt(hexstr=responseObject['result'])
print('nonce of address {} is {}'.format(myAddress, myNonce))

### prepare the data field of the transaction
# function selector and argument encoding
# https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding
value1, value2 = 10, 32 # random numbers here
function = 'add(uint256,uint256)' # from smart contract
methodId = w3.sha3(text=function)[0:4].hex()
param1 = (value1).to_bytes(32, byteorder='big').hex()
param2 = (value2).to_bytes(32, byteorder='big').hex()
data = '0x' + methodId + param1 + param2

transaction_dict = {'from':myAddress,
                    'to':contractAddress,
                    'chainId':CHAINID,
                    'gasPrice':1, # careful with gas price, gas price below the threshold defined in the node config will cause all sorts of issues (tx not bieng broadcasted for example)
                    'gas':2000000, # rule of thumb / guess work
                    'nonce':myNonce,
                    'data':data}

### sign the transaction
signed_transaction_dict = w3.eth.account.signTransaction(transaction_dict, myPrivateKey)
params = [signed_transaction_dict.rawTransaction.hex()]

### send the transacton to your node
print('executing {} with value {},{}'.format(function, value1, value2))
requestObject, requestId = createJSONRPCRequestObject('eth_sendRawTransaction', params, requestId)
responseObject = postJSONRPCRequestObject(URL, requestObject)
transactionHash = responseObject['result']
print('transaction hash {}'.format(transactionHash))

### wait for the transaction to be mined
while(True):
    requestObject, requestId = createJSONRPCRequestObject('eth_getTransactionReceipt', [transactionHash], requestId)
    responseObject = postJSONRPCRequestObject(URL, requestObject)
    receipt = responseObject['result']
    if(receipt is not None):
        if(receipt['status'] == '0x1'):
            print('transaction successfully mined')
        else:
            pp.pprint(responseObject)
            raise ValueError('transacation status is "0x0", failed to deploy contract. Check gas, gasPrice first')
        break
    time.sleep(PERIOD/10)



''' ============= READ YOUR SMART CONTRACT STATE USING GETTER  =============='''
# we don't need a nonce since this does not create a transaction but only ask
# our node to read it's local database

### prepare the data field of the transaction
# function selector and argument encoding
# https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding
# state is declared as public in the smart contract. This creates a getter function
methodId = w3.sha3(text='state()')[0:4].hex()
data = '0x' + methodId
transaction_dict = {'from':myAddress,
                    'to':contractAddress,
                    'chainId':CHAINID,
                    'data':data}

params = [transaction_dict, 'latest']
requestObject, requestId = createJSONRPCRequestObject('eth_call', params, requestId)
responseObject = postJSONRPCRequestObject(URL, requestObject)
state = w3.toInt(hexstr=responseObject['result'])
print('using getter for public variables: result is {}'.format(state))



''' ============= READ YOUR SMART CONTRACT STATE GET FUNCTIONS  =============='''
# we don't need a nonce since this does not create a transaction but only ask
# our node to read it's local database

### prepare the data field of the transaction
# function selector and argument encoding
# https://solidity.readthedocs.io/en/develop/abi-spec.html#function-selector-and-argument-encoding
# state is declared as public in the smart contract. This creates a getter function
methodId = w3.sha3(text='getState()')[0:4].hex()
data = '0x' + methodId
transaction_dict = {'from':myAddress,
                    'to':contractAddress,
                    'chainId':CHAINID,
                    'data':data}

params = [transaction_dict, 'latest']
requestObject, requestId = createJSONRPCRequestObject('eth_call', params, requestId)
responseObject = postJSONRPCRequestObject(URL, requestObject)
state = w3.toInt(hexstr=responseObject['result'])
print('using getState() function: result is {}'.format(state))


''' prints
nonce of address 0xF464A67CA59606f0fFE159092FF2F474d69FD675 is 4
contract submission hash 0x64fc8ce5cbb5cf822674b88b52563e89f9e98132691a4d838ebe091604215b25
newly deployed contract at address 0x7e99eaa36bedba49a7f0ea4096ab2717b40d3787
nonce of address 0xF464A67CA59606f0fFE159092FF2F474d69FD675 is 5
executing add(uint256,uint256) with value 10,32
transaction hash 0xcbe3883db957cf3b643567c078081343c0cbd1fdd669320d9de9d05125168926
transaction successfully mined
using getter for public variables: result is 42
using getState() function: result is 42
'''
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容