diff --git a/.travis.yml b/.travis.yml index 65c89fc1..bd3c82c6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ install: - pip install -r requirements.txt - pip install coveralls readme_renderer script: +- python setup.py install - coverage run --source pyethapp setup.py test - python setup.py check --restructuredtext --strict after_success: diff --git a/pyethapp/config.py b/pyethapp/config.py index 2928bbd1..42c3dbf8 100644 --- a/pyethapp/config.py +++ b/pyethapp/config.py @@ -143,6 +143,7 @@ def set_config_param(config, s, strict=True): raise ValueError('Invalid config parameter') d = config for key in keys[:-1]: + key = key.strip() if strict and key not in d: raise KeyError('Unknown config option %s' % param) d = d.setdefault(key, {}) diff --git a/pyethapp/jsonrpc.py b/pyethapp/jsonrpc.py index e21770cd..95a4c69a 100644 --- a/pyethapp/jsonrpc.py +++ b/pyethapp/jsonrpc.py @@ -33,6 +33,7 @@ import ethereum.bloom as bloom from accounts import Account from ipc_rpc import bind_unix_listener, serve +from ethereum.exceptions import InvalidTransaction from ethereum.utils import int32 @@ -1190,7 +1191,7 @@ def call(self, data, block_id='pending'): try: success, output = processblock.apply_transaction(test_block, tx) - except processblock.InvalidTransaction: + except InvalidTransaction as e: success = False # make sure we didn't change the real state snapshot_after = block.snapshot() diff --git a/pyethapp/rpc_client.py b/pyethapp/rpc_client.py index de0552e0..fe1f25f5 100644 --- a/pyethapp/rpc_client.py +++ b/pyethapp/rpc_client.py @@ -23,6 +23,9 @@ # 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,14 @@ 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 @@ -141,6 +145,20 @@ def sender(self): 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 call(self, method, *args): + request = self.protocol.create_request(method, args) + reply = self.transport.send_message(request.serialize()) + if self.print_communication: + print json.dumps(json.loads(request.serialize()), indent=2) + print reply def blocknumber(self): """ Return the most recent block. """ @@ -362,15 +380,14 @@ def send_transaction(self, sender, to, value=0, data='', startgas=0, locally sign the transaction. This requires an extended server implementation that accepts the variables v, r, and s. """ - if not self.privkey and not sender: raise ValueError('Either privkey or sender needs to be supplied.') if self.privkey and not sender: sender = privtoaddr(self.privkey) - if nonce is None: - nonce = self.nonce(sender) + if nonce is None: + nonce = self.nonce(sender) elif self.privkey: if sender != privtoaddr(self.privkey): raise ValueError('sender for a different privkey.') @@ -382,7 +399,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) diff --git a/pyethapp/tests/test_rpc_client.py b/pyethapp/tests/test_rpc_client.py index 104874a8..d4b2cb63 100644 --- a/pyethapp/tests/test_rpc_client.py +++ b/pyethapp/tests/test_rpc_client.py @@ -1,9 +1,74 @@ +from pyethapp.jsonrpc import quantity_decoder from pyethapp.rpc_client import JSONRPCClient +import pytest +from subprocess import Popen +import time +from pyethapp.jsonrpc import address_encoder +from ethereum import utils + +def executable_installed(program): + import os + + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + for path in os.environ["PATH"].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None + +def prepare_rpc_tests(tmpdir): + rpc_tests = tmpdir.mkdir('testdata') + + assert Popen(['git', 'clone', 'https://github.com/ethereum/rpc-tests'], cwd=str(rpc_tests)).wait() == 0 + tests_dir = rpc_tests.join('rpc-tests') + import os.path + fpath = str(tests_dir.join('lib/config.js')) + assert os.path.isfile(fpath) + assert Popen(['git', 'submodule', 'update', '--init', '--recursive'], cwd=str(tests_dir)).wait() == 0 + assert os.path.isfile(str(tests_dir.join('lib/tests/BlockchainTests/bcRPC_API_Test.json')).decode('unicode-escape')) + return tests_dir + + +@pytest.fixture() +def test_setup(request, tmpdir): + """ + start the test_app with `subprocess.Popen`, so we can kill it properly. + :param request: + :param tmpdir: + :return: + """ + rpc_tests_dir = prepare_rpc_tests(tmpdir) + + test_data = rpc_tests_dir.join('lib/tests/BlockchainTests/bcRPC_API_Test.json') + assert executable_installed('pyethapp') + test_app = Popen([ + 'pyethapp', + '-d', str(tmpdir), + '-l:info,eth.chainservice:debug,jsonrpc:debug', + '-c jsonrpc.listen_port=8081', + '-c p2p.max_peers=0', + '-c p2p.min_peers=0', + 'blocktest', + str(test_data), + 'RPC_API_Test' + ]) + def fin(): + test_app.terminate() + request.addfinalizer(fin) + + time.sleep(60) + return (test_app, rpc_tests_dir) + def test_find_block(): + restore = JSONRPCClient.call JSONRPCClient.call = lambda self, cmd, num, flag: num client = JSONRPCClient() client.find_block(lambda x: x == '0x5') + JSONRPCClient.call = restore def test_default_host(): @@ -18,3 +83,68 @@ def test_set_host(): client = JSONRPCClient(host) assert client.transport.endpoint == 'http://{}:{}'.format(host, client.port) assert client.transport.endpoint != 'http://{}:{}'.format(default_host, client.port) + +# The fixture takes much time to initialize, so the tests are grouped into one method +def test_client(test_setup): + (test_app, rpc_tests_dir) = test_setup + client = JSONRPCClient(port=8081) + + 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) + + sender = client.sender + assert sender == '\xde\x0b)Vi\xa9\xfd\x93\xd5\xf2\x8d\x9e\xc8^@\xf4\xcbi{\xae' + + coinbase = client.coinbase + assert coinbase == '\xde\x0b)Vi\xa9\xfd\x93\xd5\xf2\x8d\x9e\xc8^@\xf4\xcbi{\xae' + + blocknumber = client.blocknumber() + assert blocknumber == 32 + + nonce = client.nonce(sender) + assert nonce == 0 + + balance1 = client.balance(sender) + assert balance1 == 5156250000000000000 + + gaslimit = client.gaslimit() + assert gaslimit == 3141592 + + lastgasprice = client.lastgasprice() + assert lastgasprice == 1 + + balance2 = client.balance('\xff' * 20) + assert balance2 == 0 + fid = client.new_filter('pending', 'pending') + + # The following tests require an account with a positive balance + # accs = client.call('eth_accounts') + # sender = accs[0] + # res_est = client.eth_sendTransaction(nonce, sender, address_encoder('\xff' * 20), 1) + # assert 'result' in res_est.keys() + + # res_call = client.eth_call(utils.encode_hex(a0), '\xff' * 20, 0) + # assert 'result' in res_call.keys() + + # res_st = client.send_transaction(sender, address_encoder('\xff' * 20), 1) + # assert 'result' in res_st.keys() + + # solidity_code = "contract test { function multiply(uint a) returns(uint d) { return a * 7; } }" + + # import ethereum._solidity + # s = ethereum._solidity.get_solidity() + # if s is None: + # pytest.xfail("solidity not installed, not tested") + # else: + # abi = s.mk_full_signature(solidity_code) + # abic = client.new_abi_contract(abi, sender) + # mult = abic.multiply(11111111) + # assert mult == 77777777 + + +def test_default_tx_gas_assigned(): + default_gas = 12345 + client = JSONRPCClient(default_tx_gas=default_gas) + assert client.default_tx_gas == default_gas +