diff --git a/.coveragerc b/.coveragerc index f4653318..e14be4f9 100644 --- a/.coveragerc +++ b/.coveragerc @@ -6,7 +6,6 @@ omit = */examples/* */tests/* -[report] exclude_lines = pragma: no cover def __repr__ diff --git a/.travis.yml b/.travis.yml index e83fe042..8a39f52d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,16 +3,16 @@ sudo: required dist: trusty python: - '2.7' +- '3.6' before_install: - sudo add-apt-repository -y ppa:ethereum/ethereum - sudo apt-get update - sudo apt-get install -y solc install: - USE_PYETHEREUM_DEVELOP=1 python setup.py install -- pip install coveralls readme_renderer +- pip install coveralls readme_renderer tox-travis script: -- coverage run --source pyethapp setup.py test -- python setup.py check --restructuredtext --strict +- tox after_success: - coveralls env: diff --git a/README.rst b/README.rst index 862d39ef..fd57a555 100644 --- a/README.rst +++ b/README.rst @@ -25,7 +25,7 @@ Introduction pyethapp is the python based client implementing the Ethereum_ cryptoeconomic state machine. -Ethereum as a platform is focussed on enabling people to build new ideas using blockchain technology. +Ethereum as a platform is focused on enabling people to build new ideas using blockchain technology. The python implementation aims to provide an easily hackable and extendable codebase. diff --git a/dev_requirements.txt b/dev_requirements.txt index 65fa2123..b50585f9 100644 --- a/dev_requirements.txt +++ b/dev_requirements.txt @@ -1,6 +1,5 @@ -pytest==2.9.1 +pytest mock==2.0.0 pytest-mock==1.6.0 -ethereum-serpent>=1.8.1 -pytest==2.9.1 coverage==4.0.3 +ethereum-serpent diff --git a/pyethapp/accounts.py b/pyethapp/accounts.py index af35a859..c8789b4f 100644 --- a/pyethapp/accounts.py +++ b/pyethapp/accounts.py @@ -1,3 +1,5 @@ +from builtins import hex +from builtins import object import json import os from random import SystemRandom @@ -7,10 +9,14 @@ from ethereum.tools import keys from ethereum.slogging import get_logger from ethereum.utils import privtopub # this is different than the one used in devp2p.crypto -from ethereum.utils import sha3, is_string, decode_hex, remove_0x_head +from ethereum.utils import sha3, is_string, encode_hex, remove_0x_head, to_string +from rlp.utils import decode_hex + +from pyethapp.utils import MinType + log = get_logger('accounts') -DEFAULT_COINBASE = 'de0b295669a9fd93d5f28d9ec85e40f4cb697bae'.decode('hex') +DEFAULT_COINBASE = decode_hex('de0b295669a9fd93d5f28d9ec85e40f4cb697bae') random = SystemRandom() @@ -22,7 +28,7 @@ def mk_privkey(seed): def mk_random_privkey(): k = hex(random.getrandbits(256))[2:-1].zfill(64) assert len(k) == 64 - return k.decode('hex') + return decode_hex(k) class Account(object): @@ -38,7 +44,7 @@ class Account(object): def __init__(self, keystore, password=None, path=None): self.keystore = keystore try: - self._address = self.keystore['address'].decode('hex') + self._address = decode_hex(self.keystore['address']) except KeyError: self._address = None self.locked = True @@ -61,6 +67,13 @@ def new(cls, password, key=None, uuid=None, path=None): """ if key is None: key = mk_random_privkey() + + # [NOTE]: key and password should be bytes + if not is_string(key): + key = to_string(key) + if not is_string(password): + password = to_string(password) + keystore = keys.make_keystore_json(key, password) keystore['id'] = uuid return Account(keystore, password, path) @@ -94,9 +107,9 @@ def dump(self, include_address=True, include_id=True): d['crypto'] = self.keystore['crypto'] d['version'] = self.keystore['version'] if include_address and self.address is not None: - d['address'] = self.address.encode('hex') + d['address'] = encode_hex(self.address) if include_id and self.uuid is not None: - d['id'] = self.uuid + d['id'] = str(self.uuid) return json.dumps(d) def unlock(self, password): @@ -146,7 +159,7 @@ def address(self): if self._address: pass elif 'address' in self.keystore: - self._address = self.keystore['address'].decode('hex') + self._address = decode_hex(self.keystore['address']) elif not self.locked: self._address = keys.privtoaddr(self.privkey) else: @@ -187,7 +200,7 @@ def sign_tx(self, tx): def __repr__(self): if self.address is not None: - address = self.address.encode('hex') + address = encode_hex(self.address) else: address = '?' return ''.format(address=address, id=self.uuid) @@ -257,7 +270,9 @@ def coinbase(self): return DEFAULT_COINBASE cb = self.accounts_with_address[0].address else: - if not is_string(cb_hex): + # [NOTE]: check it! + # if not is_string(cb_hex): + if not isinstance(cb_hex, str): raise ValueError('coinbase must be string') try: cb = decode_hex(remove_0x_head(cb_hex)) @@ -305,8 +320,9 @@ def add_account(self, account, store=True, include_address=True, include_id=True errno=e.errno) raise self.accounts.append(account) - self.accounts.sort(key=lambda account: account.path) - + min_value = MinType() + self.accounts.sort(key=lambda account: min_value if account.path is None else account.path) + def update_account(self, account, new_password, include_address=True, include_id=True): """Replace the password of an account. @@ -424,7 +440,7 @@ def find(self, identifier): except ValueError: pass else: - return self.get_by_id(str(uuid)) + return self.get_by_id(uuid.hex) try: index = int(identifier, 10) @@ -441,7 +457,7 @@ def find(self, identifier): if identifier[:2] == '0x': identifier = identifier[2:] try: - address = identifier.decode('hex') + address = decode_hex(identifier) except TypeError: success = False else: @@ -480,16 +496,16 @@ def get_by_address(self, address): assert len(address) == 20 accounts = [account for account in self.accounts if account.address == address] if len(accounts) == 0: - raise KeyError('account with address {} not found'.format(address.encode('hex'))) + raise KeyError('account with address {} not found'.format(encode_hex(address))) elif len(accounts) > 1: - log.warning('multiple accounts with same address found', address=address.encode('hex')) + log.warning('multiple accounts with same address found', address=encode_hex(address)) return accounts[0] def sign_tx(self, address, tx): self.get_by_address(address).sign_tx(tx) def propose_path(self, address): - return os.path.join(self.keystore_dir, address.encode('hex')) + return os.path.join(self.keystore_dir, encode_hex(address)) def __contains__(self, address): assert len(address) == 20 diff --git a/pyethapp/app.py b/pyethapp/app.py index 029f5e25..65af45ae 100644 --- a/pyethapp/app.py +++ b/pyethapp/app.py @@ -1,4 +1,9 @@ # -*- coding: utf8 -*- +from __future__ import print_function +from __future__ import absolute_import +from builtins import zip +from builtins import next +from builtins import range import copy import json import os @@ -19,20 +24,26 @@ from ethereum import config as eth_config from ethereum.block import Block from ethereum.snapshot import create_snapshot, load_snapshot as _load_snapshot +from ethereum.utils import ( + encode_hex, + decode_hex, + to_string, +) from gevent.event import Event -import config as app_config -import eth_protocol -import utils -from accounts import AccountsService, Account -from console_service import Console -from db_service import DBService -from eth_service import ChainService -from jsonrpc import JSONRPCServer, IPCRPCServer -from pow_service import PoWService +from . import config as app_config +from . import eth_protocol +from . import utils +from .accounts import AccountsService, Account +from .console_service import Console +from .db_service import DBService +from .eth_service import ChainService +from .jsonrpc import JSONRPCServer, IPCRPCServer +from .pow_service import PoWService from pyethapp import __version__ from pyethapp.profiles import PROFILES, DEFAULT_PROFILE -from pyethapp.utils import merge_dict, load_contrib_services, FallbackChoice, enable_greenlet_debugger +from pyethapp.utils import merge_dict, load_contrib_services, FallbackChoice, \ + enable_greenlet_debugger log = slogging.get_logger('app') @@ -57,7 +68,7 @@ class EthApp(BaseApp): # Separators should be underscore! @click.group(help='Welcome to {} {}'.format(EthApp.client_name, EthApp.client_version)) @click.option('--profile', type=FallbackChoice( - PROFILES.keys(), + list(PROFILES.keys()), {'frontier': 'livenet', 'morden': 'testnet'}, "PyEthApp's configuration profiles have been renamed to " "'livenet' and 'testnet'. The previous values 'frontier' and " @@ -83,8 +94,8 @@ class EthApp(BaseApp): help='Unlock an account (prompts for password)') @click.option('--password', type=click.File(), help='path to a password file') @click.pass_context -def app(ctx, profile, alt_config, config_values, alt_data_dir, log_config, bootstrap_node, log_json, - mining_pct, unlock, password, log_file): +def app(ctx, profile, alt_config, config_values, alt_data_dir, log_config, + bootstrap_node, log_json, mining_pct, unlock, password, log_file): # configure logging slogging.configure(log_config, log_json=log_json, log_file=log_file) @@ -108,6 +119,9 @@ def app(ctx, profile, alt_config, config_values, alt_data_dir, log_config, boots # Store custom genesis to restore if overridden by profile value genesis_from_config_file = config.get('eth', {}).get('genesis') + # Store custom network_id to restore if overridden by profile value + network_id_from_config_file = config.get('eth', {}).get('network_id') + # Store custom bootstrap_nodes to restore them overridden by profile value bootstrap_nodes_from_config_file = config.get('discovery', {}).get('bootstrap_nodes') @@ -123,6 +137,10 @@ def app(ctx, profile, alt_config, config_values, alt_data_dir, log_config, boots del config['eth']['genesis_hash'] config['eth']['genesis'] = genesis_from_config_file + if network_id_from_config_file: + del config['eth']['network_id'] + config['eth']['network_id'] = network_id_from_config_file + if bootstrap_nodes_from_config_file: # Fixed bootstrap_nodes taken from profile must be deleted as custom bootstrap_nodes loaded del config['discovery']['bootstrap_nodes'] @@ -145,9 +163,10 @@ def app(ctx, profile, alt_config, config_values, alt_data_dir, log_config, boots # Load genesis config app_config.update_config_from_genesis_json(config, - genesis_json_filename_or_dict=config['eth']['genesis']) + genesis_json_filename_or_dict=config['eth']['genesis']) if bootstrap_node: - config['discovery']['bootstrap_nodes'] = [bytes(bootstrap_node)] + # [NOTE]: check it + config['discovery']['bootstrap_nodes'] = [to_string(bootstrap_node)] if mining_pct > 0: config['pow']['activated'] = True config['pow']['cpu_pct'] = int(min(100, mining_pct)) @@ -305,7 +324,7 @@ def blocktest(ctx, file, name): log.fatal('Name not found in file') ctx.abort() try: - blocks = utils.load_block_tests(data.values()[0], app.services.chain.chain.db) + blocks = utils.load_block_tests(list(data.values())[0], app.services.chain.chain.db) except ValueError: log.fatal('Invalid blocks encountered') ctx.abort() @@ -357,7 +376,7 @@ def snapshot(ctx, recent, filename): s = create_snapshot(app.services.chain.chain, recent) with open(filename, 'w') as f: json.dump(s, f, sort_keys=False, indent=4, separators=(',', ': '), encoding='ascii') - print 'snapshot saved to %s' % filename + print('snapshot saved to %s' % filename) @app.command('load_snapshot') @@ -376,7 +395,7 @@ def load_snapshot(ctx, filename): with open(filename, 'r') as f: s = json.load(f, encoding='ascii') _load_snapshot(app.services.chain.chain, s) - print 'snapshot %s loaded.' % filename + print('snapshot %s loaded.' % filename) @app.command('export') @@ -416,7 +435,7 @@ def export_blocks(ctx, from_, to, file): sys.exit(1) log.info('Starting export') - for n in xrange(from_, to + 1): + for n in range(from_, to + 1): log.debug('Exporting block {}'.format(n)) if (n - from_) % 50000 == 0: log.info('Exporting block {} to {}'.format(n, min(n + 50000, to))) @@ -524,7 +543,7 @@ def new_account(ctx, uuid): """ app = ctx.obj['app'] if uuid: - id_ = str(uuid4()) + id_ = uuid4() else: id_ = None password = ctx.obj['password'] @@ -532,7 +551,7 @@ def new_account(ctx, uuid): password = click.prompt('Password to encrypt private key', default='', hide_input=True, confirmation_prompt=True, show_default=False) account = Account.new(password, uuid=id_) - account.path = os.path.join(app.services.accounts.keystore_dir, account.address.encode('hex')) + account.path = os.path.join(app.services.accounts.keystore_dir, encode_hex(account.address)) try: app.services.accounts.add_account(account) except IOError: @@ -541,8 +560,8 @@ def new_account(ctx, uuid): sys.exit(1) else: click.echo('Account creation successful') - click.echo(' Address: ' + account.address.encode('hex')) - click.echo(' Id: ' + str(account.uuid)) + click.echo(' Address: {}'.format(encode_hex(account.address))) + click.echo(' Id: {}'.format(account.uuid)) @account.command('list') @@ -564,8 +583,8 @@ def list_accounts(ctx): id='Id (if any)', locked='Locked')) for i, account in enumerate(accounts): - click.echo(fmt.format(i='#' + str(i + 1), - address=(account.address or '').encode('hex'), + click.echo(fmt.format(i='#' + to_string(i + 1), + address=encode_hex(account.address or ''), id=account.uuid or '', locked='yes' if account.locked else 'no')) @@ -586,12 +605,12 @@ def import_account(ctx, f, uuid): """ app = ctx.obj['app'] if uuid: - id_ = str(uuid4()) + id_ = uuid4() else: id_ = None privkey_hex = f.read() try: - privkey = privkey_hex.strip().decode('hex') + privkey = decode_hex(privkey_hex.strip()) except TypeError: click.echo('Could not decode private key from file (should be hex encoded)') sys.exit(1) @@ -599,8 +618,9 @@ def import_account(ctx, f, uuid): if password is None: password = click.prompt('Password to encrypt private key', default='', hide_input=True, confirmation_prompt=True, show_default=False) + account = Account.new(password, privkey, uuid=id_) - account.path = os.path.join(app.services.accounts.keystore_dir, account.address.encode('hex')) + account.path = os.path.join(app.services.accounts.keystore_dir, encode_hex(account.address)) try: app.services.accounts.add_account(account) except IOError: @@ -609,8 +629,8 @@ def import_account(ctx, f, uuid): sys.exit(1) else: click.echo('Account creation successful') - click.echo(' Address: ' + account.address.encode('hex')) - click.echo(' Id: ' + str(account.uuid)) + click.echo(' Address: {}'.format(encode_hex(account.address))) + click.echo(' Id: {}'.format(account.uuid)) @account.command('update') @@ -648,7 +668,7 @@ def update_account(ctx, account): sys.exit(1) click.echo('Updating account') - click.echo('Address: {}'.format(old_account.address.encode('hex'))) + click.echo('Address: {}'.format(encode_hex(old_account.address))) click.echo(' Id: {}'.format(old_account.uuid)) new_password = click.prompt('New password', default='', hide_input=True, @@ -699,7 +719,7 @@ def unlock_accounts(account_ids, account_service, max_attempts=3, password=None) sys.exit(1) return - max_attempts_str = str(max_attempts) if max_attempts else 'oo' + max_attempts_str = to_string(max_attempts) if max_attempts else 'oo' attempt_fmt = '(attempt {{attempt}}/{})'.format(max_attempts_str) first_attempt_fmt = 'Password for account {id} ' + attempt_fmt further_attempts_fmt = 'Wrong password. Please try again ' + attempt_fmt diff --git a/pyethapp/codernitydb_service.py b/pyethapp/codernitydb_service.py index 673a75d3..73f46dba 100644 --- a/pyethapp/codernitydb_service.py +++ b/pyethapp/codernitydb_service.py @@ -77,7 +77,7 @@ def put(self, key, value): def commit(self): log.debug('committing', db=self) - for k, v in self.uncommitted.items(): + for k, v in list(self.uncommitted.items()): if v is None: doc = self.db.get('key', k, with_doc=True)['doc'] self.db.delete(doc) diff --git a/pyethapp/config.py b/pyethapp/config.py index 4b0a9317..fd8559cc 100644 --- a/pyethapp/config.py +++ b/pyethapp/config.py @@ -16,18 +16,21 @@ """ +from __future__ import print_function +from __future__ import absolute_import +from builtins import str import os import copy import click -from devp2p.utils import update_config_with_defaults # updates only missing entries +from devp2p.utils import update_config_with_defaults # updates only missing entries import errno import yaml import ethereum.slogging as slogging from devp2p.service import BaseService from devp2p.app import BaseApp -from accounts import mk_random_privkey -from ethereum.tools.keys import decode_hex -from ethereum.utils import parse_int_or_hex, remove_0x_head +from .accounts import mk_random_privkey +from rlp.utils import decode_hex +from ethereum.utils import parse_int_or_hex, remove_0x_head, encode_hex CONFIG_FILE_NAME = 'config.yaml' @@ -40,6 +43,7 @@ def get_config_path(data_dir=default_data_dir): return os.path.join(data_dir, CONFIG_FILE_NAME) + default_config_path = get_config_path(default_data_dir) @@ -60,7 +64,7 @@ def setup_data_dir(data_dir=None): def check_config(config, required_config=required_config): "check if values are set" - for k, v in required_config.items(): + for k, v in list(required_config.items()): if not config.get(k): return False if isinstance(v, dict): @@ -76,7 +80,7 @@ def validate_alt_config_file(ctx, param, value): if value: try: yaml_ = load_config(value) - except IOError, e: + except IOError as e: raise click.BadParameter(str(e)) else: if not isinstance(yaml_, dict): @@ -92,7 +96,7 @@ def setup_required_config(data_dir=default_data_dir): assert not os.path.exists(config_path) if not os.path.exists(data_dir): setup_data_dir(data_dir) - config = dict(node=dict(privkey_hex=mk_random_privkey().encode('hex'))) + config = dict(node=dict(privkey_hex=encode_hex(mk_random_privkey()))) write_config(config, config_path) @@ -122,7 +126,7 @@ def write_config(config, path=default_config_path): """ assert path log.info('writing config', path=path) - with open(path, 'wb') as f: + with open(path, 'w') as f: yaml.dump(config, f) @@ -164,7 +168,7 @@ def dump_config(config): konfig['accounts']['privkeys_hex'] = [mask(key) for key in konfig['accounts']['privkeys_hex']] if len(konfig.get('node', {}).get('privkey_hex')): konfig['node']['privkey_hex'] = mask(konfig['node']['privkey_hex']) - print yaml.dump(konfig) + print(yaml.dump(konfig)) def update_config_from_genesis_json(config, genesis_json_filename_or_dict): diff --git a/pyethapp/console_service.py b/pyethapp/console_service.py index 8e360ad8..cf436fe3 100644 --- a/pyethapp/console_service.py +++ b/pyethapp/console_service.py @@ -1,7 +1,14 @@ """ Essential parts borrowed from https://github.com/ipython/ipython/pull/1654 """ -import cStringIO +from __future__ import print_function +from __future__ import absolute_import +from future import standard_library +standard_library.install_aliases() +from builtins import str +from builtins import range +from builtins import object +import io import errno import os import select @@ -222,7 +229,7 @@ def new_contract(this, abi, address, sender=None): return ABIContract(sender or this.coinbase, abi, address, this.call, this.transact) def block_from_rlp(this, rlp_data): - from eth_protocol import TransientBlock + from .eth_protocol import TransientBlock import rlp l = rlp.decode_lazy(rlp_data) return TransientBlock.init_from_rlp(l).to_block() @@ -242,7 +249,7 @@ def block_from_rlp(this, rlp_data): self.console_locals = dict(eth=Eth(self.app), solidity=solc_wrapper, serpent=serpent, denoms=denoms, true=True, false=False, Eth=Eth) - for k, v in self.app.script_globals.items(): + for k, v in list(self.app.script_globals.items()): self.console_locals[k] = v def _run(self): @@ -261,8 +268,8 @@ def _run(self): if hasattr(self.console_locals['eth'].app, 'apps'): print('\n' * 2 + bc.OKGREEN) print("Hint:" + bc.OKBLUE) - print('\tOther nodes are accessible from {}`eth.app.apps`{}').format( - bc.HEADER, bc.OKBLUE) + print(('\tOther nodes are accessible from {}`eth.app.apps`{}').format( + bc.HEADER, bc.OKBLUE)) print('\tThey where automatically assigned to:') print("\t`{}eth1{}`".format( bc.HEADER, bc.OKBLUE)) @@ -284,7 +291,7 @@ def _run(self): if isinstance(handler, StreamHandler) and handler.stream == sys.stderr: root.removeHandler(handler) - stream = cStringIO.StringIO() + stream = io.StringIO() handler = StreamHandler(stream=stream) handler.formatter = Formatter("%(levelname)s:%(name)s %(message)s") root.addHandler(handler) @@ -298,15 +305,15 @@ def lastlog(n=10, prefix=None, level=None): """ lines = (stream.getvalue().strip().split('\n') or []) if prefix: - lines = filter(lambda line: line.split(':')[1].startswith(prefix), lines) + lines = [line for line in lines if line.split(':')[1].startswith(prefix)] if level: - lines = filter(lambda line: line.split(':')[0] == level, lines) + lines = [line for line in lines if line.split(':')[0] == level] for line in lines[-n:]: print(line) self.console_locals['lastlog'] = lastlog - err = cStringIO.StringIO() + err = io.StringIO() sys.stderr = err def lasterr(n=1): diff --git a/pyethapp/db_service.py b/pyethapp/db_service.py index 405428ee..ec756070 100644 --- a/pyethapp/db_service.py +++ b/pyethapp/db_service.py @@ -1,9 +1,10 @@ # -*- coding: utf8 -*- +from __future__ import absolute_import from devp2p.service import BaseService from ethereum.db import BaseDB from ethereum.slogging import get_logger -from ephemdb_service import EphemDB +from .ephemdb_service import EphemDB log = get_logger('db') @@ -11,21 +12,21 @@ dbs['EphemDB'] = EphemDB try: - from leveldb_service import LevelDBService + from .leveldb_service import LevelDBService except ImportError: pass else: dbs['LevelDB'] = LevelDBService -try: - from codernitydb_service import CodernityDB -except ImportError: - pass -else: - dbs['CodernityDB'] = CodernityDB +# try: +# from .codernitydb_service import CodernityDB +# except ImportError: +# pass +# else: +# dbs['CodernityDB'] = CodernityDB try: - from lmdb_service import LmDBService + from .lmdb_service import LmDBService except ImportError: pass else: diff --git a/pyethapp/eth_protocol.py b/pyethapp/eth_protocol.py index ab999f2b..a1a71e76 100644 --- a/pyethapp/eth_protocol.py +++ b/pyethapp/eth_protocol.py @@ -1,7 +1,12 @@ from devp2p.protocol import BaseProtocol, SubProtocolError from ethereum.transactions import Transaction from ethereum.block import Block, BlockHeader -from ethereum.utils import hash32, int_to_big_endian, big_endian_to_int +from ethereum.utils import ( + hash32, + int_to_big_endian, + big_endian_to_int, + encode_hex +) import rlp import gevent import time @@ -48,7 +53,7 @@ def hex_hash(self): return self.header.hex_hash def __repr__(self): - return '' % (self.header.number, self.header.hash.encode('hex')[:8]) + return '' % (self.header.number, encode_hex(self.header.hash)[:8]) class ETHProtocolError(SubProtocolError): @@ -167,7 +172,7 @@ class getblockheaders(BaseProtocol.command): ] def create(self, proto, hash_or_number, amount, skip=0, reverse=1): - if isinstance(hash_or_number, (int, long)): + if isinstance(hash_or_number, int): block = int_to_big_endian(hash_or_number) else: block = hash_or_number @@ -244,7 +249,6 @@ class newblock(BaseProtocol.command): @classmethod def decode_payload(cls, rlp_data): # convert to dict - # print rlp_data.encode('hex') ll = rlp.decode_lazy(rlp_data) assert len(ll) == 2 transient_block = TransientBlock.init_from_rlp(ll[0], time.time()) diff --git a/pyethapp/eth_service.py b/pyethapp/eth_service.py index f5e8810d..a8bb3620 100644 --- a/pyethapp/eth_service.py +++ b/pyethapp/eth_service.py @@ -1,4 +1,10 @@ # -*- coding: utf8 -*- +from __future__ import absolute_import +from __future__ import division +from builtins import str +from builtins import range +from past.utils import old_div +from builtins import object import copy import time import statistics @@ -28,10 +34,13 @@ from ethereum.exceptions import InvalidTransaction, InvalidNonce, \ InsufficientBalance, InsufficientStartGas, VerificationFailed from ethereum.transactions import Transaction -from ethereum.utils import encode_hex +from ethereum.utils import ( + encode_hex, + to_string, +) -from synchronizer import Synchronizer -import eth_protocol +from .synchronizer import Synchronizer +from . import eth_protocol from pyethapp import sentry from pyethapp.dao import is_dao_challenge, build_dao_header @@ -115,6 +124,7 @@ class ChainService(WiredService): block_queue_size = 2048 processed_gas = 0 processed_elapsed = 0 + process_time_queue_period = 5 def __init__(self, app): self.config = app.config @@ -136,8 +146,8 @@ def __init__(self, app): self.db.put("I am not pruning", "1") if 'network_id' in self.db: - db_network_id = self.db.get('network_id') - if db_network_id != str(sce['network_id']): + db_network_id = self.db.get(b'network_id') + if db_network_id != to_string(sce['network_id']): raise RuntimeError( "The database in '{}' was initialized with network id {} and can not be used " "when connecting to network id {}. Please choose a different data directory.".format( @@ -146,7 +156,7 @@ def __init__(self, app): ) else: - self.db.put('network_id', str(sce['network_id'])) + self.db.put(b'network_id', to_string(sce['network_id'])) self.db.commit() assert self.db is not None @@ -163,8 +173,7 @@ def __init__(self, app): env=env, genesis=genesis_data, coinbase=coinbase, new_head_cb=self._on_new_head) header = self.chain.state.prev_headers[0] - - log.info('chain at', number=self.chain.head.number) + log.info('chain at', number=header.number) if 'genesis_hash' in sce: assert sce['genesis_hash'] == self.chain.genesis.hex_hash, \ "Genesis hash mismatch.\n Expected: %s\n Got: %s" % ( @@ -187,6 +196,7 @@ def __init__(self, app): self.broadcast_filter = DuplicatesFilter() self.on_new_head_cbs = [] self.newblock_processing_times = deque(maxlen=1000) + gevent.spawn_later(self.process_time_queue_period, self.process_time_queue) @property def is_syncing(self): @@ -200,6 +210,14 @@ def is_mining(self): return self.app.services.validator.active return False + def process_time_queue(self): + try: + self.chain.process_time_queue() + except Exception as e: + log.info(str(e)) + finally: + gevent.spawn_later(self.process_time_queue_period, self.process_time_queue) + # TODO: Move to pyethereum def get_receipts(self, block): # Receipts are no longer stored in the database, so need to generate @@ -373,7 +391,7 @@ def gpsec(self, gas_spent=0, elapsed=0): if gas_spent: self.processed_gas += gas_spent self.processed_elapsed += elapsed - return int(self.processed_gas / (0.001 + self.processed_elapsed)) + return int(old_div(self.processed_gas, (0.001 + self.processed_elapsed))) def broadcast_newblock(self, block, chain_difficulty=None, origin=None): if not chain_difficulty: @@ -398,6 +416,64 @@ def broadcast_transaction(self, tx, origin=None): else: log.debug('already broadcasted tx') + def query_headers(self, hash_mode, max_hashes, skip, reverse, origin_hash=None, number=None): + headers = [] + unknown = False + while not unknown and len(headers) < max_hashes: + if hash_mode: + if not origin_hash: + break + block = self.chain.get_block(origin_hash) + if not block: + break + # If reached genesis, stop + if block.number == 0: + break + origin = block.header + else: + # If reached genesis, stop + if number is None or number == 0: + break + block = self.chain.get_block_by_number(number) + if block is None: + break + origin = block.header + + headers.append(origin) + + if hash_mode: # hash traversal + if reverse: + for i in range(skip+1): + try: + block = self.chain.get_block(origin_hash) + if block: + origin_hash = block.prevhash + else: + unknown = True + break + except KeyError: + unknown = True + break + else: + blockhash = self.chain.get_blockhash_by_number(origin.number + skip + 1) + try: + # block = self.chain.get_block(blockhash) + if block and self.chain.get_blockhashes_from_hash(blockhash, skip+1)[skip] == origin_hash: + origin_hash = blockhash + else: + unknown = True + except KeyError: + unknown = True + else: # number traversal + if reverse: + if number >= (skip + 1): + number -= (skip + 1) + else: + unknown = True + else: + number += (skip + 1) + return headers + # wire protocol receivers ########### def on_wire_protocol_start(self, proto): @@ -447,7 +523,7 @@ def on_receive_status(self, proto, eth_version, network_id, chain_difficulty, ch # check genesis if genesis_hash != self.chain.genesis.hash: - log.warn("invalid genesis hash", remote_id=proto, genesis=genesis_hash.encode('hex')) + log.warn("invalid genesis hash", remote_id=proto, genesis=encode_hex(genesis_hash)) raise eth_protocol.ETHProtocolError('wrong genesis block') # initiate DAO challenge @@ -513,59 +589,19 @@ def on_receive_getblockheaders(self, proto, hash_or_number, block, amount, skip, origin_hash = self.chain.get_blockhash_by_number(hash_or_number[1]) except KeyError: origin_hash = b'' - if not origin_hash or self.chain.has_blockhash(origin_hash): - log.debug("unknown block") + if not origin_hash or not self.chain.has_blockhash(origin_hash): + log.debug('unknown block: {}'.format(encode_hex(origin_hash))) proto.send_blockheaders(*[]) return - unknown = False - while not unknown and (headers) < max_hashes: - if not origin_hash: - break - try: - block_rlp = self.chain.db.get(last) - if block_rlp == 'GENESIS': - #last = self.chain.genesis.header.prevhash - break - else: - last = rlp.decode_lazy(block_rlp)[0][0] # [head][prevhash] - except KeyError: - break - assert origin - headers.append(origin) - - if hash_mode: # hash traversal - if reverse: - for i in xrange(skip+1): - try: - header = self.chain.get_block(origin_hash) - origin_hash = header.prevhash - except KeyError: - unknown = True - break - else: - origin_hash = self.chain.get_blockhash_by_number(origin.number + skip + 1) - try: - header = self.chain.get_block(origin_hash) - if self.chain.get_blockhashes_from_hash(header.hash, skip+1)[skip] == origin_hash: - origin_hash = header.hash - else: - unknown = True - except KeyError: - unknown = True - else: # number traversal - if reverse: - if origin.number >= (skip+1): - number = origin.number - (skip + 1) - origin_hash = self.chain.get_blockhash_by_number(number) - else: - unknown = True - else: - number = origin.number + skip + 1 - try: - origin_hash = self.chain.get_blockhash_by_number(number) - except KeyError: - unknown = True + headers = self.query_headers( + hash_mode, + max_hashes, + skip, + reverse, + origin_hash=origin_hash, + number=block_id, + ) log.debug("sending: found blockheaders", count=len(headers)) proto.send_blockheaders(*headers) @@ -591,7 +627,7 @@ def on_receive_getblockbodies(self, proto, blockhashes): found = [] for bh in blockhashes[:self.wire_protocol.max_getblocks_count]: try: - found.append(self.chain.db.get(bh)) + found.append(self.chain.get_block(bh)) except KeyError: log.debug("unknown block requested", block_hash=encode_hex(bh)) if found: diff --git a/pyethapp/ipc_rpc.py b/pyethapp/ipc_rpc.py index 3b4cc43f..813428f7 100644 --- a/pyethapp/ipc_rpc.py +++ b/pyethapp/ipc_rpc.py @@ -1,7 +1,8 @@ #!/usr/bin/env python """Derived from https://groups.google.com/d/topic/gevent/5__B9hOup38/discussion """ -import _socket +from __future__ import print_function +import socket as _socket import os import pwd from gevent.server import StreamServer @@ -12,7 +13,7 @@ def unlink(path): from errno import ENOENT try: os.unlink(path) - except OSError, ex: + except OSError as ex: if ex.errno != ENOENT: raise @@ -21,7 +22,7 @@ def link(src, dest): from errno import ENOENT try: os.link(src, dest) - except OSError, ex: + except OSError as ex: if ex.errno != ENOENT: raise @@ -42,7 +43,7 @@ def bind_unix_listener(path, backlog=50, user=None): if user is not None: user = pwd.getpwnam(user) os.chown(tempname, user.pw_uid, user.pw_gid) - os.chmod(tempname, 0600) + os.chmod(tempname, 0o600) sock.listen(backlog) try: os.rename(tempname, path) @@ -61,8 +62,8 @@ def bind_unix_listener(path, backlog=50, user=None): def handle(socket, address): - print socket, address - print socket.recv(4096) + print(socket, address) + print(socket.recv(4096)) socket.sendall('pong\n') @@ -71,6 +72,6 @@ def serve(sock, handler=handle): if __name__ == "__main__": path = os.path.join(tempfile.gettempdir(), "echo.ipc") - print "Starting echo sever..." - print "Test it with:\n\techo 'ping'| socat - {}".format(path) + print("Starting echo sever...") + print("Test it with:\n\techo 'ping'| socat - {}".format(path)) serve(bind_unix_listener(path)) diff --git a/pyethapp/jsonrpc.py b/pyethapp/jsonrpc.py index e46beecc..ceac8d17 100644 --- a/pyethapp/jsonrpc.py +++ b/pyethapp/jsonrpc.py @@ -1,4 +1,12 @@ +from __future__ import print_function +from __future__ import absolute_import +from builtins import zip +from builtins import str +from builtins import map +from builtins import range +from builtins import object import os +import sys import inspect from copy import deepcopy from collections import Iterable @@ -19,12 +27,12 @@ import gevent.wsgi import rlp from decorator import decorator -from accounts import Account +from .accounts import Account from devp2p.service import BaseService from ethereum.exceptions import InvalidTransaction from ethereum.trie import Trie -from eth_protocol import ETHProtocol -from ipc_rpc import bind_unix_listener, serve +from .eth_protocol import ETHProtocol +from .ipc_rpc import bind_unix_listener, serve from tinyrpc.dispatch import public as public_ from tinyrpc.dispatch import RPCDispatcher from tinyrpc.exc import BadRequestError, MethodNotFoundError @@ -47,6 +55,10 @@ def _fail_on_error_dispatch(self, request): return request.respond(result) +def is_json_string(data): + return (sys.version_info >= (3,) and isinstance(data, str)) or \ + (sys.version_info.major == 2 and isinstance(data, unicode)) + PROPAGATE_ERRORS = False if PROPAGATE_ERRORS: RPCDispatcher._dispatch = _fail_on_error_dispatch @@ -87,8 +99,8 @@ def new_f(*args, **kwargs): raise JSONRPCInvalidParamsError(t) else: return f(*args, **kwargs) - new_f.func_name = f.func_name - new_f.func_doc = f.func_doc + new_f.__name__ = f.__name__ + new_f.__doc__ = f.__doc__ return public_(new_f) @@ -347,7 +359,8 @@ def register(cls, json_rpc_service): def quantity_decoder(data): """Decode `data` representing a quantity.""" - if not is_string(data): + # [NOTE]: decode to `str` for both python2 and python3 + if not is_json_string(data): success = False elif not data.startswith('0x'): success = False # must start with 0x prefix @@ -370,7 +383,7 @@ def quantity_encoder(i): """Encode integer quantity `data`.""" assert is_numeric(i) data = int_to_big_endian(i) - return '0x' + (encode_hex(data).lstrip('0') or '0') + return str('0x' + (encode_hex(data).lstrip('0') or '0')) def data_decoder(data): @@ -392,14 +405,14 @@ def data_decoder(data): def data_encoder(data, length=None): """Encode unformatted binary `data`. - If `length` is given, the result will be padded like this: ``data_encoder('\xff', 3) == + If `length` is given, the result will be padded like this: ``data_encoder('b\xff', 3) == '0x0000ff'``. """ s = encode_hex(data) if length is None: - return '0x' + s + return str('0x' + s) else: - return '0x' + s.rjust(length * 2, '0') + return str('0x' + s.rjust(length * 2, '0')) def address_decoder(data): @@ -412,8 +425,8 @@ def address_decoder(data): def address_encoder(address): assert len(address) in (20, 0) - return '0x' + encode_hex(address) - + result = str('0x' + encode_hex(address)) + return result def block_id_decoder(data): """Decode a block identifier as expected from :meth:`JSONRPCServer.get_block`.""" @@ -642,11 +655,11 @@ def unlockAccount(self, account_address, passwd, duration): @encode_res(address_encoder) def newAccount(self, passwd): account = Account.new(passwd) - account.path = os.path.join(self.app.services.accounts.keystore_dir, account.address.encode('hex')) + account.path = os.path.join(self.app.services.accounts.keystore_dir, encode_hex(account.address)) self.app.services.accounts.add_account(account) account.lock() assert account.locked - assert self.app.services.accounts.find(account.address.encode('hex')) + assert self.app.services.accounts.find(encode_hex(account.address)) return account.address @@ -722,7 +735,7 @@ def compilers(self): @public def getCompilers(self): - return self.compilers.keys() + return list(self.compilers.keys()) @public def compileSolidity(self, code): @@ -839,7 +852,7 @@ def syncing(self): currentBlock=self.chain.chain.head.number, highestBlock=synctask.end_block_number, ) - return {k: quantity_encoder(v) for k, v in result.items()} + return {k: quantity_encoder(v) for k, v in list(result.items())} @public @encode_res(quantity_encoder) @@ -1004,7 +1017,7 @@ def getUncleByBlockNumberAndIndex(self, block_id, index): @public def getWork(self): - print 'Sending work...' + print('Sending work...') h = self.chain.head_candidate return [ encode_hex(h.header.mining_hash), @@ -1014,7 +1027,7 @@ def getWork(self): @public def test(self, nonce): - print 80808080808 + print(80808080808) return nonce @public @@ -1035,24 +1048,24 @@ def lastGasPrice(self): @decode_arg('mining_hash', data_decoder) @decode_arg('mix_digest', data_decoder) def submitWork(self, nonce, mining_hash, mix_digest): - print 'submitting work' + print('submitting work') h = self.chain.head_candidate - print 'header: %s' % encode_hex(rlp.encode(h)) + print('header: %s' % encode_hex(rlp.encode(h))) if h.header.mining_hash != mining_hash: return False - print 'mining hash: %s' % encode_hex(mining_hash) - print 'nonce: %s' % encode_hex(nonce) - print 'mixhash: %s' % encode_hex(mix_digest) - print 'seed: %s' % encode_hex(h.header.seed) + print('mining hash: %s' % encode_hex(mining_hash)) + print('nonce: %s' % encode_hex(nonce)) + print('mixhash: %s' % encode_hex(mix_digest)) + print('seed: %s' % encode_hex(h.header.seed)) h.header.nonce = nonce h.header.mixhash = mix_digest if not self.chain.check_header(h.header): - print 'PoW check false' + print('PoW check false') return False - print 'PoW check true' + print('PoW check true') self.chain.chain.add_block(h) self.chain.broadcast_newblock(h) - print 'Added: %d' % h.header.number + print('Added: %d' % h.header.number) return True @public @@ -1158,26 +1171,10 @@ def sendRawTransaction(self, data): @decode_arg('block_id', block_id_decoder) @encode_res(data_encoder) def call(self, data, block_id='pending'): - block = self.json_rpc_server.get_block(block_id) - snapshot_before = block.snapshot() - tx_root_before = snapshot_before['txs'].root_hash # trie object in snapshot is mutable + if block_id != 'pending': + raise Exception("Only pending supported right now") - # rebuild block state before finalization - if block.has_parent(): - parent = self.chain.get_parent(block) - test_block = block.init_from_parent(parent, block.coinbase, - timestamp=block.timestamp) - for tx in block.get_transactions(): - success, output = apply_transaction(test_block, tx) - assert success - else: - env = Env(db=block.db) - test_block = mk_genesis_block(env) - original = {key: value for key, value in snapshot_before.items() if key != 'txs'} - original = deepcopy(original) - original['txs'] = Trie(snapshot_before['txs'].db, snapshot_before['txs'].root_hash) - test_block = mk_genesis_block(env) - test_block.revert(original) + prestate = self.app.services.chain.state.ephemeral_clone() # validate transaction if not isinstance(data, dict): @@ -1202,21 +1199,17 @@ def call(self, data, block_id='pending'): try: sender = address_decoder(data['from']) except KeyError: - sender = '\x00' * 20 + sender = b'\x00' * 20 # apply transaction - nonce = test_block.get_nonce(sender) + nonce = prestate.get_nonce(sender) tx = Transaction(nonce, gasprice, startgas, to, value, data_) tx.sender = sender try: - success, output = apply_transaction(test_block, tx) + success, output = apply_transaction(prestate, tx) except InvalidTransaction: success = False - # make sure we didn't change the real state - snapshot_after = block.snapshot() - assert snapshot_after == snapshot_before - assert snapshot_after['txs'].root_hash == tx_root_before if success: return output @@ -1242,7 +1235,7 @@ def estimateGas(self, data, block_id='pending'): else: env = Env(db=block.db) test_block = mk_genesis_block(env) - original = {key: value for key, value in snapshot_before.items() if key != 'txs'} + original = {key: value for key, value in list(snapshot_before.items()) if key != 'txs'} original = deepcopy(original) original['txs'] = Trie(snapshot_before['txs'].db, snapshot_before['txs'].root_hash) test_block = mk_genesis_block(env) @@ -1271,7 +1264,7 @@ def estimateGas(self, data, block_id='pending'): try: sender = address_decoder(data['from']) except KeyError: - sender = '\x00' * 20 + sender = b'\x00' * 20 # apply transaction nonce = test_block.get_nonce(sender) @@ -1360,7 +1353,7 @@ def check(self): # skip blocks that have already been checked if self.last_block_checked is not None: - print self.last_block_checked + print(self.last_block_checked) first = max(self.last_block_checked.number + 1, first) if first > last: return {} @@ -1403,21 +1396,21 @@ def check(self): # In case of the frequent usage of multiple 'or' statements in the filter # the following logic should be optimized so that the fewer amount of blocks gets checked. # It is currently optimal for filters with a single 'or' statement. - _topic_and_bloom = bloom.bloom_from_list(map(int32.serialize, and_topics or [])) + _topic_and_bloom = bloom.bloom_from_list(list(map(int32.serialize, and_topics or []))) bloom_passed = False for or_t in or_topics: - or_bl = bloom.bloom_from_list(map(int32.serialize, [or_t])) + or_bl = bloom.bloom_from_list(list(map(int32.serialize, [or_t]))) if bloom.bloom_combine(_bloom, _topic_and_bloom, or_bl) == _bloom: bloom_passed = True break if not bloom_passed: continue else: - _topic_bloom = bloom.bloom_from_list(map(int32.serialize, self.topics or [])) + _topic_bloom = bloom.bloom_from_list(list(map(int32.serialize, self.topics or []))) if bloom.bloom_combine(_bloom, _topic_bloom) != _bloom: continue block = self.chain.get_block(block) - print 'bloom filter passed' + print('bloom filter passed') logger.debug('-') logger.debug('with block', block=block) receipts = self.chainservice.get_receipts(block) @@ -1446,11 +1439,11 @@ def check(self): continue # still here, so match was successful => add to log list tx = block.transactions[r_idx] - id_ = sha3(''.join((tx.hash, str(l_idx)))) + id_ = sha3(''.join((encode_hex(tx.hash), str(l_idx)))) pending = block == self.chainservice.head_candidate r = dict(log=log, log_idx=l_idx, block=block, txhash=tx.hash, tx_idx=r_idx, pending=pending) - logger.debug('FOUND LOG', id=id_.encode('hex')) + logger.debug('FOUND LOG', id=encode_hex(id_)) new_logs[id_] = r # (log, i, block) # don't check blocks again, that have been checked already and won't change anymore self.last_block_checked = blocks_to_check[-1] @@ -1460,19 +1453,19 @@ def check(self): self.last_block_checked = blocks_to_check[-2] if len(blocks_to_check) >= 2 else None if self.last_block_checked and not isinstance(self.last_block_checked, Block): self.last_block_checked = self.chain.get_block(self.last_block_checked) - actually_new_ids = new_logs.viewkeys() - self.log_dict.viewkeys() + actually_new_ids = list(set(new_logs.keys()) - set(self.log_dict.keys())) self.log_dict.update(new_logs) return {id_: new_logs[id_] for id_ in actually_new_ids} @property def logs(self): self.check() - return self.log_dict.values() + return list(self.log_dict.values()) @property def new_logs(self): d = self.check() - return d.values() + return list(d.values()) class BlockFilter(object): @@ -1670,7 +1663,7 @@ def trace_block(self, blockhash, exclude=[]): blk = self.app.services.chain.chain.get(blockhash) for i in range(blk.transaction_count): tx_lst_serialized, sr, _ = blk.get_transaction(i) - txhash_hex = sha3(rlp.encode(tx_lst_serialized)).encode('hex') + txhash_hex = encode_hex(sha3(rlp.encode(tx_lst_serialized))) txs.append(self.trace_transaction(txhash_hex, exclude)) return txs @@ -1726,9 +1719,9 @@ def getTransactionReceipt(self, tx_hash): def show_methods(dispatcher, prefix=''): # https://github.com/micheles/decorator/blob/3.4.1/documentation.rst - for name, method in dispatcher.method_map.items(): - print prefix + name, inspect.formatargspec(public_methods[name]) - for sub_prefix, subdispatcher_list in dispatcher.subdispatchers.items(): + for name, method in list(dispatcher.method_map.items()): + print(prefix + name, inspect.formatargspec(public_methods[name])) + for sub_prefix, subdispatcher_list in list(dispatcher.subdispatchers.items()): for sub in subdispatcher_list: show_methods(sub, prefix + sub_prefix) diff --git a/pyethapp/leveldb_service.py b/pyethapp/leveldb_service.py index 063fb1c8..88028626 100644 --- a/pyethapp/leveldb_service.py +++ b/pyethapp/leveldb_service.py @@ -1,14 +1,19 @@ import os +import sys from devp2p.service import BaseService from ethereum.db import BaseDB from gevent.event import Event +from gevent.hub import getcurrent import leveldb from ethereum import slogging +from ethereum.utils import encode_hex +import random slogging.set_level('db', 'debug') log = slogging.get_logger('db') compress = decompress = lambda x: x +PY3 = sys.version_info >= (3,) """ @@ -79,29 +84,41 @@ def reopen(self): self.db = leveldb.LevelDB(self.dbfile) def get(self, key): - log.trace('getting entry', key=key.encode('hex')[:8]) + log.trace('getting entry', key=encode_hex(key)[:8]) if key in self.uncommitted: if self.uncommitted[key] is None: raise KeyError("key not in db") log.trace('from uncommitted') return self.uncommitted[key] log.trace('from db') - o = decompress(self.db.Get(key)) + + if PY3: + if isinstance(key, str): + key = key.encode() + o = bytes(self.db.Get(key)) + else: + o = decompress(self.db.Get(key)) self.uncommitted[key] = o return o def put(self, key, value): - log.trace('putting entry', key=key.encode('hex')[:8], len=len(value)) + log.trace('putting entry', key=encode_hex(key)[:8], len=len(value)) self.uncommitted[key] = value def commit(self): log.debug('committing', db=self) batch = leveldb.WriteBatch() - for k, v in self.uncommitted.items(): + for k, v in list(self.uncommitted.items()): if v is None: batch.Delete(k) else: - batch.Put(k, compress(v)) + compress_v = compress(v) + if PY3: + if isinstance(k, str): + k = k.encode() + if isinstance(compress_v, str): + compress_v = compress_v.encode() + batch.Put(k, compress_v) self.db.Write(batch, sync=False) self.uncommitted.clear() log.debug('committed', db=self, num=len(self.uncommitted)) @@ -119,6 +136,9 @@ def _has_key(self, key): return True except KeyError: return False + except Exception as e: + log.info('key: {}, type(key):{}'.format(key, type(key))) + raise def __contains__(self, key): return self._has_key(key) @@ -163,6 +183,7 @@ def __init__(self, app): self.stop_event = Event() dbfile = os.path.join(self.app.config['data_dir'], 'leveldb') LevelDB.__init__(self, dbfile) + self.h = random.randrange(10**50) def _run(self): self.stop_event.wait() @@ -171,3 +192,6 @@ def stop(self): self.stop_event.set() # commit? log.debug('closing db') + + def __hash__(self): + return self.h diff --git a/pyethapp/lmdb_service.py b/pyethapp/lmdb_service.py index 287c5aea..ec98b4a2 100644 --- a/pyethapp/lmdb_service.py +++ b/pyethapp/lmdb_service.py @@ -83,13 +83,13 @@ def get(self, key): def commit(self): keys_to_delete = ( key - for key, value in self.uncommitted.items() + for key, value in list(self.uncommitted.items()) if value is DELETE ) items_to_insert = ( (key, value) - for key, value in self.uncommitted.items() + for key, value in list(self.uncommitted.items()) if value not in (DELETE, NULL) # NULL shouldn't happen ) diff --git a/pyethapp/pow_service.py b/pyethapp/pow_service.py index a66bec66..bd8015ba 100644 --- a/pyethapp/pow_service.py +++ b/pyethapp/pow_service.py @@ -1,3 +1,6 @@ +from __future__ import division +from builtins import object +from past.utils import old_div import time import gevent import gipc @@ -38,15 +41,15 @@ def _run(self): log_sub.info('nonce found') self.nonce_callback(bin_nonce, mixhash, self.mining_hash) break - delay = elapsed * (1 - self.cpu_pct / 100.) + delay = elapsed * (1 - old_div(self.cpu_pct, 100.)) hashrate = int(self.rounds // (elapsed + delay)) self.hashrate_callback(hashrate) log_sub.trace('sleeping', delay=delay, elapsed=elapsed, rounds=self.rounds) gevent.sleep(delay + 0.001) nonce += self.rounds # adjust - adjust = elapsed / self.max_elapsed - self.rounds = int(self.rounds / adjust) + adjust = old_div(elapsed, self.max_elapsed) + self.rounds = int(old_div(self.rounds, adjust)) log_sub.debug('mining task finished', is_stopped=self.is_stopped) @@ -147,10 +150,11 @@ def recv_hashrate(self, hashrate): self.hashrate = hashrate def recv_found_nonce(self, bin_nonce, mixhash, mining_hash): - log.info('nonce found', mining_hash=mining_hash.encode('hex')) + log.info('nonce found: {}'.format(encode_hex(mining_hash))) block = self.chain.head_candidate if block.mining_hash != mining_hash: log.warn('mining_hash does not match') + gevent.spawn_later(0.5, self.mine_head_candidate) return False block.header.mixhash = mixhash block.header.nonce = bin_nonce diff --git a/pyethapp/profiles.py b/pyethapp/profiles.py index 00d691eb..83d5a4e7 100644 --- a/pyethapp/profiles.py +++ b/pyethapp/profiles.py @@ -33,7 +33,12 @@ 'enode://2676755dd8477ad3beea32b4e5a144fa10444b70dfa3e05effb0fdfa75683ebd' '4f75709e1f8126cb5317c5a35cae823d503744e790a3a038ae5dd60f51ee9101' '@144.76.62.101:30303' - ) + ), + ( + 'enode://c5f596e7465d8b677efacc2de673f556d1839e13815383f5d323c90861cf5d630' + '45201f86fe993b91691b9d757d77f98dea0c2fb05ada778ee4a762550d93062' + '@66.172.12.251:30303' + ), ] }, }, diff --git a/pyethapp/rpc_client.py b/pyethapp/rpc_client.py index fe964e20..f846f245 100644 --- a/pyethapp/rpc_client.py +++ b/pyethapp/rpc_client.py @@ -1,4 +1,8 @@ """ A simple way of interacting to a ethereum node through JSON RPC commands. """ +from __future__ import print_function +from builtins import map +from builtins import str +from builtins import object import logging import warnings import json @@ -7,7 +11,14 @@ from ethereum.abi import ContractTranslator from ethereum.tools.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.utils import ( + denoms, + int_to_big_endian, + big_endian_to_int, + normalize_address, + decode_hex, + encode_hex, +) from ethereum.tools._solidity import solidity_unresolved_symbols, solidity_library_symbol, solidity_resolve_symbols from tinyrpc.protocols.jsonrpc import JSONRPCErrorResponse, JSONRPCSuccessResponse from tinyrpc.protocols.jsonrpc import JSONRPCProtocol @@ -22,7 +33,7 @@ # 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 -z_address = '\x00' * 20 +z_address = b'\x00' * 20 log = logging.getLogger(__name__) @@ -45,7 +56,7 @@ def block_tag_encoder(val): def topic_encoder(topic): - assert isinstance(topic, (int, long)) + assert isinstance(topic, int) return data_encoder(int_to_big_endian(topic)) @@ -65,7 +76,7 @@ def deploy_dependencies_symbols(all_contract): symbols_to_contract[symbol] = contract_name - for contract_name, contract in all_contract.items(): + for contract_name, contract in list(all_contract.items()): unresolved_symbols = solidity_unresolved_symbols(contract['bin_hex']) dependencies[contract_name] = [ symbols_to_contract[unresolved] @@ -161,7 +172,7 @@ def blocknumber(self): def nonce(self, address): if len(address) == 40: - address = address.decode('hex') + address = decode_hex(address) try: res = self.call('eth_nonce', address_encoder(address), 'pending') @@ -219,7 +230,7 @@ def deploy_solidity_contract(self, sender, contract_name, all_contracts, # pyli symbols = solidity_unresolved_symbols(contract['bin_hex']) if symbols: - available_symbols = map(solidity_library_symbol, all_contracts.keys()) # pylint: disable=bad-builtin + available_symbols = list(map(solidity_library_symbol, list(all_contracts.keys()))) # pylint: disable=bad-builtin unknown_symbols = set(symbols) - set(available_symbols) if unknown_symbols: @@ -240,7 +251,7 @@ def deploy_solidity_contract(self, sender, contract_name, all_contracts, # pyli dependency_contract = all_contracts[deploy_contract] hex_bytecode = solidity_resolve_symbols(dependency_contract['bin_hex'], libraries) - bytecode = hex_bytecode.decode('hex') + bytecode = decode_hex(hex_bytecode) dependency_contract['bin_hex'] = hex_bytecode dependency_contract['bin'] = bytecode @@ -251,7 +262,7 @@ def deploy_solidity_contract(self, sender, contract_name, all_contracts, # pyli data=bytecode, gasprice=gasprice, ) - transaction_hash = transaction_hash_hex.decode('hex') + transaction_hash = decode_hex(transaction_hash_hex) self.poll(transaction_hash, timeout=timeout) receipt = self.eth_getTransactionReceipt(transaction_hash) @@ -261,13 +272,13 @@ def deploy_solidity_contract(self, sender, contract_name, all_contracts, # pyli libraries[deploy_contract] = contract_address - deployed_code = self.eth_getCode(contract_address.decode('hex')) + deployed_code = self.eth_getCode(decode_hex(contract_address)) if deployed_code == '0x': raise RuntimeError("Contract address has no code, check gas usage.") hex_bytecode = solidity_resolve_symbols(contract['bin_hex'], libraries) - bytecode = hex_bytecode.decode('hex') + bytecode = decode_hex(hex_bytecode) contract['bin_hex'] = hex_bytecode contract['bin'] = bytecode @@ -285,13 +296,13 @@ def deploy_solidity_contract(self, sender, contract_name, all_contracts, # pyli data=bytecode, gasprice=gasprice, ) - transaction_hash = transaction_hash_hex.decode('hex') + transaction_hash = decode_hex(transaction_hash_hex) self.poll(transaction_hash, timeout=timeout) receipt = self.eth_getTransactionReceipt(transaction_hash) contract_address = receipt['contractAddress'] - deployed_code = self.eth_getCode(contract_address[2:].decode('hex')) + deployed_code = self.eth_getCode(decode_hex(contract_address[2:])) if deployed_code == '0x': raise RuntimeError("Deployment of {} failed. Contract address has no code, check gas usage.".format( @@ -352,7 +363,7 @@ def filter_changes(self, fid): blockNumber=quantity_decoder, logIndex=quantity_decoder, transactionIndex=quantity_decoder) - return [{k: decoders[k](v) for k, v in c.items() if v is not None} for c in changes] + return [{k: decoders[k](v) for k, v in list(c.items()) if v is not None} for c in changes] def call(self, method, *args): """ Do the request and returns the result. @@ -368,8 +379,8 @@ 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 + print(json.dumps(json.loads(request.serialize()), indent=2)) + print(reply) jsonrpc_reply = self.protocol.parse_reply(reply) if isinstance(jsonrpc_reply, JSONRPCSuccessResponse): @@ -429,7 +440,7 @@ def send_transaction(self, sender, to, value=0, data='', startgas=0, res = self.eth_sendTransaction(**tx_dict) assert len(res) in (20, 32) - return res.encode('hex') + return encode_hex(res) def eth_sendTransaction(self, nonce=None, sender='', to='', value=0, data='', gasPrice=default_gasprice, gas=default_startgas, diff --git a/pyethapp/sentry.py b/pyethapp/sentry.py index 966a9026..8e5a45ec 100644 --- a/pyethapp/sentry.py +++ b/pyethapp/sentry.py @@ -1,3 +1,6 @@ +from future import standard_library +standard_library.install_aliases() +from builtins import str from ethereum import utils import random, rlp, sys try: diff --git a/pyethapp/synchronizer.py b/pyethapp/synchronizer.py index 16d3988c..7b06fa80 100644 --- a/pyethapp/synchronizer.py +++ b/pyethapp/synchronizer.py @@ -1,4 +1,3 @@ -#import sys from __future__ import print_function from __future__ import absolute_import from builtins import str @@ -9,7 +8,7 @@ from .eth_protocol import TransientBlockBody, TransientBlock from ethereum.block import BlockHeader from ethereum.slogging import get_logger -import ethereum.utils as utils +from ethereum.utils import encode_hex import traceback import queue as Q from gevent.queue import Queue @@ -203,7 +202,6 @@ def fetch_headerbatch(self,origin,skeleton): self.headertask_queue.put((index,index)) while True: - # requests = iter(self.batch_requests) deferred = AsyncResult() self.header_request=deferred @@ -414,6 +412,7 @@ def __init__(self, synchronizer, blockhash, chain_difficulty=0, originator_only= gevent.spawn(self.run) # gevent.spawn(self.schedule_block_fetch) + @property def protocols(self): if self.originator_only: @@ -453,7 +452,7 @@ def run(self): #body fetcher def schedule_block_fetch(self): batch_header = [] - log_st.debug('start sheduleing blocks') + log_st.debug('start sheduling blocks') #?? maxsize wrong?? self.synchronizer.blockheader_queue = Queue(maxsize=0) @@ -508,7 +507,7 @@ def fetch_blocks(self): if len(self.block_requests_pool) == 0: log_body_st.debug('block body fetching completed!') # return True - break + # break fetching = False task_empty = False @@ -773,7 +772,6 @@ def protocols(self): self._protocols = dict((p, cd) for p, cd in list(self._protocols.items()) if not p.is_stopped) return sorted(list(self._protocols.keys()), key=lambda p: self._protocols[p], reverse=True) - def receive_newblock(self, proto, t_block, chain_difficulty): "called if there's a newblock announced on the network" log.debug('newblock', proto=proto, block=t_block, chain_difficulty=chain_difficulty, @@ -849,8 +847,15 @@ def receive_status(self, proto, blockhash, chain_difficulty): log.debug('sufficient difficulty') self.synctask = SyncTask(self, proto, blockhash, chain_difficulty) if not self.syncbody: - self.syncbody = SyncBody(self, chain_difficulty) - + self.syncbody = SyncBody(self, chain_difficulty) + if not self.synctask: + self.synctask = SyncTask(self, proto, blockhash, chain_difficulty) + else: + log.debug('received status but already syncing, won\'t start new sync task', + proto=proto, + blockhash=encode_hex(blockhash), + chain_difficulty=chain_difficulty) + def receive_newblockhashes(self, proto, newblockhashes): """ diff --git a/pyethapp/tests/test_account_service.py b/pyethapp/tests/test_account_service.py index 97a339b7..89c4bcb0 100644 --- a/pyethapp/tests/test_account_service.py +++ b/pyethapp/tests/test_account_service.py @@ -1,13 +1,20 @@ +from builtins import str +from builtins import range import os import shutil import tempfile from uuid import uuid4 import ethereum.tools.keys from ethereum.slogging import get_logger -from ethereum.utils import decode_hex, remove_0x_head +from ethereum.utils import ( + encode_hex, + decode_hex, + remove_0x_head, +) from devp2p.app import BaseApp import pytest from pyethapp.accounts import Account, AccountsService, DEFAULT_COINBASE +from pyethapp.utils import MinType # reduce key derivation iterations @@ -136,10 +143,10 @@ def test_find(app, account): s.add_account(account, store=False) assert len(s) == 1 assert s.find('1') == account - assert s.find(account.address.encode('hex')) == account - assert s.find(account.address.encode('hex').upper()) == account - assert s.find('0x' + account.address.encode('hex')) == account - assert s.find('0x' + account.address.encode('hex').upper()) == account + assert s.find(encode_hex(account.address)) == account + assert s.find(encode_hex(account.address).upper()) == account + assert s.find('0x' + encode_hex(account.address)) == account + assert s.find('0x' + encode_hex(account.address).upper()) == account assert s.find(account.uuid) == account assert s.find(account.uuid.upper()) == account with pytest.raises(ValueError): @@ -268,14 +275,17 @@ def test_account_sorting(app): '/absolute/path/a', None ] - paths_sorted = sorted(paths) + # paths_sortable = [(x or "") for x in paths] + min_value = MinType() + paths_sorted = sorted(paths, key=lambda x: min_value if x is None else x) + # paths_sorted = sorted(paths, key=lambda x: (x is None, x)) s = app.services.accounts for path in paths: s.add_account(Account(keystore_dummy, path=path), store=False) assert [account.path for account in s.accounts] == paths_sorted - assert [s.find(str(i)).path for i in xrange(1, len(paths) + 1)] == paths_sorted + # assert [s.find(str(i)).path for i in range(1, len(paths) + 1)] == paths_sorted def test_update(app, account, password): @@ -316,7 +326,7 @@ def test_update(app, account, password): assert os.listdir(app.config['accounts']['keystore_dir']) == ['update_test'] - files = ['update_test~' + str(i) for i in xrange(20)] + files = ['update_test~' + str(i) for i in range(20)] files.append('update_test~') for filename in files: # touch files @@ -339,13 +349,13 @@ def test_coinbase(app, account): assert s.coinbase == account.address # coinbase configured - app.config['pow'] = {'coinbase_hex': account.address.encode('hex')} + app.config['pow'] = {'coinbase_hex': encode_hex(account.address)} app.config['accounts']['must_include_coinbase'] = True assert s.coinbase == account.address app.config['accounts']['must_include_coinbase'] = False assert s.coinbase == account.address - for invalid_coinbase in [123, '\x00' * 20, '\x00' * 40, '', 'aabbcc', 'aa' * 19, 'ff' * 21]: + for invalid_coinbase in [123, b'\x00' * 20, b'\x00' * 40, b'', b'aabbcc', b'aa' * 19, b'ff' * 21]: app.config['pow'] = {'coinbase_hex': invalid_coinbase} app.config['accounts']['must_include_coinbase'] = False with pytest.raises(ValueError): @@ -379,7 +389,12 @@ def test_get_by_address_no_match(app, account): s.get_by_address(account.address) assert False, "An exception should have been raised on no account match" except KeyError as e: - assert e.message == "account with address 41ad2bc63a2059f9b623533d87fe99887d794847 not found" + if hasattr(e, 'message'): + # py2 + assert e.message == "account with address 41ad2bc63a2059f9b623533d87fe99887d794847 not found" + else: + # py3 + assert "account with address 41ad2bc63a2059f9b623533d87fe99887d794847 not found" in str(e) def test_get_by_address_multiple_match(app, account, patched_logger_warning): """ @@ -391,4 +406,4 @@ def test_get_by_address_multiple_match(app, account, patched_logger_warning): s.add_account(account, store=False) s.get_by_address(account.address) patched_logger_warning.assert_called_once_with( - "multiple accounts with same address found", address=account.address.encode('hex')) + "multiple accounts with same address found", address=encode_hex(account.address)) diff --git a/pyethapp/tests/test_accounts.py b/pyethapp/tests/test_accounts.py index d27d4e43..3c0f8a3f 100644 --- a/pyethapp/tests/test_accounts.py +++ b/pyethapp/tests/test_accounts.py @@ -1,8 +1,15 @@ +from __future__ import division +from builtins import str +from past.utils import old_div import json from uuid import uuid4 import ethereum.tools.keys from ethereum.tools.keys import privtoaddr from ethereum.transactions import Transaction +from ethereum.utils import ( + decode_hex, + encode_hex, +) from pyethapp.accounts import Account import pytest @@ -12,7 +19,7 @@ @pytest.fixture() def privkey(): - return 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'.decode('hex') + return decode_hex('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') @pytest.fixture() @@ -47,7 +54,7 @@ def test_account_creation(account, password, privkey, uuid): def test_locked(keystore, uuid): account = Account(keystore) assert account.locked - assert account.address.encode('hex') == keystore['address'] + assert encode_hex(account.address) == keystore['address'] assert account.privkey is None assert account.pubkey is None assert account.uuid == uuid @@ -80,7 +87,7 @@ def test_unlock_wrong(keystore, password, privkey, uuid): account.unlock('4321' + password) assert account.locked with pytest.raises(ValueError): - account.unlock(password[:len(password) / 2]) + account.unlock(password[:old_div(len(password), 2)]) assert account.locked account.unlock(password) assert not account.locked @@ -120,7 +127,7 @@ def test_dump(account): keystore = json.loads(account.dump(include_address=True, include_id=True)) required_keys = set(['crypto', 'version']) assert set(keystore.keys()) == required_keys | set(['address', 'id']) - assert keystore['address'] == account.address.encode('hex') + assert keystore['address'] == encode_hex(account.address) assert keystore['id'] == account.uuid keystore = json.loads(account.dump(include_address=False, include_id=True)) @@ -129,7 +136,7 @@ def test_dump(account): keystore = json.loads(account.dump(include_address=True, include_id=False)) assert set(keystore.keys()) == required_keys | set(['address']) - assert keystore['address'] == account.address.encode('hex') + assert keystore['address'] == encode_hex(account.address) keystore = json.loads(account.dump(include_address=False, include_id=False)) assert set(keystore.keys()) == required_keys diff --git a/pyethapp/tests/test_app.py b/pyethapp/tests/test_app.py index cdde339c..e28db7b7 100644 --- a/pyethapp/tests/test_app.py +++ b/pyethapp/tests/test_app.py @@ -1,3 +1,4 @@ +from builtins import str import os import pytest from pyethapp import app @@ -80,12 +81,12 @@ def test_custom_config_file(param): if arg.endswith('.json'): patterns = ['genesis: {}'.format(param[1])] else: - patterns = ["{}: '{}'".format(k, v) for k, v in genesis_json.items() if k != 'alloc'] + patterns = ["{}: '{}'".format(k, v) for k, v in list(genesis_json.items()) if k != 'alloc'] for pat in patterns: assert pat in result.output, '`{}` not found'.format(pat) - for k, v in genesis_json['alloc'].items(): + for k, v in list(genesis_json['alloc'].items()): assert k in result.output assert v['balance'] in result.output diff --git a/pyethapp/tests/test_config.py b/pyethapp/tests/test_config.py index 1d282817..17b0ac9f 100644 --- a/pyethapp/tests/test_config.py +++ b/pyethapp/tests/test_config.py @@ -1,3 +1,5 @@ +from __future__ import print_function +from builtins import str from devp2p.peermanager import PeerManager from devp2p.discovery import NodeDiscovery from devp2p.app import BaseApp @@ -27,8 +29,8 @@ def test_save_load_config(): # from fn or base path config.write_config(conf, path=garbage_conf) conf2 = config.load_config(path=garbage_conf) - print 'b', conf - print 'a', conf2 + print('b', conf) + print('a', conf2) assert conf == conf2 path = tempfile.mktemp() diff --git a/pyethapp/tests/test_console_service.py b/pyethapp/tests/test_console_service.py index ca6b03e1..6b330f5c 100644 --- a/pyethapp/tests/test_console_service.py +++ b/pyethapp/tests/test_console_service.py @@ -1,3 +1,4 @@ +from builtins import str from itertools import count import pytest import serpent @@ -9,6 +10,7 @@ import ethereum.config from ethereum.slogging import get_logger, configure_logging from ethereum.state import State +from ethereum.utils import encode_hex 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 @@ -82,7 +84,7 @@ def mine_next_block(self): 'max_peers': 0, 'listen_port': 29873 }, - 'node': {'privkey_hex': mk_random_privkey().encode('hex')}, + 'node': {'privkey_hex': encode_hex(mk_random_privkey())}, 'discovery': { 'boostrap_nodes': [], 'listen_port': 29873 @@ -93,9 +95,9 @@ def mine_next_block(self): '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}, + encode_hex(tester.accounts[0]): {'balance': 10**24}, + encode_hex(tester.accounts[1]): {'balance': 1}, + encode_hex(tester.accounts[2]): {'balance': 10**24}, } } }, @@ -135,14 +137,14 @@ def main(a,b): tx = eth.transact(to='', data=evm_code, startgas=500000, sender=sender) hc_state_dict = State(chainservice.head_candidate.state_root, chain.env).to_dict() - code = hc_state_dict[tx.creates.encode('hex')]['code'] + code = hc_state_dict[encode_hex(tx.creates)]['code'] assert len(code) > 2 assert code != '0x' test_app.mine_next_block() creates = chain.head.transactions[0].creates - code = chain.state.to_dict()[creates.encode('hex')]['code'] + code = chain.state.to_dict()[encode_hex(creates)]['code'] assert len(code) > 2 assert code != '0x' @@ -187,7 +189,7 @@ def test_console_name_reg_contract(test_app): tx = eth.transact(to='', data=evm_code, startgas=500000, sender=sender) hc_state_dict = State(chainservice.head_candidate.state_root, chain.env).to_dict() - code = hc_state_dict[tx.creates.encode('hex')]['code'] + code = hc_state_dict[encode_hex(tx.creates)]['code'] assert len(code) > 2 assert code != '0x' @@ -195,7 +197,7 @@ def test_console_name_reg_contract(test_app): creates = chain.head.transactions[0].creates state_dict = chain.state.to_dict() - code = state_dict[creates.encode('hex')]['code'] + code = state_dict[encode_hex(creates)]['code'] assert len(code) > 2 assert code != '0x' @@ -208,4 +210,4 @@ def test_console_name_reg_contract(test_app): test_app.mine_next_block() result = namereg.resolve(sender) - assert result == 'alice' + ('\x00' * 27) + assert result == b'alice' + ('\x00' * 27).encode() diff --git a/pyethapp/tests/test_eth_protocol.py b/pyethapp/tests/test_eth_protocol.py index 2386370f..1a3a0858 100644 --- a/pyethapp/tests/test_eth_protocol.py +++ b/pyethapp/tests/test_eth_protocol.py @@ -1,3 +1,5 @@ +from __future__ import print_function +from builtins import object from pyethapp.eth_protocol import ETHProtocol, TransientBlockBody from devp2p.service import WiredService from devp2p.protocol import BaseProtocol @@ -58,7 +60,7 @@ def test_status(): assert _p == proto assert isinstance(_d, dict) assert _d['chain_difficulty'] == chain.chain.get_score(head) - print _d + print(_d) assert _d['chain_head_hash'] == head.hash assert _d['genesis_hash'] == genesis.hash assert 'eth_version' in _d diff --git a/pyethapp/tests/test_eth_service.py b/pyethapp/tests/test_eth_service.py index e9602c30..fe35be68 100644 --- a/pyethapp/tests/test_eth_service.py +++ b/pyethapp/tests/test_eth_service.py @@ -1,10 +1,16 @@ +from builtins import range +from builtins import object import os import pytest from ethereum.db import EphemDB +from ethereum.utils import ( + decode_hex, + encode_hex, +) from pyethapp.config import update_config_with_defaults from pyethapp import eth_service from pyethapp import leveldb_service -from pyethapp import codernitydb_service +# from pyethapp import codernitydb_service from pyethapp import eth_protocol from ethereum import slogging from ethereum.tools import tester @@ -21,10 +27,10 @@ class AppMock(object): class Services(dict): - class accounts: + class accounts(object): coinbase = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - class peermanager: + class peermanager(object): @classmethod def broadcast(*args, **kwargs): @@ -94,7 +100,7 @@ def test_receive_newblock(): app = AppMock() eth = eth_service.ChainService(app) proto = eth_protocol.ETHProtocol(PeerMock(app), eth) - d = eth_protocol.ETHProtocol.newblock.decode_payload(newblk_rlp.decode('hex')) + d = eth_protocol.ETHProtocol.newblock.decode_payload(decode_hex(newblk_rlp)) eth.on_receive_newblock(proto, **d) @@ -103,9 +109,9 @@ def receive_blockheaders(rlp_data, leveldb=False, codernitydb=False): if leveldb: app.db = leveldb_service.LevelDB( os.path.join(app.config['app']['dir'], app.config['db']['path'])) - if codernitydb: - app.db = codernitydb_service.CodernityDB( - os.path.join(app.config['app']['dir'], app.config['db']['path'])) + # if codernitydb: + # app.db = codernitydb_service.CodernityDB( + # os.path.join(app.config['app']['dir'], app.config['db']['path'])) eth = eth_service.ChainService(app) proto = eth_protocol.ETHProtocol(PeerMock(app), eth) @@ -114,16 +120,16 @@ def receive_blockheaders(rlp_data, leveldb=False, codernitydb=False): def test_receive_block1(): - rlp_data = rlp.encode([rlp.decode(block_1.decode('hex'))]) + rlp_data = rlp.encode([rlp.decode(decode_hex(block_1))]) receive_blockheaders(rlp_data) def test_receive_blockheaders_256(): - receive_blockheaders(data256.decode('hex')) + receive_blockheaders(decode_hex(data256)) def test_receive_blockheaders_256_leveldb(): - receive_blockheaders(data256.decode('hex'), leveldb=True) + receive_blockheaders(decode_hex(data256), leveldb=True) @pytest.fixture @@ -138,11 +144,11 @@ def test_app(tmpdir): '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': 10 ** 24}, - tester.accounts[2].encode('hex'): {'balance': 10 ** 24}, - tester.accounts[3].encode('hex'): {'balance': 10 ** 24}, - tester.accounts[4].encode('hex'): {'balance': 10 ** 24}, + encode_hex(tester.accounts[0]): {'balance': 10 ** 24}, + encode_hex(tester.accounts[1]): {'balance': 10 ** 24}, + encode_hex(tester.accounts[2]): {'balance': 10 ** 24}, + encode_hex(tester.accounts[3]): {'balance': 10 ** 24}, + encode_hex(tester.accounts[4]): {'balance': 10 ** 24}, } } } @@ -170,3 +176,72 @@ def make_transaction(key, nonce, value, to): tx = Transaction(nonce, gasprice, startgas, to, value, data, v, r, s) tx.sign(key) return tx + + +def test_query_headers(test_app): + test_chain = tester.Chain() + test_chain.mine(30) + + chainservice = test_app.chain + chainservice.chain = test_chain.chain + + # query_headers(hash_mode, origin_hash, max_hashes, skip, reverse) + # case 1-1: hash_mode and reverse + headers = chainservice.query_headers( + 1, + 5, + 0, + True, + origin_hash=test_chain.chain.get_block_by_number(10).hash, + ) + assert len(headers) == 5 + assert headers[0].number == 10 + assert headers[-1].number == 6 + + # case 1-2: hash_mode and reverse, reach genesis + headers = chainservice.query_headers( + 1, + 20, + 0, + True, + origin_hash=test_chain.chain.get_block_by_number(10).hash, + ) + assert len(headers) == 10 + assert headers[0].number == 10 + assert headers[-1].number == 1 + + # case 2: hash_mode and not reverse + headers = chainservice.query_headers( + 1, + 5, + 0, + False, + origin_hash=test_chain.chain.get_block_by_number(10).hash, + ) + assert len(headers) == 5 + assert headers[0].number == 10 + assert headers[-1].number == 14 + + # case 3: number mode and reverse + headers = chainservice.query_headers( + 0, + 5, + 0, + True, + number=10, + ) + assert len(headers) == 5 + assert headers[0].number == 10 + assert headers[-1].number == 6 + + # case 4: number mode and not reverse + headers = chainservice.query_headers( + 0, + 5, + 0, + False, + number=10, + ) + assert len(headers) == 5 + assert headers[0].number == 10 + assert headers[-1].number == 14 diff --git a/pyethapp/tests/test_genesis.py b/pyethapp/tests/test_genesis.py index f846eaf1..5e529108 100644 --- a/pyethapp/tests/test_genesis.py +++ b/pyethapp/tests/test_genesis.py @@ -4,6 +4,7 @@ from ethereum.config import Env, default_config from ethereum.genesis_helpers import mk_genesis_block from ethereum.state import State +from ethereum.utils import encode_hex from pyethapp.utils import merge_dict from pyethapp.config import update_config_from_genesis_json import pyethapp.config as konfig @@ -28,12 +29,12 @@ def test_genesis_config(): genesis = mk_genesis_block(env) state = State(genesis.state_root, env) - for address, value_dict in alloc.items(): - value = value_dict.values()[0] + for address, value_dict in list(alloc.items()): + value = list(value_dict.values())[0] assert state.get_balance(address) == value -@pytest.mark.parametrize('profile', PROFILES.keys()) +@pytest.mark.parametrize('profile', list(PROFILES.keys())) def test_profile(profile): config = dict(eth=dict()) @@ -50,4 +51,4 @@ def test_profile(profile): env = Env(DB(), bc) genesis = mk_genesis_block(env) - assert genesis.hash.encode('hex') == config['eth']['genesis_hash'] + assert encode_hex(genesis.hash) == config['eth']['genesis_hash'] diff --git a/pyethapp/tests/test_jsonrpc.py b/pyethapp/tests/test_jsonrpc.py index 39ae6bd6..caf5eeb4 100644 --- a/pyethapp/tests/test_jsonrpc.py +++ b/pyethapp/tests/test_jsonrpc.py @@ -1,10 +1,14 @@ # -*- coding: utf8 -*- +from builtins import hex +from builtins import str +from builtins import range +from builtins import object import os from os import path from itertools import count +import json import gevent import gc -import json import pytest import rlp @@ -16,7 +20,14 @@ from ethereum.tools import tester from ethereum.slogging import get_logger, configure_logging from ethereum.state import State +from ethereum.utils import ( + decode_hex, + encode_hex, +) +from ethereum.tools import _solidity +from ethereum.abi import event_id, normalize_name from devp2p.peermanager import PeerManager +from tinyrpc.protocols.jsonrpc import JSONRPCProtocol from pyethapp.accounts import Account, AccountsService, mk_random_privkey from pyethapp.app import EthApp @@ -26,12 +37,10 @@ from pyethapp.jsonrpc import Compilers, JSONRPCServer, quantity_encoder, address_encoder, data_decoder, \ data_encoder, default_gasprice, default_startgas from pyethapp.rpc_client import JSONRPCClient -from tinyrpc.protocols.jsonrpc import JSONRPCProtocol from pyethapp.profiles import PROFILES from pyethapp.pow_service import PoWService -from ethereum.tools import _solidity -from ethereum.abi import event_id, normalize_name from pyethapp.rpc_client import ContractProxy +from pyethapp.utils import to_comparable_logs ethereum.tools.keys.PBKDF2_CONSTANTS['c'] = 100 # faster key derivation log = get_logger('test.jsonrpc') # pylint: disable=invalid-name @@ -50,11 +59,11 @@ # # (compiled with online Solidity compiler at https://chriseth.github.io/browser-solidity/ version # 0.1.1-34172c3b/RelWithDebInfo-Emscripten/clang/int) -LOG_EVM = ( +LOG_EVM = decode_hex( '606060405260448060116000396000f30060606040523615600d57600d565b60425b7f5e7df75d54' 'e493185612379c616118a4c9ac802de621b010c96f74d22df4b30a60405180905060405180910390' 'a15b565b00' -).decode('hex') +) def test_externally(): @@ -218,7 +227,7 @@ def rpc_request(self, method, *args, **kwargs): 'max_peers': 0, 'listen_port': 29873 }, - 'node': {'privkey_hex': mk_random_privkey().encode('hex')}, + 'node': {'privkey_hex': encode_hex(mk_random_privkey())}, 'discovery': { 'boostrap_nodes': [], 'listen_port': 29873 @@ -230,9 +239,9 @@ def rpc_request(self, method, *args, **kwargs): '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}, + encode_hex(tester.accounts[0]): {'balance': 10 ** 24}, + encode_hex(tester.accounts[1]): {'balance': 1}, + encode_hex(tester.accounts[2]): {'balance': 10 ** 24}, } } }, @@ -550,19 +559,19 @@ def test_send_transaction(test_app): chain = chainservice.chain hc = chainservice.head_candidate state = State(hc.state_root, chain.env) - assert state.get_balance('\xff' * 20) == 0 + assert state.get_balance(b'\xff' * 20) == 0 sender = test_app.services.accounts.unlocked_accounts[0].address assert state.get_balance(sender) > 0 tx = { 'from': address_encoder(sender), - 'to': address_encoder('\xff' * 20), + 'to': address_encoder(b'\xff' * 20), 'value': quantity_encoder(1) } tx_hash = data_decoder(test_app.client.call('eth_sendTransaction', tx)) test_app.mine_next_block() assert len(chain.head.transactions) == 1 assert tx_hash == chain.head.transactions[0].hash - assert chain.state.get_balance('\xff' * 20) == 1 + assert chain.state.get_balance(b'\xff' * 20) == 1 # send transactions from account which can't pay gas tx['from'] = address_encoder(test_app.services.accounts.unlocked_accounts[1].address) @@ -585,14 +594,14 @@ def main(a,b): tx = { 'from': address_encoder(sender), 'to': address_encoder(tx_to), - 'data': evm_code.encode('hex') + 'data': encode_hex(evm_code) } data_decoder(test_app.client.call('eth_sendTransaction', tx)) assert len(chainservice.head_candidate.transactions) == 1 creates = chainservice.head_candidate.transactions[0].creates candidate_state_dict = State(chainservice.head_candidate.state_root, chain.env).to_dict() - code = candidate_state_dict[creates.encode('hex')]['code'] + code = candidate_state_dict[encode_hex(creates)]['code'] assert len(code) > 2 assert code != '0x' @@ -601,7 +610,7 @@ def main(a,b): assert len(chain.head.transactions) == 1 creates = chain.head.transactions[0].creates state_dict = State(chain.head.state_root, chain.env).to_dict() - code = state_dict[creates.encode('hex')]['code'] + code = state_dict[encode_hex(creates)]['code'] assert len(code) > 2 assert code != '0x' @@ -627,7 +636,7 @@ def main(a,b): creates = chainservice.head_candidate.transactions[0].creates candidate_state_dict = State(chainservice.head_candidate.state_root, chain.env).to_dict() - code = candidate_state_dict[creates.encode('hex')]['code'] + code = candidate_state_dict[encode_hex(creates)]['code'] assert len(code) > 2 assert code != '0x' @@ -636,7 +645,7 @@ def main(a,b): assert len(chain.head.transactions) == 1 creates = chain.head.transactions[0].creates state_dict = State(chain.head.state_root, chain.env).to_dict() - code = state_dict[creates.encode('hex')]['code'] + code = state_dict[encode_hex(creates)]['code'] assert len(code) > 2 assert code != '0x' @@ -646,7 +655,7 @@ def test_pending_transaction_filter(test_app): assert test_app.client.call('eth_getFilterChanges', filter_id) == [] tx = { 'from': address_encoder(test_app.services.accounts.unlocked_accounts[0].address), - 'to': address_encoder('\xff' * 20) + 'to': address_encoder(b'\xff' * 20) } sequences = [ @@ -773,7 +782,8 @@ def test_get_logs(test_app): 'fromBlock': quantity_encoder(n0), 'toBlock': 'pending' }) - assert sorted(logs7) == sorted(logs3 + logs6 + logs1) + assert to_comparable_logs(logs7) == \ + to_comparable_logs(logs3 + logs6 + logs1) def test_get_filter_changes(test_app): @@ -867,8 +877,9 @@ def test_get_filter_changes(test_app): }) tx_hashes.append(test_app.client.call('eth_sendTransaction', tx)) logs.append(test_app.client.call('eth_getFilterChanges', range_filter_id)) - assert sorted(logs[-1]) == sorted(logs_in_range + [pending_log]) + assert to_comparable_logs(logs[-1]) == \ + to_comparable_logs(logs_in_range + [pending_log]) def test_eth_nonce(test_app): """ diff --git a/pyethapp/tools.py b/pyethapp/tools.py index 2940a722..e043d507 100644 --- a/pyethapp/tools.py +++ b/pyethapp/tools.py @@ -1,3 +1,7 @@ +from __future__ import print_function +from builtins import zip +from builtins import str +from builtins import range import os import sys import time @@ -10,7 +14,7 @@ from devp2p.crypto import privtopub def generate_data_dirs(num_participants, prefix='v'): - privkeys = [utils.sha3(str(i)) for i in range(num_participants)] + privkeys = [utils.sha3(utils.to_string(i)) for i in range(num_participants)] addrs = [utils.privtoaddr(k) for k in privkeys] genesis = generate_genesis(None, num_participants) @@ -21,11 +25,11 @@ def generate_data_dirs(num_participants, prefix='v'): jsonrpc_port = 4000+i deposit_size = 500 + 500*i - bootstrap_nodes = range(num_participants) + bootstrap_nodes = list(range(num_participants)) bootstrap_nodes.remove(i) bootstrap_nodes = ["enode://%s@0.0.0.0:%d" % (utils.encode_hex(privtopub(privkeys[n])), 40000+n) for n in bootstrap_nodes] - dir = prefix + str(i) + dir = prefix + utils.to_string(i) try: os.stat(dir) except: @@ -63,15 +67,15 @@ def generate_data_dirs(num_participants, prefix='v'): with open(genesis_path, 'w') as f: json.dump(genesis, f, sort_keys=False, indent=4, separators=(',', ': ')) - print "genesis for validator %d generated" % i + print("genesis for validator %d generated" % i) with open(config_path, 'w') as f: yaml.dump(config, f, default_flow_style=False, indent=4) - print "config for validator %d generated" % i + print("config for validator %d generated" % i) def generate_genesis(path=None, num_participants=1): - privkeys = [utils.sha3(str(i)) for i in range(num_participants)] + privkeys = [utils.sha3(utils.to_string(i)) for i in range(num_participants)] addrs = [utils.privtoaddr(k) for k in privkeys] deposit_sizes = [i * 500 + 500 for i in range(num_participants)] randaos = [RandaoManager(utils.sha3(k)) for k in privkeys] @@ -86,9 +90,9 @@ def generate_genesis(path=None, num_participants=1): to=casper_config['METROPOLIS_BLOCKHASH_STORE'], data=utils.encode_int32(0)) genesis_number = call_casper(s, 'getBlockNumber') - print 'genesis block hash: %s' % utils.encode_hex(genesis_hash) - print 'genesis block number: %d' % genesis_number - print '%d validators: %r' % (num_participants, [utils.encode_hex(a) for a in addrs]) + print('genesis block hash: %s' % utils.encode_hex(genesis_hash)) + print('genesis block number: %d' % genesis_number) + print('%d validators: %r' % (num_participants, [utils.encode_hex(a) for a in addrs])) snapshot = s.to_snapshot() header = s.prev_headers[0] @@ -107,15 +111,15 @@ def generate_genesis(path=None, num_participants=1): if path: with open(path, 'w') as f: json.dump(genesis, f, sort_keys=False, indent=4, separators=(',', ': ')) - print 'casper genesis generated' + print('casper genesis generated') else: return genesis def usage(): - print "usage:" - print "python pyethapp/tools.py genesis pyethapp/genesisdata/genesis_metropolis.json 3" - print "python pyethapp/tools.py datadir 3" + print("usage:") + print("python pyethapp/tools.py genesis pyethapp/genesisdata/genesis_metropolis.json 3") + print("python pyethapp/tools.py datadir 3") if __name__ == "__main__": if len(sys.argv) == 1: @@ -127,6 +131,6 @@ def usage(): elif sys.argv[1] == "datadir": generate_data_dirs(int(sys.argv[2])) else: - print "unknown command: %s" % sys.argv[1] + print("unknown command: %s" % sys.argv[1]) usage() sys.exit(1) diff --git a/pyethapp/utils.py b/pyethapp/utils.py index b677195e..af778154 100644 --- a/pyethapp/utils.py +++ b/pyethapp/utils.py @@ -1,7 +1,10 @@ +from __future__ import print_function +from builtins import input import signal import warnings from collections import Mapping import os +from functools import total_ordering import click import ethereum @@ -33,7 +36,7 @@ def load_contrib_services(config): # FIXME os.chdir(config_directory) for filename in os.listdir(contrib_directory): if filename.endswith('.py'): - print filename + print(filename) try: __import__(filename[:-3]) library_conflict = True @@ -46,11 +49,11 @@ def load_contrib_services(config): # FIXME sys.path.pop() contrib_services = [] for module in contrib_modules: - print 'm', module, dir(module) + print('m', module, dir(module)) on_start, on_block = None, None for variable in dir(module): cls = getattr(module, variable) - if isinstance(cls, (type, types.ClassType)): + if isinstance(cls, type): if issubclass(cls, BaseService) and cls != BaseService: contrib_services.append(cls) if variable == 'on_block': @@ -95,7 +98,7 @@ def load_block_tests(data, db): """ scanners = ethereum.utils.scanners initial_alloc = {} - for address, acct_state in data['pre'].items(): + for address, acct_state in list(data['pre'].items()): address = ethereum.utils.decode_hex(address) balance = scanners['int256b'](acct_state['balance'][2:]) nonce = scanners['int256b'](acct_state['nonce'][2:]) @@ -116,7 +119,7 @@ def load_block_tests(data, db): parent = blocks[ethereum.utils.decode_hex(blk['blockHeader']['parentHash'])] block = rlp.decode(rlpdata, Block, parent=parent, env=env) blocks[block.hash] = block - return sorted(blocks.values(), key=lambda b: b.number) + return sorted(list(blocks.values()), key=lambda b: b.number) def merge_dict(dest, source): @@ -156,7 +159,7 @@ def convert(self, value, param, ctx): def enable_greenlet_debugger(): def _print_exception(self, context, type_, value, traceback): ultratb.VerboseTB(call_pdb=True)(type_, value, traceback) - resp = raw_input( + resp = input( "{c.OKGREEN}Debugger exited. " "{c.OKBLUE}Do you want to quit pyethapp?{c.ENDC} [{c.BOLD}Y{c.ENDC}/n] ".format( c=bcolors @@ -166,3 +169,20 @@ def _print_exception(self, context, type_, value, traceback): os.kill(os.getpid(), signal.SIGTERM) gevent.get_hub().__class__.print_exception = _print_exception + + +@total_ordering +class MinType(object): + """ Return Min value for sorting comparison + + This class is used for comparing unorderded types. e.g., NoneType + """ + def __le__(self, other): + return True + + def __eq__(self, other): + return (self is other) + + +def to_comparable_logs(logs): + return sorted(set(x) for x in logs) diff --git a/pyethapp/validator_service.py b/pyethapp/validator_service.py index b693bf1f..dce6e667 100644 --- a/pyethapp/validator_service.py +++ b/pyethapp/validator_service.py @@ -1,3 +1,4 @@ +from __future__ import print_function import time import random import gevent @@ -37,9 +38,9 @@ def __init__(self, app): self.chain.time = lambda: int(time.time()) self.key = self.config['validator']['privkey'] - print "*"*100 - print repr(self.key) - print len(self.key) + print("*"*100) + print(repr(self.key)) + print(len(self.key)) self.address = privtoaddr(self.key) self.validation_code = generate_validation_code(self.address) self.validation_code_hash = sha3(self.validation_code) @@ -140,7 +141,7 @@ def update(self): if self.active: self.next_skip_count = 0 self.next_skip_timestamp = get_timestamp(self.chain, self.next_skip_count) - log.debug('Head changed: %s, will attempt creating a block at %d' % (self.chain.head_hash.encode('hex'), self.next_skip_timestamp)) + log.debug('Head changed: %s, will attempt creating a block at %d' % (encode_hex(self.chain.head_hash), self.next_skip_timestamp)) def withdraw(self, gasprice=20 * 10**9): sigdata = make_withdrawal_signature(self.key) diff --git a/requirements.txt b/requirements.txt index ec33f027..17065682 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ gevent==1.1.0 -gipc==0.4.0 +gipc CodernityDB leveldb lmdb @@ -13,9 +13,12 @@ statistics requests rlp>=0.6.0,<0.7.0 devp2p>=0.9.3 -ethereum>=2.0.4 +ethereum==2.1.1 pbkdf2 scrypt pexpect pyelliptic==1.5.7 tinyrpc[gevent,httpclient,jsonext,websocket,wsgi] +pycryptodome==3.4.6 +future +https://github.com/ethereum/serpent/tarball/develop diff --git a/setup.py b/setup.py index 57f170e2..4c081903 100755 --- a/setup.py +++ b/setup.py @@ -30,31 +30,17 @@ def run_tests(self): LONG_DESCRIPTION = README + '\n\n' + HISTORY -INSTALL_REQUIRES_REPLACEMENTS = { - 'https://github.com/ethereum/ethash/tarball/master#egg=pyethash': 'pyethash', +# requirements +install_requires = set(x.strip() for x in open('requirements.txt')) +install_requires_replacements = { + 'https://github.com/ethereum/serpent/tarball/develop': 'ethereum-serpent', } +install_requires = [install_requires_replacements.get(r, r) for r in install_requires] -INSTALL_REQUIRES = list() -with open('requirements.txt') as requirements_file: - for requirement in requirements_file: - dependency = INSTALL_REQUIRES_REPLACEMENTS.get( - requirement.strip(), - requirement.strip(), - ) - - INSTALL_REQUIRES.append(dependency) - -INSTALL_REQUIRES = list(set(INSTALL_REQUIRES)) - -DEPENDENCY_LINKS = [] -if os.environ.get("USE_PYETHEREUM_DEVELOP"): - # Force installation of specific commits of devp2p and pyethereum. - devp2p_ref='525e15a9967da3174ec9e4e367b5adfb76138bb4' - pyethereum_ref='8edc5954fb8b6697cb7c9d7d85ed71e5f6d74e0f' - DEPENDENCY_LINKS = [ - 'http://github.com/ethereum/pydevp2p/tarball/%s#egg=devp2p-9.99.9' % devp2p_ref, - 'http://github.com/ethereum/pyethereum/tarball/%s#egg=ethereum-9.99.9' % pyethereum_ref, - ] +# dependency links +dependency_links = [ + 'https://github.com/ethereum/serpent/tarball/develop#egg=ethereum-serpent-9.99.9', +] # *IMPORTANT*: Don't manually change the version here. Use the 'bump2version' utility. # see: https://github.com/ethereum/pyethapp/wiki/Development:-Versions-and-Releases @@ -84,12 +70,13 @@ def run_tests(self): 'Natural Language :: English', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.6', ], cmdclass={'test': PyTest}, - install_requires=INSTALL_REQUIRES, - dependency_links=DEPENDENCY_LINKS, + install_requires=install_requires, + dependency_links=dependency_links, tests_require=[ - 'ethereum-serpent>=1.8.1', + # 'ethereum-serpent>=1.8.1', 'mock==2.0.0', 'pytest-mock==1.6.0', ], diff --git a/tox.ini b/tox.ini index 5e8bd1a7..4158a562 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,5 @@ [tox] -envlist = py27,flake8,check_readme,coverage - +envlist = py{27, 36},flake8,check_readme,coverage [testenv] setenv = @@ -14,7 +13,9 @@ commands = coverage run --source pyethapp --branch -m py.test {posargs} [testenv:coverage] -basepython = python2.7 +basepython = + py27: python2.7 + py36: python3.6 skip_install = True deps = @@ -25,7 +26,9 @@ commands = [testenv:flake8] -basepython = python2.7 +basepython = + py27: python2.7 + py36: python3.6 skip_install = True deps =