diff --git a/pyethapp/rpc_client.py b/pyethapp/rpc_client.py index f7fb41e8..4fe9c813 100644 --- a/pyethapp/rpc_client.py +++ b/pyethapp/rpc_client.py @@ -9,7 +9,8 @@ from ethereum.keys import privtoaddr from ethereum.transactions import Transaction from ethereum.utils import denoms, int_to_big_endian, big_endian_to_int, normalize_address -from ethereum._solidity import solidity_unresolved_symbols, solidity_library_symbol, solidity_resolve_symbols +from ethereum._solidity import solidity_unresolved_symbols, solidity_library_symbol, \ + solidity_resolve_symbols from tinyrpc.protocols.jsonrpc import JSONRPCErrorResponse, JSONRPCSuccessResponse from tinyrpc.protocols.jsonrpc import JSONRPCProtocol from tinyrpc.transports.http import HttpPostClientTransport @@ -23,6 +24,8 @@ # pylint: disable=invalid-name,too-many-arguments,too-few-public-methods # The number of arguments an it's names are determined by the JSON-RPC spec +DEFAULT_TX_GAS = 3141591 # genesis block gasLimit - 1 + z_address = '\x00' * 20 log = logging.getLogger(__name__) @@ -116,13 +119,15 @@ class JSONRPCClientReplyError(Exception): class JSONRPCClient(object): protocol = JSONRPCProtocol() - def __init__(self, host='127.0.0.1', port=4000, print_communication=True, privkey=None, sender=None): + def __init__(self, host='127.0.0.1', port=4000, print_communication=True, privkey=None, + sender=None, default_tx_gas=None): "specify privkey for local signing" self.transport = HttpPostClientTransport('http://{}:{}'.format(host, port)) self.print_communication = print_communication self.privkey = privkey self._sender = sender self.port = port + self._default_tx_gas = default_tx_gas def __repr__(self): return '' % self.port @@ -142,6 +147,13 @@ def coinbase(self): """ Return the client coinbase address. """ return address_decoder(self.call('eth_coinbase')) + @property + def default_tx_gas(self): + if self._default_tx_gas: + return self._default_tx_gas + else: + return DEFAULT_TX_GAS + def blocknumber(self): """ Return the most recent block. """ return quantity_decoder(self.call('eth_blockNumber')) @@ -167,7 +179,8 @@ def balance(self, account): return quantity_decoder(res) def gaslimit(self): - return quantity_decoder(self.call('eth_gasLimit')) + latest_block_info = self.call('eth_getBlockByNumber', 'latest', False) + return quantity_decoder(latest_block_info['gasLimit']) def lastgasprice(self): return quantity_decoder(self.call('eth_lastGasPrice')) @@ -357,7 +370,6 @@ def call(self, method, *args): def send_transaction(self, sender, to, value=0, data='', startgas=0, gasprice=10 * denoms.szabo, nonce=None): """ Helper to send signed messages. - This method will use the `privkey` provided in the constructor to locally sign the transaction. This requires an extended server implementation that accepts the variables v, r, and s. @@ -382,7 +394,7 @@ def send_transaction(self, sender, to, value=0, data='', startgas=0, nonce = 0 if not startgas: - startgas = self.gaslimit() - 1 + startgas = self.default_tx_gas tx = Transaction(nonce, gasprice, startgas, to=to, value=value, data=data) @@ -618,7 +630,6 @@ def __call__(self, *args, **kargs): class ContractProxy(object): """ Exposes a smart contract as a python object. - Contract calls can be made directly in this object, all the functions will be exposed with the equivalent api and will perform the argument translation. diff --git a/pyethapp/tests/conftest.py b/pyethapp/tests/conftest.py new file mode 100644 index 00000000..d26ca046 --- /dev/null +++ b/pyethapp/tests/conftest.py @@ -0,0 +1,131 @@ +from itertools import count +import gevent +import gc + +import pytest +import ethereum +import ethereum.config +import ethereum.keys +from ethereum.ethpow import mine +from ethereum import tester +from ethereum.slogging import get_logger +from devp2p.peermanager import PeerManager +import ethereum._solidity + +from pyethapp.accounts import Account, AccountsService, mk_random_privkey +from pyethapp.app import EthApp +from pyethapp.config import update_config_with_defaults, get_default_config +from pyethapp.db_service import DBService +from pyethapp.eth_service import ChainService +from pyethapp.jsonrpc import JSONRPCServer +from pyethapp.profiles import PROFILES +from pyethapp.pow_service import PoWService + + +log = get_logger('test.jsonrpc') + + +@pytest.fixture(params=[0, + PROFILES['testnet']['eth']['block']['ACCOUNT_INITIAL_NONCE']]) +def test_app(request, tmpdir): + + class TestApp(EthApp): + + def start(self): + super(TestApp, self).start() + log.debug('adding test accounts') + # high balance account + self.services.accounts.add_account(Account.new('', tester.keys[0]), store=False) + # low balance account + self.services.accounts.add_account(Account.new('', tester.keys[1]), store=False) + # locked account + locked_account = Account.new('', tester.keys[2]) + locked_account.lock() + self.services.accounts.add_account(locked_account, store=False) + assert set(acct.address for acct in self.services.accounts) == set(tester.accounts[:3]) + + def mine_next_block(self): + """Mine until a valid nonce is found. + + :returns: the new head + """ + log.debug('mining next block') + block = self.services.chain.chain.head_candidate + delta_nonce = 10 ** 6 + for start_nonce in count(0, delta_nonce): + bin_nonce, mixhash = mine(block.number, block.difficulty, block.mining_hash, + start_nonce=start_nonce, rounds=delta_nonce) + if bin_nonce: + break + self.services.pow.recv_found_nonce(bin_nonce, mixhash, block.mining_hash) + log.debug('block mined') + assert self.services.chain.chain.head.difficulty == 1 + return self.services.chain.chain.head + + def rpc_request(self, method, *args): + """Simulate an incoming JSON RPC request and return the result. + + Example:: + + >>> assert test_app.rpc_request('eth_getBalance', '0x' + 'ff' * 20) == '0x0' + + """ + log.debug('simulating rpc request', method=method) + method = self.services.jsonrpc.dispatcher.get_method(method) + res = method(*args) + log.debug('got response', response=res) + return res + + config = { + 'data_dir': str(tmpdir), + 'db': {'implementation': 'EphemDB'}, + 'pow': {'activated': False}, + 'p2p': { + 'min_peers': 0, + 'max_peers': 0, + 'listen_port': 29873 + }, + 'node': {'privkey_hex': mk_random_privkey().encode('hex')}, + 'discovery': { + 'boostrap_nodes': [], + 'listen_port': 29873 + }, + 'eth': { + 'block': { # reduced difficulty, increased gas limit, allocations to test accounts + 'ACCOUNT_INITIAL_NONCE': request.param, + 'GENESIS_DIFFICULTY': 1, + 'BLOCK_DIFF_FACTOR': 2, # greater than difficulty, thus difficulty is constant + 'GENESIS_GAS_LIMIT': 3141592, + 'GENESIS_INITIAL_ALLOC': { + tester.accounts[0].encode('hex'): {'balance': 10 ** 24}, + tester.accounts[1].encode('hex'): {'balance': 1}, + tester.accounts[2].encode('hex'): {'balance': 10 ** 24}, + } + } + }, + 'jsonrpc': {'listen_port': 4488, 'listen_host': '127.0.0.1'} + } + services = [DBService, AccountsService, PeerManager, ChainService, PoWService, JSONRPCServer] + update_config_with_defaults(config, get_default_config([TestApp] + services)) + update_config_with_defaults(config, {'eth': {'block': ethereum.config.default_config}}) + app = TestApp(config) + for service in services: + service.register_with_app(app) + + def fin(): + log.debug('stopping test app') + for service in app.services: + gevent.sleep(.1) + try: + app.services[service].stop() + except Exception as e: + log.DEV(str(e), exc_info=e) + pass + app.stop() + gevent.killall(task for task in gc.get_objects() if isinstance(task, gevent.Greenlet)) + + request.addfinalizer(fin) + + log.debug('starting test app') + app.start() + return app diff --git a/pyethapp/tests/test_jsonrpc.py b/pyethapp/tests/test_jsonrpc.py index 87f307e5..4a0b5b24 100644 --- a/pyethapp/tests/test_jsonrpc.py +++ b/pyethapp/tests/test_jsonrpc.py @@ -1,31 +1,19 @@ # -*- coding: utf8 -*- import os from os import path -from itertools import count -import gevent -import gc -import pytest -import rlp -import serpent import ethereum +import ethereum._solidity import ethereum.config import ethereum.keys -from ethereum.ethpow import mine +import pytest +import rlp +import serpent from ethereum import tester from ethereum.slogging import get_logger -from devp2p.peermanager import PeerManager -import ethereum._solidity -from pyethapp.accounts import Account, AccountsService, mk_random_privkey -from pyethapp.app import EthApp -from pyethapp.config import update_config_with_defaults, get_default_config -from pyethapp.db_service import DBService -from pyethapp.eth_service import ChainService -from pyethapp.jsonrpc import Compilers, JSONRPCServer, quantity_encoder, address_encoder, data_decoder, \ +from pyethapp.jsonrpc import Compilers, quantity_encoder, address_encoder, data_decoder, \ data_encoder, default_gasprice, default_startgas -from pyethapp.profiles import PROFILES -from pyethapp.pow_service import PoWService ethereum.keys.PBKDF2_CONSTANTS['c'] = 100 # faster key derivation log = get_logger('test.jsonrpc') # pylint: disable=invalid-name @@ -119,112 +107,6 @@ def test_compile_solidity(): assert compiler_info['abiDefinition'] == info['abiDefinition'] -@pytest.fixture(params=[0, - PROFILES['testnet']['eth']['block']['ACCOUNT_INITIAL_NONCE']]) -def test_app(request, tmpdir): - - class TestApp(EthApp): - - def start(self): - super(TestApp, self).start() - log.debug('adding test accounts') - # high balance account - self.services.accounts.add_account(Account.new('', tester.keys[0]), store=False) - # low balance account - self.services.accounts.add_account(Account.new('', tester.keys[1]), store=False) - # locked account - locked_account = Account.new('', tester.keys[2]) - locked_account.lock() - self.services.accounts.add_account(locked_account, store=False) - assert set(acct.address for acct in self.services.accounts) == set(tester.accounts[:3]) - - def mine_next_block(self): - """Mine until a valid nonce is found. - - :returns: the new head - """ - log.debug('mining next block') - block = self.services.chain.chain.head_candidate - delta_nonce = 10 ** 6 - for start_nonce in count(0, delta_nonce): - bin_nonce, mixhash = mine(block.number, block.difficulty, block.mining_hash, - start_nonce=start_nonce, rounds=delta_nonce) - if bin_nonce: - break - self.services.pow.recv_found_nonce(bin_nonce, mixhash, block.mining_hash) - log.debug('block mined') - assert self.services.chain.chain.head.difficulty == 1 - return self.services.chain.chain.head - - def rpc_request(self, method, *args): - """Simulate an incoming JSON RPC request and return the result. - - Example:: - - >>> assert test_app.rpc_request('eth_getBalance', '0x' + 'ff' * 20) == '0x0' - - """ - log.debug('simulating rpc request', method=method) - method = self.services.jsonrpc.dispatcher.get_method(method) - res = method(*args) - log.debug('got response', response=res) - return res - - config = { - 'data_dir': str(tmpdir), - 'db': {'implementation': 'EphemDB'}, - 'pow': {'activated': False}, - 'p2p': { - 'min_peers': 0, - 'max_peers': 0, - 'listen_port': 29873 - }, - 'node': {'privkey_hex': mk_random_privkey().encode('hex')}, - 'discovery': { - 'boostrap_nodes': [], - 'listen_port': 29873 - }, - 'eth': { - 'block': { # reduced difficulty, increased gas limit, allocations to test accounts - 'ACCOUNT_INITIAL_NONCE': request.param, - 'GENESIS_DIFFICULTY': 1, - 'BLOCK_DIFF_FACTOR': 2, # greater than difficulty, thus difficulty is constant - 'GENESIS_GAS_LIMIT': 3141592, - 'GENESIS_INITIAL_ALLOC': { - tester.accounts[0].encode('hex'): {'balance': 10 ** 24}, - tester.accounts[1].encode('hex'): {'balance': 1}, - tester.accounts[2].encode('hex'): {'balance': 10 ** 24}, - } - } - }, - 'jsonrpc': {'listen_port': 4488, 'listen_host': '127.0.0.1'} - } - services = [DBService, AccountsService, PeerManager, ChainService, PoWService, JSONRPCServer] - update_config_with_defaults(config, get_default_config([TestApp] + services)) - update_config_with_defaults(config, {'eth': {'block': ethereum.config.default_config}}) - app = TestApp(config) - for service in services: - service.register_with_app(app) - - def fin(): - log.debug('stopping test app') - for service in app.services: - gevent.sleep(.1) - try: - app.services[service].stop() - except Exception as e: - log.DEV(str(e), exc_info=e) - pass - app.stop() - gevent.killall(task for task in gc.get_objects() if isinstance(task, gevent.Greenlet)) - - request.addfinalizer(fin) - - log.debug('starting test app') - app.start() - return app - - def test_send_transaction(test_app): chain = test_app.services.chain.chain assert chain.head_candidate.get_balance('\xff' * 20) == 0 diff --git a/pyethapp/tests/test_rpc_client.py b/pyethapp/tests/test_rpc_client.py index 104874a8..ddda4b2b 100644 --- a/pyethapp/tests/test_rpc_client.py +++ b/pyethapp/tests/test_rpc_client.py @@ -1,11 +1,27 @@ +from pyethapp.jsonrpc import quantity_decoder from pyethapp.rpc_client import JSONRPCClient + def test_find_block(): JSONRPCClient.call = lambda self, cmd, num, flag: num client = JSONRPCClient() client.find_block(lambda x: x == '0x5') +def test_default_tx_gas(test_app): + JSONRPCClient.call = lambda self, cmd, block_id, flag: test_app.rpc_request(cmd, block_id, flag) + client = JSONRPCClient(port=test_app.config['jsonrpc']['listen_port']) + genesis_block_info = client.call('eth_getBlockByNumber', 'earliest', False) + genesis_gas_limit = quantity_decoder(genesis_block_info['gasLimit']) + assert client.default_tx_gas == (genesis_gas_limit - 1) + + +def test_default_tx_gas_assigned(): + default_gas = 12345 + client = JSONRPCClient(default_tx_gas=default_gas) + assert client.default_tx_gas == default_gas + + def test_default_host(): default_host = '127.0.0.1' client = JSONRPCClient() diff --git a/requirements.txt b/requirements.txt index 7d9d38f0..b7405aa2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ ethereum>=1.3.5 pbkdf2 scrypt pexpect +pytest