本文探讨了如何将JSON-RPC請求發送到Geth節點以建立原生的交易。目标是在使用進階庫(如web3py或web3js)時了解并檢視背景發生的情況。
另外,對處理錯誤和異常不是本文的重點。如果出現任何問題,它将隻是顯示失敗。這篇文章主要是學習。對于生産環境,還是考慮使用web3.py。
我們将僅使用HTTP請求在私有鍊上使用智能合約部署和互動(調用函數和讀取公共變量)。交易是離線簽名的,然後才發送到geth節點進行處理。
對于本指南,我們使用的是私有的Proof-of-Authority網絡。如果想建立這樣一個網絡,可以閱讀我們以前的文章。本文假設使用Ganache(以前稱為TestRPC)或任何以太坊網絡都完全沒問題。是以,不會介紹有關在網絡設定的任何内容,重點是使用python将HTTP請求發送到Geth節點。
條件
- 1.通過IPC或RPC通路以太坊網絡(可能是公有,私有或像Ganache這樣的模拟器)。
- 2.安裝了python 3。 我個人喜歡Anaconda發行版。
- 3.安裝最新版本的web3py。
1.向Geth發送一個簡單的請求
讓我們通過向Geth發送一個非常簡單的請求來熱個身。查詢下網絡ID。 第一步是閱讀文檔。 我們需要的方法稱為
net_version
,在此處進行描述。
我的Geth節點URL和端口是:
http://localhost:8501
。如果你使用的是具有預設值的
Ganache
,則URL可能是
http://localhost:7545
。
我正在使用Requests python library來發出我的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']))
結果是:
raw json response: {'id': , 'jsonrpc': '2.0', 'result': '1515'}
network id:
不錯,從那裡我們準備好與合約一起部署和交易,這建立了一個良好的基礎。1515是我的私有區塊鍊的網絡ID,如創世檔案中所定義。目前看起來都很棒。 使用Ganache,應該獲得5777的網絡ID。
但在能夠簽署和發送交易之前,我們需要一個位址,一個私鑰和一些以太币。
2.建立公鑰私鑰對并擷取一些以太币
web3py(release 4)庫将幫助我們建立密鑰對。
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 :
my private key is:
請永遠不要分享你的私鑰!我這樣做是因為它是一個本地私有鍊,我每天都要銷毀并重新開機幾次。我沒有在任何公共網絡上使用這個密鑰對。
現在為了獲得這個位址,有多種方法:
1.一種非常簡單的方法是在
genesis.json
檔案中添加此位址并啟動新網絡。下面是之前我的創世紀檔案,其中包括我們剛剛建立的位址(删除
0x
)。
{
"config": {
"chainId": ,
"homesteadBlock": ,
"eip150Block": ,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": ,
"eip158Block": ,
"byzantiumBlock": ,
"clique": {
"period": ,
"epoch":
}
},
"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.如果你有可以挖礦的節點或
ganache
,請打開Geth Javascript控制台并手動建立交易:
$ geth attach ipc:'http://localhost:8501' // 7545 for ganache
Welcome to the Geth JavaScript console!
instance: Geth/v1-stable-bb3c89d/linux-amd64/go1
coinbase:
at block: (Wed, Feb :: CET)
modules: eth: miner: net: personal: rpc: txpool: web3:
> eth.sendTransaction({'from':eth.coinbase, 'to':'0xF464A67CA59606f0fFE159092FF2F474d69FD675', 'value':})
"0xdbc86acbe3644ac2cdb68132bbeecda40733c10f07ca16d87a2e5001e50eec4c"
> exit
這裡我從
0x87366...
發送1000以太币到我的位址
0xF464A...
,1個以太坊是10的18次方wei(1個後跟18個零)。值的機關是wei。
3.在公共測試鍊上,使用faucet。
3.使用智能合約部署和交易
太好了,既然我們有一個帶有一些以太網的位址(為了支付gas費用),我們可以離線建立我們的交易,簽名并将其發送到具有原生
JSON-RPC
的HTTP請求節點。
我們将使用
send_rawTransaction
方法,該方法将交易的簽名作為輸入參數。
python代碼正在查詢
truffle
在編譯智能合約時建立的包含合約
abi
和位元組碼的json檔案。在測試python代碼之前,建立一個
truffle
工作區并編譯虛拟合約
AdditionContract.sol
。
$ truffle init
// add the smart contract in contracts/
$ truffle compile
然後更新python代碼,用
geth
節點的URL以及
truffle
工作空間和
genesis
檔案的路徑(不要忘記在路徑中用你的userName替換我的userName)。
其他一切都在代碼中,應該是不言自明的。
pragma solidity ^;
contract AdditionContract {
uint public state = ;
function add(uint value1, uint value2) public {
state = value1 + value2;
}
function getState() public constant returns (uint) {
return state;
}
}
文末附完整代碼。
我們讓一切都變得簡單易于修改和測試。 玩的開心 :)
python用web3.py庫開發以太坊來說非常的友善,有興趣的使用者可以關注我們的python以太坊教程,主要是針對python工程師使用web3.py進行區塊鍊以太坊開發的詳解。
另外其他語言可以學習的以太坊教程如下:
- web3j教程,主要是針對java和android程式員進行區塊鍊以太坊開發的web3j詳解。
- 以太坊教程,主要介紹智能合約與dapp應用開發,适合入門。
- 以太坊開發,主要是介紹使用node.js、mongodb、區塊鍊、ipfs實作去中心化電商DApp實戰,适合進階。
- php以太坊,主要是介紹使用php進行智能合約開發互動,進行賬号建立、交易、轉賬、代币開發以及過濾器和事件等内容。
- C#以太坊,主要講解如何使用C#開發基于.Net的以太坊應用,包括賬戶管理、狀态與交易、智能合約開發與互動、過濾器和事件等。
彙智網原創翻譯,轉載請标明出處。這裡是原文
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=)
requestId = # 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'],)
# 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+
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':, # 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':, # 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/)
''' ================= 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 = , # random numbers here
function = 'add(uint256,uint256)' # from smart contract
methodId = w3.sha3(text=function)[:].hex()
param1 = (value1).to_bytes(, byteorder='big').hex()
param2 = (value2).to_bytes(, byteorder='big').hex()
data = '0x' + methodId + param1 + param2
transaction_dict = {'from':myAddress,
'to':contractAddress,
'chainId':CHAINID,
'gasPrice':, # 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':, # 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/)
''' ============= 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()')[:].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()')[:].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
'''