diff --git a/.gitignore b/.gitignore index 539da74..0966e83 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ *.py[co] +.idea/ +build/lib/ +ethjsonrpc.egg-info/ +.ropeproject/ +dist/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..8b7960a --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8cc6fb6 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.rst b/README.rst index 5db894e..9dcea37 100644 --- a/README.rst +++ b/README.rst @@ -1,10 +1,12 @@ -ethjsonrpc +InfuraEthJsonRpc ========== +Extension of the ethjsonrpc library (https://github.com/ConsenSys/ethjsonrpc) -Python client for Ethereum using the JSON-RPC interface +* Provides classes to query data from an infura node +* To speed up the methods, get an access token from Infura. (https://infura.io/register.html) -* complete: implements all 62 JSON-RPC methods plus several client-specific methods -* provides a high-level interface to create contracts on the blockchain and to call contract methods +Earlier instructions +==================== Important note -------------- @@ -35,104 +37,32 @@ On Ubuntu 14.04: $ sudo apt-get install libpython-dev $ sudo apt-get install libssl-dev - -To install ethjsonrpc: +New Instructions +================ +To install: .. code:: bash - $ pip install ethjsonrpc - + $ git clone https://github.com/ankitchiplunkar/ethjsonrpc.git + $ cd ethjsonrpc + $ python setup.py install -Make sure to have a node running an Ethereum client (such as geth) for the library to connect to. Example ------- .. code:: python - >>> from ethjsonrpc import EthJsonRpc # to use Parity-specific methods, import ParityEthJsonRpc - >>> c = EthJsonRpc('127.0.0.1', 8545) - >>> c.net_version() - u'1' - >>> c.web3_clientVersion() - u'Geth/v1.3.3/linux/go1.5.1' - >>> c.eth_gasPrice() - 50000000000 - >>> c.eth_blockNumber() - 828948 - - -High-level functionality ------------------------- - -These examples assume the following simple Solidity contract: - -.. code:: - - contract Example { - - string s; - - function set_s(string new_s) { - s = new_s; - } - - function get_s() returns (string) { - return s; - } - } - - -Compile it like this: - -.. code:: bash - - $ solc --binary stdout example.sol - - -Setup -````` - -.. code:: python - - >>> compiled = '606060405261020f806100136000396000f30060606040526000357c01000000000000000000000000000000000000000000000000000000009004806375d74f3914610044578063e7aab290146100bd57610042565b005b61004f600450610191565b60405180806020018281038252838181518152602001915080519060200190808383829060006004602084601f0104600302600f01f150905090810190601f1680156100af5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b61010d6004803590602001906004018035906020019191908080601f016020809104026020016040519081016040528093929190818152602001838380828437820191505050505050905061010f565b005b806000600050908051906020019082805482825590600052602060002090601f01602090048101928215610160579182015b8281111561015f578251826000505591602001919060010190610141565b5b50905061018b919061016d565b80821115610187576000818150600090555060010161016d565b5090565b50505b50565b60206040519081016040528060008152602001506000600050805480601f0160208091040260200160405190810160405280929190818152602001828054801561020057820191906000526020600020905b8154815290600101906020018083116101e357829003601f168201915b5050505050905061020c565b9056' - >>> from ethjsonrpc import EthJsonRpc # to use Parity-specific methods, import ParityEthJsonRpc - >>> c = EthJsonRpc('127.0.0.1', 8545) - - -Creating a contract on the blockchain -````````````````````````````````````` - -.. code:: python - - >>> # continued from above - >>> contract_tx = c.create_contract(c.eth_coinbase(), compiled, gas=300000) - >>> # wait here for the contract to be created when a new block is mined - >>> contract_addr = c.get_contract_address(contract_tx) - >>> contract_addr - u'0x24988147f2f2300450103d8c42c43182cf226857' - - -Calling a contract function with a transaction (storing data) -````````````````````````````````````````````````````````````` - -.. code:: python - - >>> # continued from above - >>> tx = c.call_with_transaction(c.eth_coinbase(), contract_addr, 'set_s(string)', ['Hello, world']) - >>> tx - u'0x15bde63d79466e3db5169a913bb2069130ca387033d2ff2e29f4dfbef1bc6e0d' - - -Calling a contract function on the local blockchain (reading data) -`````````````````````````````````````````````````````````````````` - -.. code:: python - - >>> # continued from above - >>> results = c.call(contract_addr, 'get_s()', [], ['string']) - >>> results - ['Hello, world'] + >>> c = InfuraEthJsonRpc(network='mainnet') + # other possible networks are 'ropsten', 'rinkeby', 'kovan' and 'infuranet' + >>> c.net_version() + u'1' + >>> c.web3_clientVersion() + u'Geth/v1.3.3/linux/go1.5.1' + >>> c.eth_gasPrice() + 50000000000 + >>> c.eth_blockNumber() + 4896520 Additional examples @@ -140,90 +70,42 @@ Additional examples Please see ``test.py`` for additional examples. -Implemented JSON-RPC methods ----------------------------- - -* web3_clientVersion -* web3_sha3 -* net_version -* net_listening -* net_peerCount -* eth_protocolVersion -* eth_syncing -* eth_coinbase -* eth_mining -* eth_hashrate -* eth_gasPrice -* eth_accounts -* eth_blockNumber -* eth_getBalance -* eth_getStorageAt -* eth_getTransactionCount -* eth_getBlockTransactionCountByHash -* eth_getBlockTransactionCountByNumber -* eth_getUncleCountByBlockHash -* eth_getUncleCountByBlockNumber -* eth_getCode -* eth_sign -* eth_sendTransaction -* eth_sendRawTransaction -* eth_call -* eth_estimateGas -* eth_getBlockByHash -* eth_getBlockByNumber -* eth_getTransactionByHash -* eth_getTransactionByBlockHashAndIndex -* eth_getTransactionByBlockNumberAndIndex -* eth_getTransactionReceipt -* eth_getUncleByBlockHashAndIndex -* eth_getUncleByBlockNumberAndIndex -* eth_getCompilers -* eth_compileSolidity -* eth_compileLLL -* eth_compileSerpent -* eth_newFilter -* eth_newBlockFilter -* eth_newPendingTransactionFilter -* eth_uninstallFilter -* eth_getFilterChanges -* eth_getFilterLogs -* eth_getLogs -* eth_getWork -* eth_submitWork -* eth_submitHashrate -* db_putString -* db_getString -* db_putHex -* db_getHex -* shh_version -* shh_post -* shh_newIdentity -* shh_hasIdentity -* shh_newGroup -* shh_addToGroup -* shh_newFilter -* shh_uninstallFilter -* shh_getFilterChanges -* shh_getMessages - -Parity-only JSON-RPC methods ----------------------------- - -To use these methods, make sure that you're - -* running Parity as your client -* running with the ``--tracing on`` option -* using this library's ``ParityEthJsonRpc`` client (not the vanilla ``EthJsonRpc`` client) - -Methods: - -* trace_filter -* trace_get -* trace_transaction -* trace_block +Table of unavailable methods +------------------- +Since this is not a local node, the following methods are unavailable. + +* ~~web3_sha3~~ +* ~~eth_coinbase~~ +* ~~eth_sign~~ +* ~~eth_sendTransaction~~ +* ~~eth_compileSolidity~~ +* ~~eth_compileLLL~~ +* ~~eth_compileSerpent~~ +* ~~eth_newFilter~~ +* ~~eth_newBlockFilter~~ +* ~~eth_newPendingTransactionFilter~~ +* ~~eth_uninstallFilter~~ +* ~~eth_getFilterChanges~~ +* ~~eth_getFilterLogs~~ +* ~~db_putString~~ +* ~~db_getString~~ +* ~~db_putHex~~ +* ~~db_getHex~~ +* ~~shh_version~~ +* ~~shh_post~~ +* ~~shh_newIdentity~~ +* ~~shh_hasIdentity~~ +* ~~shh_newGroup~~ +* ~~shh_addToGroup~~ +* ~~shh_newFilter~~ +* ~~shh_uninstallFilter~~ +* ~~shh_getFilterChanges~~ +* ~~shh_getMessages~~ Reference --------- +* https://blog.infura.io/getting-started-with-infura-28e41844cc89 * https://github.com/ethereum/wiki/wiki/JSON-RPC * https://github.com/ethcore/parity/wiki/JSONRPC-trace-module + diff --git a/ethjsonrpc/__init__.py b/ethjsonrpc/__init__.py index 1813aae..df09920 100644 --- a/ethjsonrpc/__init__.py +++ b/ethjsonrpc/__init__.py @@ -1,8 +1,8 @@ -from ethjsonrpc.client import (EthJsonRpc, ParityEthJsonRpc, +from ethjsonrpc.client import (EthJsonRpc, ParityEthJsonRpc, InfuraEthJsonRpc, ETH_DEFAULT_RPC_PORT, GETH_DEFAULT_RPC_PORT, PYETHAPP_DEFAULT_RPC_PORT) -from ethjsonrpc.exceptions import (ConnectionError, BadStatusCodeError, +from ethjsonrpc.exceptions import (ConnectionError, BadStatusCodeError, BadMethodError, BadJsonError, BadResponseError) from ethjsonrpc.utils import wei_to_ether, ether_to_wei diff --git a/ethjsonrpc/client.py b/ethjsonrpc/client.py index 89a9edb..73be1b8 100644 --- a/ethjsonrpc/client.py +++ b/ethjsonrpc/client.py @@ -10,7 +10,7 @@ from ethjsonrpc.constants import BLOCK_TAGS, BLOCK_TAG_LATEST from ethjsonrpc.utils import hex_to_dec, clean_hex, validate_block from ethjsonrpc.exceptions import (ConnectionError, BadStatusCodeError, - BadJsonError, BadResponseError) + BadJsonError, BadResponseError, BadMethodError) GETH_DEFAULT_RPC_PORT = 8545 ETH_DEFAULT_RPC_PORT = 8545 @@ -18,6 +18,9 @@ PYETHAPP_DEFAULT_RPC_PORT = 4000 MAX_RETRIES = 3 JSON_MEDIA_TYPE = 'application/json' +DEFAULT_NODE = 'local' +DEFAULT_INFURA_NETWORK = 'mainnet' +DEFAULT_INFURA_TOKEN = "" class EthJsonRpc(object): @@ -26,31 +29,38 @@ class EthJsonRpc(object): ''' DEFAULT_GAS_PER_TX = 90000 - DEFAULT_GAS_PRICE = 50 * 10**9 # 50 gwei + DEFAULT_GAS_PRICE = 50 * 10 ** 9 # 50 gwei - def __init__(self, host='localhost', port=GETH_DEFAULT_RPC_PORT, tls=False): + def __init__(self, host='localhost', port=GETH_DEFAULT_RPC_PORT, tls=False, node=DEFAULT_NODE): self.host = host self.port = port self.tls = tls + self.session = requests.Session() self.session.mount(self.host, HTTPAdapter(max_retries=MAX_RETRIES)) + scheme = 'http' + if self.tls: + scheme += 's' + + if node == 'local': + self.url = '{}://{}:{}'.format(scheme, self.host, self.port) + elif node == 'infura': + self.url = '{}://{}'.format(scheme, self.host) + def _call(self, method, params=None, _id=1): params = params or [] data = { 'jsonrpc': '2.0', - 'method': method, - 'params': params, - 'id': _id, + 'method': method, + 'params': params, + 'id': _id, } - scheme = 'http' - if self.tls: - scheme += 's' - url = '{}://{}:{}'.format(scheme, self.host, self.port) + headers = {'Content-Type': JSON_MEDIA_TYPE} try: - r = self.session.post(url, headers=headers, data=json.dumps(data)) + r = self.session.post(self.url, headers=headers, data=json.dumps(data)) except RequestsConnectionError: raise ConnectionError if r.status_code / 100 != 2: @@ -78,9 +88,9 @@ def _encode_function(self, signature, param_values): encoded_params = encode_abi(types, param_values) return utils.zpad(utils.encode_int(prefix), 4) + encoded_params -################################################################################ -# high-level methods -################################################################################ + ################################################################################ + # high-level methods + ################################################################################ def transfer(self, from_, to, amount): ''' @@ -95,9 +105,9 @@ def create_contract(self, from_, code, gas, sig=None, args=None): ''' from_ = from_ or self.eth_coinbase() if sig is not None and args is not None: - types = sig[sig.find('(') + 1: sig.find(')')].split(',') - encoded_params = encode_abi(types, args) - code += encoded_params.encode('hex') + types = sig[sig.find('(') + 1: sig.find(')')].split(',') + encoded_params = encode_abi(types, args) + code += encoded_params.encode('hex') return self.eth_sendTransaction(from_address=from_, gas=gas, data=code) def get_contract_address(self, tx): @@ -129,9 +139,9 @@ def call_with_transaction(self, from_, address, sig, args, gas=None, gas_price=N return self.eth_sendTransaction(from_address=from_, to_address=address, data=data_hex, gas=gas, gas_price=gas_price, value=value) -################################################################################ -# JSON-RPC methods -################################################################################ + ################################################################################ + # JSON-RPC methods + ################################################################################ def web3_clientVersion(self): ''' @@ -506,9 +516,9 @@ def eth_newFilter(self, from_block=BLOCK_TAG_LATEST, to_block=BLOCK_TAG_LATEST, ''' _filter = { 'fromBlock': from_block, - 'toBlock': to_block, - 'address': address, - 'topics': topics, + 'toBlock': to_block, + 'address': address, + 'topics': topics, } return self._call('eth_newFilter', [_filter]) @@ -637,12 +647,12 @@ def shh_post(self, topics, payload, priority, ttl, from_=None, to=None): NEEDS TESTING ''' whisper_object = { - 'from': from_, - 'to': to, - 'topics': topics, - 'payload': payload, + 'from': from_, + 'to': to, + 'topics': topics, + 'payload': payload, 'priority': hex(priority), - 'ttl': hex(ttl), + 'ttl': hex(ttl), } return self._call('shh_post', [whisper_object]) @@ -685,7 +695,7 @@ def shh_newFilter(self, to, topics): NEEDS TESTING ''' _filter = { - 'to': to, + 'to': to, 'topics': topics, } return self._call('shh_newFilter', [_filter]) @@ -714,14 +724,13 @@ def shh_getMessages(self, filter_id): ''' return self._call('shh_getMessages', [filter_id]) - class ParityEthJsonRpc(EthJsonRpc): ''' EthJsonRpc subclass for Parity-specific methods ''' def __init__(self, host='localhost', port=PARITY_DEFAULT_RPC_PORT, tls=False): - EthJsonRpc.__init__(self, host=host, port=port, tls=tls) + EthJsonRpc.__init__(self, host=host, port=port, tls=tls, node=DEFAULT_NODE) def trace_filter(self, from_block=None, to_block=None, from_addresses=None, to_addresses=None): ''' @@ -772,3 +781,133 @@ def trace_block(self, block=BLOCK_TAG_LATEST): ''' block = validate_block(block) return self._call('trace_block', [block]) + + def trace_replayTransaction(self, tx_hash, mode='trace'): + ''' + https://wiki.parity.io/JSONRPC-trace-module.html#trace_replaytransaction + + NEEDS TESTING + ''' + return self._call(method='trace_replayTransaction', params=[tx_hash, + [mode]]) + + def trace_replayBlockTransactions(self, block=BLOCK_TAG_LATEST, mode='trace'): + ''' + https://wiki.parity.io/JSONRPC-trace-module.html#trace_replayblocktransactions + + NEEDS TESTING + ''' + block = validate_block(block) + return self._call(method='trace_replayBlockTransactions', + params=[block, [mode]]) + + +class InfuraEthJsonRpc(EthJsonRpc): + ''' + EthJsonRpc subclass for Infura-specific methods + ''' + def __init__(self, network=DEFAULT_INFURA_NETWORK, infura_token=DEFAULT_INFURA_TOKEN, tls=True): + if infura_token: + host = '{}.infura.io/{}'.format(network, infura_token) + else: + host = '{}.infura.io'.format(network) + + EthJsonRpc.__init__(self, host=host, tls=tls, node='infura') + + # methods to disable unavailable functions + def call(self, address, sig, args, result_types): + raise BadMethodError() + + def call_with_transaction(self, from_, address, sig, args, gas=None, gas_price=None, value=None): + raise BadMethodError() + + def create_contract(self, from_, code, gas, sig=None, args=None): + raise BadMethodError() + + def transfer(self, from_, to, amount): + raise BadMethodError() + + def web3_sha3(self, data): + raise BadMethodError() + + def eth_coinbase(self): + raise BadMethodError() + + def eth_sign(self, address, data): + raise BadMethodError() + + def eth_sendTransaction(self, to_address=None, from_address=None, gas=None, gas_price=None, value=None, data=None, + nonce=None): + raise BadMethodError() + + def eth_compileSolidity(self, code): + raise BadMethodError() + + def eth_compileLLL(self, code): + raise BadMethodError() + + def eth_compileSerpent(self, code): + raise BadMethodError() + + def eth_newFilter(self, from_block=BLOCK_TAG_LATEST, to_block=BLOCK_TAG_LATEST, address=None, topics=None): + raise BadMethodError() + + def eth_newBlockFilter(self): + raise BadMethodError() + + def eth_newPendingTransactionFilter(self): + raise BadMethodError() + + def eth_uninstallFilter(self, filter_id): + raise BadMethodError() + + def eth_getFilterChanges(self, filter_id): + raise BadMethodError() + + def eth_getFilterLogs(self, filter_id): + raise BadMethodError() + + def db_putString(self, db_name, key, value): + raise BadMethodError() + + def db_getString(self, db_name, key): + raise BadMethodError() + + def db_putHex(self, db_name, key, value): + raise BadMethodError() + + def db_getHex(self, db_name, key): + raise BadMethodError() + + def shh_version(self): + raise BadMethodError() + + def shh_post(self, topics, payload, priority, ttl, from_=None, to=None): + raise BadMethodError() + + def shh_newIdentity(self): + raise BadMethodError() + + def shh_hasIdentity(self, address): + raise BadMethodError() + + def shh_newGroup(self): + raise BadMethodError() + + def shh_addToGroup(self): + raise BadMethodError() + + def shh_newFilter(self, to, topics): + raise BadMethodError() + + def shh_uninstallFilter(self, filter_id): + raise BadMethodError() + + def shh_getFilterChanges(self, filter_id): + raise BadMethodError() + + def shh_getMessages(self, filter_id): + raise BadMethodError() + + def eth_getCompilers(self): + raise BadMethodError() diff --git a/ethjsonrpc/exceptions.py b/ethjsonrpc/exceptions.py index 05e70ba..6ce6dad 100644 --- a/ethjsonrpc/exceptions.py +++ b/ethjsonrpc/exceptions.py @@ -16,3 +16,6 @@ class BadJsonError(EthJsonRpcError): class BadResponseError(EthJsonRpcError): pass + +class BadMethodError(EthJsonRpcError): + pass \ No newline at end of file diff --git a/test.py b/test.py index 3518e88..acbef85 100644 --- a/test.py +++ b/test.py @@ -1,4 +1,4 @@ -from ethjsonrpc import EthJsonRpc +from ethjsonrpc import InfuraEthJsonRpc methods = [ 'web3_clientVersion', @@ -7,21 +7,20 @@ 'net_listening', 'eth_protocolVersion', 'eth_syncing', - 'eth_coinbase', 'eth_mining', 'eth_hashrate', 'eth_gasPrice', 'eth_accounts', 'eth_blockNumber', - 'eth_getCompilers', - 'eth_newPendingTransactionFilter', - 'eth_getWork', +# 'eth_getCompilers', +# 'eth_newPendingTransactionFilter', +# 'eth_getWork', # 'shh_version', # 'shh_newIdentity', # 'shh_newGroup', ] -c = EthJsonRpc() +c = InfuraEthJsonRpc() print len(methods) for m in methods: meth = getattr(c, m) @@ -44,7 +43,6 @@ result = c.eth_getBlockTransactionCountByNumber(x) print 'eth_getBlockTransactionCountByNumber: %s (%s)' % (result, type(result)) - b = (199583, '0x19d761c6f944eefe91ad70b9aff3d2d76c972e5bb68c443eea7c0eaa144cef9f') result = c.eth_getUncleCountByBlockHash(b[1]) print 'eth_getUncleCountByBlockHash: %s (%s)' % (result, type(result)) @@ -56,25 +54,6 @@ ################################################################################ print '*' * 80 -db_name = 'db_name' -k = 'my_key' -v = 'my_value' -print c.db_putString(db_name, k, v) -x = c.db_getString(db_name, k) -print x -assert v == x - -db_name = 'db_name' -k = 'my_key' -v = '0xabcdef' -print c.db_putHex(db_name, k, v) -x = c.db_getHex(db_name, k) -print x -assert v == x - -################################################################################ -print '*' * 80 - b = (199583, '0x19d761c6f944eefe91ad70b9aff3d2d76c972e5bb68c443eea7c0eaa144cef9f') print c.eth_getBlockByHash(b[1], tx_objects=False) @@ -87,18 +66,6 @@ ################################################################################ print '*' * 80 -code = 'contract Test {}' -print c.eth_compileSolidity(code) - -#code = '' -#print c.eth_compileSerpent(code) - -#code = '' -#print c.eth_compileLLL(code) - -################################################################################ -print '*' * 80 - b = (246236, '0xcd43703a1ead33ffa1f317636c7b67453c5cc03a3350cd71dbbdd70fcbe0987a') index = 2 print c.eth_getTransactionByBlockHashAndIndex(b[1], index) @@ -134,7 +101,3 @@ client_id = '0x59daa26581d0acd1fce254fb7e85952f4c09d0915afd33d3886cd914bc7d283c' print c.eth_submitHashrate(hash_rate, client_id) -digest = c.web3_sha3('') -print digest -# keccak-256, not sha3-256 -assert digest == '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'