diff --git a/.travis.yml b/.travis.yml index 65c89fc1..e83fe042 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ before_install: - sudo apt-get update - sudo apt-get install -y solc install: -- pip install -r requirements.txt +- USE_PYETHEREUM_DEVELOP=1 python setup.py install - pip install coveralls readme_renderer script: - coverage run --source pyethapp setup.py test diff --git a/README.rst b/README.rst index d548ad1a..862d39ef 100644 --- a/README.rst +++ b/README.rst @@ -35,7 +35,7 @@ pyethapp leverages two ethereum core components to implement the client: * pydevp2p_ - the p2p networking library, featuring node discovery for and transport of multiple services over multiplexed and encrypted connections -.. _Ethereum: http://ethereum.org/ +.. _Ethereum: https://ethereum.org/ .. _pyethereum: https://github.com/ethereum/pyethereum .. _pydevp2p: https://github.com/ethereum/pydevp2p @@ -57,7 +57,7 @@ advised to install system-dependecies with the help of a package manager Please install a *virtualenv* environment for a comfortable Pyethapp installation. Also, it is always recommended to use it in combination with the -`virtualenvwrapper `__ +`virtualenvwrapper `__ extension. The @@ -118,7 +118,7 @@ above, then: ($ mkvirtualenv pyethapp) $ git clone https://github.com/ethereum/pyethapp $ cd pyethapp - $ python setup.py develop + $ USE_PYETHEREUM_DEVELOP=1 python setup.py develop This has the advantage that inside of Python's ``lib/python2.7/site-packages`` there is a direct link to your directory diff --git a/bumpversion.txt b/bumpversion.txt new file mode 100644 index 00000000..3c415e02 --- /dev/null +++ b/bumpversion.txt @@ -0,0 +1,15 @@ +With the config in setup.cfg it's possible to use bump2version to generate the +usual .. (e.g. 1.5.0) releases as well as alpha and rc +ones (e.g. 1.6.0a1, 1.6.0rc2, etc). For the latter, one just has to tell +bump2version to update either the "release" or "num" parts. For example, +assuming a current version of 1.5.0: + + $ bump2version minor # will update the current version to 1.6.0a0 + + $ bump2version num # will update the current version to 1.6.0a1 + + $ bump2version release # will update the current version to 1.6.0rc0 + + $ bump2version num # will update the current version to 1.6.0rc1 + + $ bump2version release # will update the current version to 1.6.0 diff --git a/dev_requirements.txt b/dev_requirements.txt new file mode 100644 index 00000000..65fa2123 --- /dev/null +++ b/dev_requirements.txt @@ -0,0 +1,6 @@ +pytest==2.9.1 +mock==2.0.0 +pytest-mock==1.6.0 +ethereum-serpent>=1.8.1 +pytest==2.9.1 +coverage==4.0.3 diff --git a/examples/export.py b/examples/export.py index 50e1010f..7df3bda5 100755 --- a/examples/export.py +++ b/examples/export.py @@ -35,7 +35,7 @@ def export_blocks(chain, head_number=None): head_number = head_number or chain.head.header.number block_number = 0 while block_number < head_number: - h = chain.index.get_block_by_number(block_number) + h = chain.get_blockhash_by_number(block_number) raw = chain.blockchain.get(h) print raw.encode('hex') _progress(block_number) diff --git a/examples/importblock.py b/examples/importblock.py index b8019227..9c5f0789 100644 --- a/examples/importblock.py +++ b/examples/importblock.py @@ -27,7 +27,7 @@ def import_block(chain, rlp_data): if __name__ == '__main__': chain = get_chain() print '\nIMPORTING BLOCK' - # h = chain.index.get_block_by_number(447360) + # h = chain.get_blockhash_by_number(447360) # b = chain.get(h) # rlp_data = rlp.encode(b) import_block(chain, rlp_data) diff --git a/pyethapp/accounts.py b/pyethapp/accounts.py index a20d7bfe..af35a859 100644 --- a/pyethapp/accounts.py +++ b/pyethapp/accounts.py @@ -1,10 +1,10 @@ import json import os -import random +from random import SystemRandom import shutil from uuid import UUID from devp2p.service import BaseService -from ethereum import keys +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 @@ -12,6 +12,8 @@ DEFAULT_COINBASE = 'de0b295669a9fd93d5f28d9ec85e40f4cb697bae'.decode('hex') +random = SystemRandom() + def mk_privkey(seed): return sha3(seed) @@ -478,7 +480,7 @@ 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 not found by address', address=address.encode('hex')) + raise KeyError('account with address {} not found'.format(address.encode('hex'))) elif len(accounts) > 1: log.warning('multiple accounts with same address found', address=address.encode('hex')) return accounts[0] diff --git a/pyethapp/app.py b/pyethapp/app.py index 6cddd66e..029f5e25 100644 --- a/pyethapp/app.py +++ b/pyethapp/app.py @@ -16,11 +16,12 @@ from devp2p.discovery import NodeDiscovery from devp2p.peermanager import PeerManager from devp2p.service import BaseService -from ethereum import blocks -from ethereum.blocks import Block +from ethereum import config as eth_config +from ethereum.block import Block +from ethereum.snapshot import create_snapshot, load_snapshot as _load_snapshot from gevent.event import Event -import config as konfig +import config as app_config import eth_protocol import utils from accounts import AccountsService, Account @@ -36,8 +37,8 @@ log = slogging.get_logger('app') -services = [DBService, AccountsService, NodeDiscovery, PeerManager, ChainService, PoWService, - JSONRPCServer, IPCRPCServer, Console] +services = [DBService, AccountsService, NodeDiscovery, PeerManager, ChainService, + PoWService, JSONRPCServer, IPCRPCServer, Console] class EthApp(BaseApp): @@ -62,12 +63,12 @@ class EthApp(BaseApp): "'livenet' and 'testnet'. The previous values 'frontier' and " "'morden' will be removed in a future update."), default=DEFAULT_PROFILE, help="Configuration profile.", show_default=True) -@click.option('alt_config', '--Config', '-C', type=str, callback=konfig.validate_alt_config_file, +@click.option('alt_config', '--Config', '-C', type=str, callback=app_config.validate_alt_config_file, help='Alternative config file') @click.option('config_values', '-c', multiple=True, type=str, help='Single configuration parameters (=)') @click.option('alt_data_dir', '-d', '--data-dir', multiple=False, type=str, - help='data directory', default=konfig.default_data_dir, show_default=True) + help='data directory', default=app_config.default_data_dir, show_default=True) @click.option('-l', '--log_config', multiple=False, type=str, default=":info", help='log_config string: e.g. ":info,eth:debug', show_default=True) @click.option('--log-json/--log-no-json', default=False, @@ -89,18 +90,18 @@ def app(ctx, profile, alt_config, config_values, alt_data_dir, log_config, boots # data dir default or from cli option alt_data_dir = os.path.expanduser(alt_data_dir) - data_dir = alt_data_dir or konfig.default_data_dir - konfig.setup_data_dir(data_dir) # if not available, sets up data_dir and required config + data_dir = alt_data_dir or app_config.default_data_dir + app_config.setup_data_dir(data_dir) # if not available, sets up data_dir and required config log.info('using data in', path=data_dir) # prepare configuration # config files only contain required config (privkeys) and config different from the default if alt_config: # specified config file - config = konfig.load_config(alt_config) + config = app_config.load_config(alt_config) if not config: log.warning('empty config given. default config values will be used') else: # load config from default or set data_dir - config = konfig.load_config(data_dir) + config = app_config.load_config(data_dir) config['data_dir'] = data_dir @@ -111,9 +112,8 @@ def app(ctx, profile, alt_config, config_values, alt_data_dir, log_config, boots bootstrap_nodes_from_config_file = config.get('discovery', {}).get('bootstrap_nodes') # add default config - konfig.update_config_with_defaults(config, konfig.get_default_config([EthApp] + services)) - - konfig.update_config_with_defaults(config, {'eth': {'block': blocks.default_config}}) + app_config.update_config_with_defaults(config, app_config.get_default_config([EthApp] + services)) + app_config.update_config_with_defaults(config, {'eth': {'block': eth_config.default_config}}) # Set config values based on profile selection merge_dict(config, PROFILES[profile]) @@ -132,7 +132,7 @@ def app(ctx, profile, alt_config, config_values, alt_data_dir, log_config, boots # override values with values from cmd line for config_value in config_values: try: - konfig.set_config_param(config, config_value) + app_config.set_config_param(config, config_value) except ValueError: raise BadParameter('Config parameter must be of the form "a.b.c=d" where "a.b.c" ' 'specifies the parameter to set and d is a valid yaml value ' @@ -144,7 +144,7 @@ def app(ctx, profile, alt_config, config_values, alt_data_dir, log_config, boots del config['eth']['genesis_hash'] # Load genesis config - konfig.update_config_from_genesis_json(config, + app_config.update_config_from_genesis_json(config, genesis_json_filename_or_dict=config['eth']['genesis']) if bootstrap_node: config['discovery']['bootstrap_nodes'] = [bytes(bootstrap_node)] @@ -181,10 +181,6 @@ def run(ctx, dev, nodial, fake, console): config['p2p']['min_peers'] = 0 if fake: - from ethereum import blocks - blocks.GENESIS_DIFFICULTY = 1024 - blocks.BLOCK_DIFF_FACTOR = 16 - blocks.MIN_GAS_LIMIT = blocks.default_config['GENESIS_GAS_LIMIT'] / 2 config['eth']['block']['GENESIS_DIFFICULTY'] = 1024 config['eth']['block']['BLOCK_DIFF_FACTOR'] = 16 @@ -201,7 +197,8 @@ def run(ctx, dev, nodial, fake, console): pass # dump config - dump_config(config) + if log.is_active('debug'): + dump_config(config) # init and unlock accounts first to check coinbase if AccountsService in services: @@ -256,9 +253,9 @@ def dump_config(config): cfg = copy.deepcopy(config) alloc = cfg.get('eth', {}).get('block', {}).get('GENESIS_INITIAL_ALLOC', {}) if len(alloc) > 100: - log.info('omitting reporting of %d accounts in genesis' % len(alloc)) + log.debug('omitting reporting of %d accounts in genesis' % len(alloc)) del cfg['eth']['block']['GENESIS_INITIAL_ALLOC'] - konfig.dump_config(cfg) + app_config.dump_config(cfg) @app.command() @@ -335,6 +332,53 @@ def blocktest(ctx, file, name): app.stop() +@app.command('snapshot') +@click.option('-r', '--recent', type=int, default=1024, + help='Number of recent blocks. State before these blocks and these blocks will be dumped. On recover these blocks will be applied on restored state. (default: 1024)') +@click.option('-f', '--filename', type=str, default=None, + help='Output file name. (default: auto-gen file prefixed by snapshot-') +@click.pass_context +def snapshot(ctx, recent, filename): + """Take a snapshot of current world state. + + The snapshot will be saved in JSON format, including data like chain configurations and accounts. + + It will overwrite exiting file if it already exists. + """ + app = EthApp(ctx.obj['config']) + DBService.register_with_app(app) + AccountsService.register_with_app(app) + ChainService.register_with_app(app) + + if not filename: + import time + filename = 'snapshot-%d.json' % int(time.time()*1000) + + 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 + + +@app.command('load_snapshot') +@click.argument('filename', type=str) +@click.pass_context +def load_snapshot(ctx, filename): + """Load snapshot FILE into local node database. + + This process will OVERWRITE data in current database!!! + """ + app = EthApp(ctx.obj['config']) + DBService.register_with_app(app) + AccountsService.register_with_app(app) + ChainService.register_with_app(app) + + with open(filename, 'r') as f: + s = json.load(f, encoding='ascii') + _load_snapshot(app.services.chain.chain, s) + print 'snapshot %s loaded.' % filename + + @app.command('export') @click.option('--from', 'from_', type=int, help='Number of the first block (default: genesis)') @click.option('--to', type=int, help='Number of the last block (default: latest)') @@ -376,7 +420,7 @@ def export_blocks(ctx, from_, to, file): log.debug('Exporting block {}'.format(n)) if (n - from_) % 50000 == 0: log.info('Exporting block {} to {}'.format(n, min(n + 50000, to))) - block_hash = app.services.chain.chain.index.get_block_by_number(n) + block_hash = app.services.chain.chain.get_blockhash_by_number(n) # bypass slow block decoding by directly accessing db block_rlp = app.services.db.get(block_hash) file.write(block_rlp) diff --git a/pyethapp/config.py b/pyethapp/config.py index 2928bbd1..4b0a9317 100644 --- a/pyethapp/config.py +++ b/pyethapp/config.py @@ -26,7 +26,7 @@ from devp2p.service import BaseService from devp2p.app import BaseApp from accounts import mk_random_privkey -from ethereum.keys import decode_hex +from ethereum.tools.keys import decode_hex from ethereum.utils import parse_int_or_hex, remove_0x_head @@ -206,6 +206,9 @@ def update_config_from_genesis_json(config, genesis_json_filename_or_dict): if unknown_keys: raise ValueError('genesis_dict contains invalid keys.') + # TODO: we should only keep raw genesis data to have better interoperability? + config['eth']['genesis_data'] = genesis_dict + config.setdefault('eth', {}).setdefault('block', {}) ethblock_config = config['eth']['block'] diff --git a/pyethapp/console_service.py b/pyethapp/console_service.py index 091bd355..8e360ad8 100644 --- a/pyethapp/console_service.py +++ b/pyethapp/console_service.py @@ -14,11 +14,12 @@ from gevent.event import Event import IPython import IPython.core.shellapp -from IPython.lib.inputhook import inputhook_manager, stdin_ready from devp2p.service import BaseService -from ethereum import processblock from ethereum.exceptions import InvalidTransaction +from ethereum.pow.consensus import initialize from ethereum.slogging import getLogger +from ethereum.messages import apply_transaction +from ethereum.state import State from ethereum.transactions import Transaction from ethereum.utils import denoms, normalize_address @@ -31,43 +32,13 @@ GUI_GEVENT = 'gevent' -def inputhook_gevent(): - while not stdin_ready(): +def inputhook_gevent(context): + while not context.input_is_ready(): gevent.sleep(0.05) return 0 -@inputhook_manager.register('gevent') -class GeventInputHook(object): - - def __init__(self, manager): - self.manager = manager - - def enable(self, app=None): - """Enable event loop integration with gevent. - Parameters - ---------- - app : ignored - Ignored, it's only a placeholder to keep the call signature of all - gui activation methods consistent, which simplifies the logic of - supporting magics. - Notes - ----- - This methods sets the PyOS_InputHook for gevent, which allows - gevent greenlets to run in the background while interactively using - IPython. - """ - self.manager.set_inputhook(inputhook_gevent) - self._current_gui = GUI_GEVENT - return app - - def disable(self): - """Disable event loop integration with gevent. - This merely sets PyOS_InputHook to NULL. - """ - self.manager.clear_inputhook() - - +IPython.terminal.pt_inputhooks.register('gevent', inputhook_gevent) # ipython needs to accept "--gui gevent" option IPython.core.shellapp.InteractiveShellApp.gui.values += ('gevent',) @@ -183,7 +154,7 @@ def __init__(this, app): @property def pending(this): - return this.chain.head_candidate + return this.chainservice.head_candidate head_candidate = pending @@ -195,7 +166,8 @@ def transact(this, to, value=0, data='', sender=None, startgas=25000, gasprice=60 * denoms.shannon): sender = normalize_address(sender or this.coinbase) to = normalize_address(to, allow_blank=True) - nonce = this.pending.get_nonce(sender) + state = State(this.head_candidate.state_root, this.chain.env) + nonce = state.get_nonce(sender) tx = Transaction(nonce, gasprice, startgas, to, value, data) this.app.services.accounts.sign_tx(sender, tx) assert tx.sender == sender @@ -208,23 +180,28 @@ def call(this, to, value=0, data='', sender=None, to = normalize_address(to, allow_blank=True) block = this.head_candidate state_root_before = block.state_root - assert block.has_parent() + assert block.prevhash == this.chain.head_hash # rebuild block state before finalization - parent = block.get_parent() - test_block = block.init_from_parent(parent, block.coinbase, - timestamp=block.timestamp) - for tx in block.get_transactions(): - success, output = processblock.apply_transaction(test_block, tx) + test_state = this.chain.mk_poststate_of_blockhash(block.prevhash) + initialize(test_state, block) + for tx in block.transactions: + success, _ = apply_transaction(test_state, tx) assert success + # Need this because otherwise the Transaction.network_id + # @property returns 0, which causes the tx to fail validation. + class MockedTx(Transaction): + network_id = None + # apply transaction - nonce = test_block.get_nonce(sender) - tx = Transaction(nonce, gasprice, startgas, to, value, data) + nonce = test_state.get_nonce(sender) + tx = MockedTx(nonce, gasprice, startgas, to, value, data) tx.sender = sender try: - success, output = processblock.apply_transaction(test_block, tx) - except InvalidTransaction: + success, output = apply_transaction(test_state, tx) + except InvalidTransaction as e: + log.debug("error applying tx in Eth.call", exc=e) success = False assert block.state_root == state_root_before @@ -236,7 +213,7 @@ def call(this, to, value=0, data='', sender=None, def find_transaction(this, tx): try: - t, blk, idx = this.chain.index.get_transaction(tx.hash) + t, blk, idx = this.chain.get_transaction(tx.hash) except: return {} return dict(tx=t, block=blk, index=idx) @@ -248,10 +225,10 @@ def block_from_rlp(this, rlp_data): from eth_protocol import TransientBlock import rlp l = rlp.decode_lazy(rlp_data) - return TransientBlock.init_from_rlp(l).to_block(this.chain.blockchain) + return TransientBlock.init_from_rlp(l).to_block() try: - from ethereum._solidity import solc_wrapper + from ethereum.tools._solidity import solc_wrapper except ImportError: solc_wrapper = None pass diff --git a/pyethapp/dao.py b/pyethapp/dao.py index 621ff2b0..5a013d01 100644 --- a/pyethapp/dao.py +++ b/pyethapp/dao.py @@ -1,4 +1,4 @@ -from ethereum.blocks import BlockHeader +from ethereum.block import BlockHeader from ethereum.utils import decode_hex, int256, big_endian_to_int diff --git a/pyethapp/db_service.py b/pyethapp/db_service.py index ed1c6725..405428ee 100644 --- a/pyethapp/db_service.py +++ b/pyethapp/db_service.py @@ -1,5 +1,4 @@ # -*- coding: utf8 -*- -import sys from devp2p.service import BaseService from ethereum.db import BaseDB diff --git a/pyethapp/eth_protocol.py b/pyethapp/eth_protocol.py index 1a77a6ac..ab999f2b 100644 --- a/pyethapp/eth_protocol.py +++ b/pyethapp/eth_protocol.py @@ -1,6 +1,6 @@ from devp2p.protocol import BaseProtocol, SubProtocolError from ethereum.transactions import Transaction -from ethereum.blocks import Block, BlockHeader +from ethereum.block import Block, BlockHeader from ethereum.utils import hash32, int_to_big_endian, big_endian_to_int import rlp import gevent @@ -22,30 +22,30 @@ class TransientBlock(rlp.Serializable): fields = [ ('header', BlockHeader), - ('transaction_list', rlp.sedes.CountableList(Transaction)), + ('transactions', rlp.sedes.CountableList(Transaction)), ('uncles', rlp.sedes.CountableList(BlockHeader)) ] @classmethod def init_from_rlp(cls, block_data, newblock_timestamp=0): header = BlockHeader.deserialize(block_data[0]) - transaction_list = rlp.sedes.CountableList(Transaction).deserialize(block_data[1]) + transactions = rlp.sedes.CountableList(Transaction).deserialize(block_data[1]) uncles = rlp.sedes.CountableList(BlockHeader).deserialize(block_data[2]) - return cls(header, transaction_list, uncles, newblock_timestamp) + return cls(header, transactions, uncles, newblock_timestamp) - def __init__(self, header, transaction_list, uncles, newblock_timestamp=0): + def __init__(self, header, transactions, uncles, newblock_timestamp=0): self.newblock_timestamp = newblock_timestamp self.header = header - self.transaction_list = transaction_list + self.transactions = transactions self.uncles = uncles - def to_block(self, env, parent=None): + def to_block(self): """Convert the transient block to a :class:`ethereum.blocks.Block`""" - return Block(self.header, self.transaction_list, self.uncles, env=env, parent=parent) + return Block(self.header, transactions=self.transactions, uncles=self.uncles) @property def hex_hash(self): - return self.header.hex_hash() + return self.header.hex_hash def __repr__(self): return '' % (self.header.number, self.header.hash.encode('hex')[:8]) @@ -225,7 +225,7 @@ def create(self, proto, *bodies): if len(bodies) == 0: return [] if isinstance(bodies[0], Block): - bodies = [TransientBlockBody(b.transaction_list, b.uncles) for b in bodies] + bodies = [TransientBlockBody(b.transactions, b.uncles) for b in bodies] return bodies class newblock(BaseProtocol.command): diff --git a/pyethapp/eth_service.py b/pyethapp/eth_service.py index 2cbc7a32..669fbfb2 100644 --- a/pyethapp/eth_service.py +++ b/pyethapp/eth_service.py @@ -1,51 +1,44 @@ # -*- coding: utf8 -*- +import copy import time +import statistics from collections import deque -import eth_protocol import gevent import gevent.lock +from gevent.queue import Queue +from gevent.event import AsyncResult + import rlp -import statistics + from devp2p.protocol import BaseProtocol from devp2p.service import WiredService -from ethereum.blocks import Block, VerificationFailed -from ethereum.chain import Chain + +from ethereum.block import Block +from ethereum.meta import make_head_candidate +from ethereum.pow.chain import Chain +from ethereum.pow.consensus import initialize, check_pow from ethereum.config import Env -from ethereum.exceptions import InvalidTransaction, InvalidNonce, InsufficientBalance, InsufficientStartGas +from ethereum.genesis_helpers import mk_genesis_data from ethereum import config as ethereum_config -from ethereum import processblock -from ethereum.processblock import validate_transaction -from ethereum.refcount_db import RefcountDB +from ethereum.messages import apply_transaction, validate_transaction +from ethereum.transaction_queue import TransactionQueue +from ethereum.experimental.refcount_db import RefcountDB from ethereum.slogging import get_logger -from ethereum.blocks import get_block_header +from ethereum.exceptions import InvalidTransaction, InvalidNonce, \ + InsufficientBalance, InsufficientStartGas, VerificationFailed from ethereum.transactions import Transaction -from ethereum.utils import sha3 -from gevent.queue import Queue -from rlp.utils import encode_hex +from ethereum.utils import encode_hex + from synchronizer import Synchronizer +import eth_protocol from pyethapp import sentry from pyethapp.dao import is_dao_challenge, build_dao_header - log = get_logger('eth.chainservice') -# patch to get context switches between tx replay -processblock_apply_transaction = processblock.apply_transaction - - -def apply_transaction(block, tx): - log.debug('apply_transaction ctx switch', tx=tx.hash.encode('hex')[:8]) - gevent.sleep(0.001) - return processblock_apply_transaction(block, tx) - - -def rlp_hash_hex(data): - return encode_hex(sha3(rlp.encode(data))) - - class DuplicatesFilter(object): def __init__(self, max_items=128): @@ -67,19 +60,36 @@ def __contains__(self, v): return v in self.filter -def update_watcher(chainservice): - timeout = 180 - d = dict(head=chainservice.chain.head) +class DAOChallenger(object): - def up(b): - log.debug('watcher head updated') - d['head'] = b - chainservice.on_new_head_cbs.append(lambda b: up(b)) + request_timeout = 8. - while True: - last = d['head'] - gevent.sleep(timeout) - assert last != d['head'], 'no updates for %d secs' % timeout + def __init__(self, chainservice, proto): + self.chainservice = chainservice + self.config = chainservice.config['eth']['block'] + self.proto = proto + self.deferred = None + gevent.spawn(self.run) + + def run(self): + self.deferred = AsyncResult() + self.proto.send_getblockheaders(self.config['DAO_FORK_BLKNUM'], 1, 0) + try: + dao_headers = self.deferred.get(block=True, timeout=self.request_timeout) + log.debug("received DAO challenge answer", proto=self.proto, answer=dao_headers) + result = len(dao_headers) == 1 and \ + dao_headers[0].hash == self.config['DAO_FORK_BLKHASH'] and \ + dao_headers[0].extra_data == self.config['DAO_FORK_BLKEXTRA'] + self.chainservice.on_dao_challenge_answer(self.proto, result) + except gevent.Timeout: + log.debug('challenge dao timed out', proto=self.proto) + self.chainservice.on_dao_challenge_answer(self.proto, False) + + def receive_blockheaders(self, proto, blockheaders): + log.debug('blockheaders received', proto=proto, num=len(blockheaders)) + if proto != self.proto: + return + self.deferred.set(blockheaders) class ChainService(WiredService): @@ -103,7 +113,6 @@ class ChainService(WiredService): synchronizer = None config = None block_queue_size = 1024 - transaction_queue_size = 1024 processed_gas = 0 processed_elapsed = 0 @@ -146,23 +155,37 @@ def __init__(self, app): log.info('initializing chain') coinbase = app.services.accounts.coinbase env = Env(self.db, sce['block']) - self.chain = Chain(env, new_head_cb=self._on_new_head, coinbase=coinbase) + + genesis_data = sce.get('genesis_data', {}) + if not genesis_data: + genesis_data = mk_genesis_data(env) + self.chain = Chain( + 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) if 'genesis_hash' in sce: - assert sce['genesis_hash'] == self.chain.genesis.hex_hash(), \ + assert sce['genesis_hash'] == self.chain.genesis.hex_hash, \ "Genesis hash mismatch.\n Expected: %s\n Got: %s" % ( - sce['genesis_hash'], self.chain.genesis.hex_hash()) + sce['genesis_hash'], self.chain.genesis.hex_hash) + self.dao_challenges = dict() self.synchronizer = Synchronizer(self, force_sync=None) self.block_queue = Queue(maxsize=self.block_queue_size) - self.transaction_queue = Queue(maxsize=self.transaction_queue_size) + # When the transaction_queue is modified, we must set + # self._head_candidate_needs_updating to True in order to force the + # head candidate to be updated. + self.transaction_queue = TransactionQueue() + self._head_candidate_needs_updating = True + # Initialize a new head candidate. + _ = self.head_candidate + self.min_gasprice = 20 * 10**9 # TODO: better be an option to validator service? self.add_blocks_lock = False self.add_transaction_lock = gevent.lock.Semaphore() self.broadcast_filter = DuplicatesFilter() self.on_new_head_cbs = [] - self.on_new_head_candidate_cbs = [] self.newblock_processing_times = deque(maxlen=1000) @property @@ -173,20 +196,40 @@ def is_syncing(self): def is_mining(self): if 'pow' in self.app.services: return self.app.services.pow.active + if 'validator' in self.app.services: + return self.app.services.validator.active return False + # TODO: Move to pyethereum + def get_receipts(self, block): + # Receipts are no longer stored in the database, so need to generate + # them on the fly here. + temp_state = self.chain.mk_poststate_of_blockhash(block.header.prevhash) + initialize(temp_state, block) + for tx in block.transactions: + apply_transaction(temp_state, tx) + return temp_state.receipts + def _on_new_head(self, block): log.debug('new head cbs', num=len(self.on_new_head_cbs)) + self.transaction_queue = self.transaction_queue.diff( + block.transactions) + self._head_candidate_needs_updating = True for cb in self.on_new_head_cbs: cb(block) - self._on_new_head_candidate() # we implicitly have a new head_candidate - - def _on_new_head_candidate(self): - # DEBUG('new head candidate cbs', len(self.on_new_head_candidate_cbs)) - for cb in self.on_new_head_candidate_cbs: - cb(self.chain.head_candidate) - def add_transaction(self, tx, origin=None, force_broadcast=False): + @property + def head_candidate(self): + if self._head_candidate_needs_updating: + self._head_candidate_needs_updating = False + # Make a copy of self.transaction_queue because + # make_head_candidate modifies it. + txqueue = copy.deepcopy(self.transaction_queue) + self._head_candidate, self._head_candidate_state = make_head_candidate( + self.chain, txqueue, timestamp=int(time.time())) + return self._head_candidate + + def add_transaction(self, tx, origin=None, force_broadcast=False, force=False): if self.is_syncing: if force_broadcast: assert origin is None # only allowed for local txs @@ -203,7 +246,10 @@ def add_transaction(self, tx, origin=None, force_broadcast=False): # validate transaction try: - validate_transaction(self.chain.head_candidate, tx) + # Transaction validation for broadcasting. Transaction is validated + # against the current head candidate. + validate_transaction(self._head_candidate_state, tx) + log.debug('valid tx, broadcasting') self.broadcast_transaction(tx, origin=origin) # asap except InvalidTransaction as e: @@ -215,12 +261,16 @@ def add_transaction(self, tx, origin=None, force_broadcast=False): log.debug('discarding tx', syncing=self.is_syncing, mining=self.is_mining) return - self.add_transaction_lock.acquire() - success = self.chain.add_transaction(tx) - self.add_transaction_lock.release() - if success: - self._on_new_head_candidate() - return success + if tx.gasprice >= self.min_gasprice: + self.add_transaction_lock.acquire() + self.transaction_queue.add_transaction(tx, force=force) + self._head_candidate_needs_updating = True + self.add_transaction_lock.release() + else: + log.info("too low gasprice, ignore", tx=encode_hex(tx.hash)[:8], gasprice=tx.gasprice) + + def check_header(self, header): + return check_pow(self.chain.state, header) def add_block(self, t_block, proto): "adds a block to the block_queue and spawns _add_block if not running" @@ -232,15 +282,19 @@ def add_block(self, t_block, proto): def add_mined_block(self, block): log.debug('adding mined block', block=block) assert isinstance(block, Block) - assert block.header.check_pow() if self.chain.add_block(block): log.debug('added', block=block, ts=time.time()) assert block == self.chain.head - self.broadcast_newblock(block, chain_difficulty=block.chain_difficulty()) + self.transaction_queue = self.transaction_queue.diff(block.transactions) + self._head_candidate_needs_updating = True + self.broadcast_newblock(block, chain_difficulty=self.chain.get_score(block)) + return True + log.debug('failed to add', block=block, ts=time.time()) + return False def knows_block(self, block_hash): "if block is in chain or in queue" - if block_hash in self.chain: + if self.chain.has_blockhash(block_hash): return True # check if queued or processed for i in range(len(self.block_queue.queue)): @@ -259,23 +313,17 @@ def _add_blocks(self): gevent.sleep(0.001) t_block, proto = self.block_queue.peek() # peek: knows_block while processing - if t_block.header.hash in self.chain: + if self.chain.has_blockhash(t_block.header.hash): log.warn('known block', block=t_block) self.block_queue.get() continue - if t_block.header.prevhash not in self.chain: + if not self.chain.has_blockhash(t_block.header.prevhash): log.warn('missing parent', block=t_block, head=self.chain.head) self.block_queue.get() continue - # FIXME, this is also done in validation and in synchronizer for new_blocks - if not t_block.header.check_pow(): - log.warn('invalid pow', block=t_block, FIXME='ban node') - sentry.warn_invalid(t_block, 'InvalidBlockNonce') - self.block_queue.get() - continue try: # deserialize st = time.time() - block = t_block.to_block(env=self.chain.env) + block = t_block.to_block() elapsed = time.time() - st log.debug('deserialized', elapsed='%.4fs' % elapsed, ts=time.time(), gas_used=block.gas_used, gpsec=self.gpsec(block.gas_used, elapsed)) @@ -297,7 +345,7 @@ def _add_blocks(self): # All checks passed log.debug('adding', block=block, ts=time.time()) - if self.chain.add_block(block, forward_pending_transactions=self.is_mining): + if self.chain.add_block(block): now = time.time() log.info('added', block=block, txs=block.transaction_count, gas_used=block.gas_used) @@ -310,6 +358,8 @@ def _add_blocks(self): min_ = min(self.newblock_processing_times) log.info('processing time', last=total, avg=avg, max=max_, min=min_, median=med) + if self.is_mining: + self.transaction_queue = self.transaction_queue.diff(block.transactions) else: log.warn('could not add', block=block) @@ -326,8 +376,8 @@ def gpsec(self, gas_spent=0, elapsed=0): def broadcast_newblock(self, block, chain_difficulty=None, origin=None): if not chain_difficulty: - assert block.hash in self.chain - chain_difficulty = block.chain_difficulty() + assert self.chain.has_blockhash(block.hash) + chain_difficulty = self.chain.get_score(block) assert isinstance(block, (eth_protocol.TransientBlock, Block)) if self.broadcast_filter.update(block.header.hash): log.debug('broadcasting newblock', origin=origin) @@ -365,7 +415,7 @@ def on_wire_protocol_start(self, proto): # send status head = self.chain.head - proto.send_status(chain_difficulty=head.chain_difficulty(), chain_head_hash=head.hash, + proto.send_status(chain_difficulty=self.chain.get_score(head), chain_head_hash=head.hash, genesis_hash=self.chain.genesis.hash) def on_wire_protocol_stop(self, proto): @@ -377,9 +427,20 @@ def on_receive_status(self, proto, eth_version, network_id, chain_difficulty, ch genesis_hash): log.debug('----------------------------------') log.debug('status received', proto=proto, eth_version=eth_version) - assert eth_version == proto.version, (eth_version, proto.version) + + if eth_version != proto.version: + if ('eth', proto.version) in proto.peer.remote_capabilities: + # if remote peer is capable of our version, keep the connection + # even the peer tried a different version + pass + else: + log.debug("no capable protocol to use, disconnect", + proto=proto, eth_version=eth_version) + proto.send_disconnect(proto.disconnect.reason.useless_peer) + return + if network_id != self.config['eth'].get('network_id', proto.network_id): - log.warn("invalid network id", remote_network_id=network_id, + log.debug("invalid network id", remote_network_id=network_id, expected_network_id=self.config['eth'].get('network_id', proto.network_id)) raise eth_protocol.ETHProtocolError('wrong network_id') @@ -388,14 +449,26 @@ def on_receive_status(self, proto, eth_version, network_id, chain_difficulty, ch log.warn("invalid genesis hash", remote_id=proto, genesis=genesis_hash.encode('hex')) raise eth_protocol.ETHProtocolError('wrong genesis block') - # request chain - self.synchronizer.receive_status(proto, chain_head_hash, chain_difficulty) - - # send transactions - transactions = self.chain.get_transactions() - if transactions: - log.debug("sending transactions", remote_id=proto) - proto.send_transactions(*transactions) + # initiate DAO challenge + self.dao_challenges[proto] = (DAOChallenger(self, proto), chain_head_hash, chain_difficulty) + + def on_dao_challenge_answer(self, proto, result): + if result: + log.debug("DAO challenge passed") + _, chain_head_hash, chain_difficulty = self.dao_challenges[proto] + + # request chain + self.synchronizer.receive_status(proto, chain_head_hash, chain_difficulty) + # send transactions + transactions = self.transaction_queue.peek() + if transactions: + log.debug("sending transactions", remote_id=proto) + proto.send_transactions(*transactions) + else: + log.debug("peer failed to answer DAO challenge, stop.", proto=proto) + if proto.peer: + proto.peer.stop() + del self.dao_challenges[proto] # transactions @@ -436,10 +509,10 @@ def on_receive_getblockheaders(self, proto, hash_or_number, block, amount, skip, proto.send_blockheaders(*headers) return try: - origin_hash = self.chain.index.get_block_by_number(hash_or_number[1]) + origin_hash = self.chain.get_blockhash_by_number(hash_or_number[1]) except KeyError: origin_hash = b'' - if not origin_hash or origin_hash not in self.chain: + if not origin_hash or self.chain.has_blockhash(origin_hash): log.debug("unknown block") proto.send_blockheaders(*[]) return @@ -449,7 +522,12 @@ def on_receive_getblockheaders(self, proto, hash_or_number, block, amount, skip, if not origin_hash: break try: - origin = get_block_header(self.chain.db, origin_hash) + 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 @@ -459,15 +537,15 @@ def on_receive_getblockheaders(self, proto, hash_or_number, block, amount, skip, if reverse: for i in xrange(skip+1): try: - header = get_block_header(self.chain.db, origin_hash) + header = self.chain.get_block(origin_hash) origin_hash = header.prevhash except KeyError: unknown = True break else: - origin_hash = self.chain.index.get_block_by_number(origin.number + skip + 1) + origin_hash = self.chain.get_blockhash_by_number(origin.number + skip + 1) try: - header = get_block_header(self.chain.db, origin_hash) + 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: @@ -478,13 +556,13 @@ def on_receive_getblockheaders(self, proto, hash_or_number, block, amount, skip, if reverse: if origin.number >= (skip+1): number = origin.number - (skip + 1) - origin_hash = self.chain.index.get_block_by_number(number) + origin_hash = self.chain.get_blockhash_by_number(number) else: unknown = True else: number = origin.number + skip + 1 try: - origin_hash = self.chain.index.get_block_by_number(number) + origin_hash = self.chain.get_blockhash_by_number(number) except KeyError: unknown = True @@ -498,7 +576,11 @@ def on_receive_blockheaders(self, proto, blockheaders): first=encode_hex(blockheaders[0].hash), last=encode_hex(blockheaders[-1].hash)) else: log.debug("recv 0 remote block headers, signifying genesis block") - self.synchronizer.receive_blockheaders(proto, blockheaders) + + if proto in self.dao_challenges: + self.dao_challenges[proto][0].receive_blockheaders(proto, blockheaders) + else: + self.synchronizer.receive_blockheaders(proto, blockheaders) # blocks ################ diff --git a/pyethapp/jsonrpc.py b/pyethapp/jsonrpc.py index 70bb9c89..e46beecc 100644 --- a/pyethapp/jsonrpc.py +++ b/pyethapp/jsonrpc.py @@ -3,9 +3,17 @@ from copy import deepcopy from collections import Iterable -import ethereum.blocks import ethereum.bloom as bloom +from ethereum.utils import (is_numeric, is_string, int_to_big_endian, big_endian_to_int, + encode_hex, decode_hex, sha3, zpad, denoms, int32) import ethereum.slogging as slogging +from ethereum.slogging import LogRecorder +from ethereum.config import Env +from ethereum.block import Block +from ethereum.state import State +from ethereum.messages import apply_transaction +from ethereum.transactions import Transaction +from ethereum.genesis_helpers import mk_genesis_block import gevent import gevent.queue import gevent.wsgi @@ -13,15 +21,8 @@ from decorator import decorator from accounts import Account from devp2p.service import BaseService -from ethereum import processblock from ethereum.exceptions import InvalidTransaction -from ethereum.slogging import LogRecorder -from ethereum.transactions import Transaction from ethereum.trie import Trie -from ethereum.utils import ( - big_endian_to_int, decode_hex, denoms, encode_hex, int_to_big_endian, is_numeric, - is_string, int32, sha3, zpad, -) from eth_protocol import ETHProtocol from ipc_rpc import bind_unix_listener, serve from tinyrpc.dispatch import public as public_ @@ -99,7 +100,7 @@ def __init__(self): super(LoggingDispatcher, self).__init__() self.logger = log.debug - def dispatch(self, request): + def dispatch(self, request, caller=None): try: if isinstance(request, Iterable): request_list = request @@ -177,26 +178,31 @@ def get_block(self, block_id=None): log.debug("get_block") assert 'chain' in self.app.services chain = self.app.services.chain.chain + head_candidate = self.app.services.chain.head_candidate if block_id is None: block_id = self.default_block else: self.default_block = block_id + if block_id == 'pending': - return self.app.services.chain.chain.head_candidate - if block_id == 'latest': - return chain.head - if block_id == 'earliest': - block_id = 0 - if is_numeric(block_id): - # by number - hash_ = chain.index.get_block_by_number(block_id) - elif block_id == chain.head_candidate.hash: - return chain.head_candidate + block = head_candidate + elif block_id == 'latest': + block = chain.head + elif block_id == 'earliest': + block = chain.get_block_by_number(0) + elif is_numeric(block_id): + block = chain.get_block_by_number(block_id) + elif block_id == head_candidate.hash: + block = head_candidate else: # by hash assert is_string(block_id) - hash_ = block_id - return chain.get(hash_) + block = chain.get_block(block_id) + if block is None: + raise KeyError("Block with id %s does not exist" % block_id) + + block.score = chain.get_score(block) + return block class IPCRPCServer(RPCServer): @@ -442,7 +448,7 @@ def bool_decoder(data): def block_encoder(block, include_transactions=False, pending=False, is_header=False): """Encode a block as JSON object. - :param block: a :class:`ethereum.blocks.Block` + :param block: a :class:`ethereum.block.Block` :param include_transactions: if true transactions are included, otherwise only their hashes :param pending: if `True` the block number of transactions, if included, is set to `None` @@ -469,15 +475,15 @@ def block_encoder(block, include_transactions=False, pending=False, is_header=Fa 'timestamp': quantity_encoder(block.timestamp), } if not is_header: - d['totalDifficulty'] = quantity_encoder(block.chain_difficulty()) + d['totalDifficulty'] = quantity_encoder(block.score) d['size'] = quantity_encoder(len(rlp.encode(block))) d['uncles'] = [data_encoder(u.hash) for u in block.uncles] if include_transactions: d['transactions'] = [] - for i, tx in enumerate(block.get_transactions()): + for i, tx in enumerate(block.transactions): d['transactions'].append(tx_encoder(tx, block, i, pending)) else: - d['transactions'] = [data_encoder(tx.hash) for tx in block.get_transactions()] + d['transactions'] = [data_encoder(tx.hash) for tx in block.transactions] return d @@ -499,6 +505,9 @@ def tx_encoder(transaction, block, i, pending): 'gasPrice': quantity_encoder(transaction.gasprice), 'gas': quantity_encoder(transaction.startgas), 'input': data_encoder(transaction.data), + 'r': quantity_encoder(transaction.r), + 's': quantity_encoder(transaction.s), + 'v': quantity_encoder(transaction.v), } @@ -524,8 +533,9 @@ def loglist_encoder(loglist): return result -def filter_decoder(filter_dict, chain): +def filter_decoder(filter_dict, chainservice): """Decodes a filter as expected by eth_newFilter or eth_getLogs to a :class:`Filter`.""" + chain = chainservice.chain if not isinstance(filter_dict, dict): raise BadRequestError('Filter must be an object') address = filter_dict.get('address', None) @@ -575,13 +585,13 @@ def filter_decoder(filter_dict, chain): block_id_dict = { 'earliest': 0, 'latest': chain.head.number, - 'pending': chain.head_candidate.number + 'pending': chainservice.head_candidate.number } range_ = [b if is_numeric(b) else block_id_dict[b] for b in (from_block, to_block)] if range_[0] > range_[1]: raise JSONRPCInvalidParamsError('fromBlock must not be newer than toBlock') - return LogFilter(chain, from_block, to_block, addresses, topics) + return LogFilter(chainservice, from_block, to_block, addresses, topics) def decode_arg(name, decoder): @@ -700,8 +710,8 @@ def compilers(self): except ImportError: pass try: - import ethereum._solidity - s = ethereum._solidity.get_solidity() + import ethereum.tools._solidity + s = ethereum.tools._solidity.get_solidity() if s: self.compilers_['solidity'] = s.compile_rich else: @@ -769,7 +779,8 @@ def gasPrice(self): @public def accounts(self): - return [address_encoder(account.address) for account in self.app.services.accounts] + return [address_encoder(self.app.services.accounts.coinbase)] + \ + [address_encoder(account.address) for account in self.app.services.accounts] class DB(Subdispatcher): @@ -841,7 +852,8 @@ def blockNumber(self): @encode_res(quantity_encoder) def getBalance(self, address, block_id=None): block = self.json_rpc_server.get_block(block_id) - return block.get_balance(address) + state = State(block.state_root, self.chain.chain.env) + return state.get_balance(address) @public @decode_arg('address', address_decoder) @@ -859,50 +871,43 @@ def getStorageAt(self, address, index, block_id=None): @encode_res(quantity_encoder) def getTransactionCount(self, address, block_id='pending'): block = self.json_rpc_server.get_block(block_id) - return block.get_nonce(address) - \ + state = State(block.state_root, self.chain.chain.env) + return state.get_nonce(address) - \ self.json_rpc_server.config['eth']['block']['ACCOUNT_INITIAL_NONCE'] @public @decode_arg('block_hash', block_hash_decoder) def getBlockTransactionCountByHash(self, block_hash): - try: - block = self.json_rpc_server.get_block(block_hash) - except KeyError: + block = self.json_rpc_server.get_block(block_hash) + if block is None: return None - else: - return quantity_encoder(block.transaction_count) + return quantity_encoder(block.transaction_count) @public @decode_arg('block_id', block_id_decoder) def getBlockTransactionCountByNumber(self, block_id): - try: - block = self.json_rpc_server.get_block(block_id) - except KeyError: + block = self.json_rpc_server.get_block(block_id) + if block is None: return None - else: - return quantity_encoder(block.transaction_count) + return quantity_encoder(block.transaction_count) @public @decode_arg('block_hash', block_hash_decoder) def getUncleCountByBlockHash(self, block_hash): - try: - block = self.json_rpc_server.get_block(block_hash) - except KeyError: + block = self.json_rpc_server.get_block(block_hash) + if block is None: return None - else: - return quantity_encoder(len(block.uncles)) + return quantity_encoder(len(block.uncles)) @public @decode_arg('block_id', block_id_decoder) def getUncleCountByBlockNumber(self, block_id): - try: - if block_id == u'pending': - return None - block = self.json_rpc_server.get_block(block_id) - except KeyError: + if block_id == u'pending': return None - else: - return quantity_encoder(len(block.uncles)) + block = self.json_rpc_server.get_block(block_id) + if block is None: + return None + return quantity_encoder(len(block.uncles)) @public @decode_arg('address', address_decoder) @@ -910,15 +915,15 @@ def getUncleCountByBlockNumber(self, block_id): @encode_res(data_encoder) def getCode(self, address, block_id=None): block = self.json_rpc_server.get_block(block_id) - return block.get_code(address) + state = State(block.state_root, self.chain.chain.env) + return state.get_code(address) @public @decode_arg('block_hash', block_hash_decoder) @decode_arg('include_transactions', bool_decoder) def getBlockByHash(self, block_hash, include_transactions): - try: - block = self.json_rpc_server.get_block(block_hash) - except KeyError: + block = self.json_rpc_server.get_block(block_hash) + if block is None: return None return block_encoder(block, include_transactions) @@ -926,9 +931,8 @@ def getBlockByHash(self, block_hash, include_transactions): @decode_arg('block_id', block_id_decoder) @decode_arg('include_transactions', bool_decoder) def getBlockByNumber(self, block_id, include_transactions): - try: - block = self.json_rpc_server.get_block(block_id) - except KeyError: + block = self.json_rpc_server.get_block(block_id) + if block is None: return None return block_encoder(block, include_transactions, pending=block_id == 'pending') @@ -936,8 +940,8 @@ def getBlockByNumber(self, block_id, include_transactions): @decode_arg('tx_hash', tx_hash_decoder) def getTransactionByHash(self, tx_hash): try: - tx, block, index = self.chain.chain.index.get_transaction(tx_hash) - if self.chain.chain.in_main_branch(block): + tx, block, index = self.chain.chain.get_transaction(tx_hash) + if block in self.chain.chain: return tx_encoder(tx, block, index, False) except KeyError: return None @@ -948,6 +952,8 @@ def getTransactionByHash(self, tx_hash): @decode_arg('index', quantity_decoder) def getTransactionByBlockHashAndIndex(self, block_hash, index): block = self.json_rpc_server.get_block(block_hash) + if block is None: + return None try: tx = block.get_transaction(index) except IndexError: @@ -959,6 +965,8 @@ def getTransactionByBlockHashAndIndex(self, block_hash, index): @decode_arg('index', quantity_decoder) def getTransactionByBlockNumberAndIndex(self, block_id, index): block = self.json_rpc_server.get_block(block_id) + if block is None: + return None try: tx = block.get_transaction(index) except IndexError: @@ -969,10 +977,12 @@ def getTransactionByBlockNumberAndIndex(self, block_id, index): @decode_arg('block_hash', block_hash_decoder) @decode_arg('index', quantity_decoder) def getUncleByBlockHashAndIndex(self, block_hash, index): + block = self.json_rpc_server.get_block(block_hash) + if block is None: + return None try: - block = self.json_rpc_server.get_block(block_hash) uncle = block.uncles[index] - except (IndexError, KeyError): + except IndexError: return None return block_encoder(uncle, is_header=True) @@ -980,20 +990,22 @@ def getUncleByBlockHashAndIndex(self, block_hash, index): @decode_arg('block_id', block_id_decoder) @decode_arg('index', quantity_decoder) def getUncleByBlockNumberAndIndex(self, block_id, index): + # TODO: think about moving this check into the Block.uncles property + if block_id == u'pending': + return None + block = self.json_rpc_server.get_block(block_id) + if block is None: + return None try: - # TODO: think about moving this check into the Block.uncles property - if block_id == u'pending': - return None - block = self.json_rpc_server.get_block(block_id) uncle = block.uncles[index] - except (IndexError, KeyError): + except IndexError: return None return block_encoder(uncle, is_header=True) @public def getWork(self): print 'Sending work...' - h = self.chain.chain.head_candidate + h = self.chain.head_candidate return [ encode_hex(h.header.mining_hash), encode_hex(h.header.seed), @@ -1024,7 +1036,7 @@ def lastGasPrice(self): @decode_arg('mix_digest', data_decoder) def submitWork(self, nonce, mining_hash, mix_digest): print 'submitting work' - h = self.chain.chain.head_candidate + h = self.chain.head_candidate print 'header: %s' % encode_hex(rlp.encode(h)) if h.header.mining_hash != mining_hash: return False @@ -1034,7 +1046,7 @@ def submitWork(self, nonce, mining_hash, mix_digest): print 'seed: %s' % encode_hex(h.header.seed) h.header.nonce = nonce h.header.mixhash = mix_digest - if not h.header.check_pow(): + if not self.chain.check_header(h.header): print 'PoW check false' return False print 'PoW check true' @@ -1062,7 +1074,8 @@ def nonce(self, address, block_id='pending'): assert address is not None block = self.json_rpc_server.get_block(block_id) assert block is not None - nonce = block.get_nonce(address) + state = State(block.state_root, self.chain.chain.env) + nonce = state.get_nonce(address) assert nonce is not None and isinstance(nonce, int) return nonce @@ -1099,7 +1112,9 @@ def get_data_default(key, decoder, default=None): assert v and r and s else: if nonce is None or nonce == 0: - nonce = self.app.services.chain.chain.head_candidate.get_nonce(sender) + hc = self.chain.head_candidate + state = State(hc.state_root, self.chain.chain.env) + nonce = state.get_nonce(sender) tx = Transaction(nonce, gasprice, startgas, to, value, data_, v, r, s) tx._sender = None @@ -1107,7 +1122,7 @@ def get_data_default(key, decoder, default=None): assert sender in self.app.services.accounts, 'can not sign: no account for sender' self.app.services.accounts.sign_tx(sender, tx) self.app.services.chain.add_transaction(tx, origin=None, force_broadcast=True) - log.debug('decoded tx', tx=tx.log_dict()) + log.debug('decoded tx', tx=tx.to_dict()) return data_encoder(tx.hash) @public @@ -1117,17 +1132,17 @@ def sendRawTransaction(self, data): decode sendRawTransaction request data, format it and relay along to the sendTransaction method to ensure the same validations and processing rules are applied """ - tx_data = rlp.codec.decode(data, ethereum.transactions.Transaction) + tx_data = rlp.codec.decode(data, Transaction) tx_dict = tx_data.to_dict() # encode addresses - tx_dict['from'] = address_encoder(tx_dict.get('sender', '')) + tx_dict['from'] = tx_dict.get('sender', '') to_value = tx_dict.get('to', '') if to_value: - tx_dict['to'] = address_encoder(to_value) + tx_dict['to'] = to_value # encode data - tx_dict['data'] = data_encoder(tx_dict.get('data', b'')) + tx_dict['data'] = tx_dict.get('data', data_encoder(b'')) # encode quantities included in the raw transaction data gasprice_key = 'gasPrice' if 'gasPrice' in tx_dict else 'gasprice' @@ -1149,19 +1164,19 @@ def call(self, data, block_id='pending'): # rebuild block state before finalization if block.has_parent(): - parent = block.get_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 = processblock.apply_transaction(test_block, tx) + success, output = apply_transaction(test_block, tx) assert success else: - env = ethereum.config.Env(db=block.db) - test_block = ethereum.blocks.genesis(env) + 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 = ethereum.blocks.genesis(env) + test_block = mk_genesis_block(env) test_block.revert(original) # validate transaction @@ -1195,7 +1210,7 @@ def call(self, data, block_id='pending'): tx.sender = sender try: - success, output = processblock.apply_transaction(test_block, tx) + success, output = apply_transaction(test_block, tx) except InvalidTransaction: success = False # make sure we didn't change the real state @@ -1218,18 +1233,19 @@ def estimateGas(self, data, block_id='pending'): # rebuild block state before finalization if block.has_parent(): - parent = block.get_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 = processblock.apply_transaction(test_block, tx) + success, output = apply_transaction(test_block, tx) assert success else: - test_block = ethereum.blocks.genesis(block.db) + 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 = ethereum.blocks.genesis(block.db) + test_block = mk_genesis_block(env) test_block.revert(original) # validate transaction @@ -1263,7 +1279,7 @@ def estimateGas(self, data, block_id='pending'): tx.sender = sender try: - success, output = processblock.apply_transaction(test_block, tx) + success, output = apply_transaction(test_block, tx) except InvalidTransaction: success = False # make sure we didn't change the real state @@ -1290,8 +1306,9 @@ class LogFilter(object): logs again or `None` if no block has been checked yet """ - def __init__(self, chain, first_block, last_block, addresses=None, topics=None): - self.chain = chain + def __init__(self, chainservice, first_block, last_block, addresses=None, topics=None): + self.chainservice = chainservice + self.chain = chainservice.chain assert is_numeric(first_block) or first_block in ('latest', 'pending', 'earliest') assert is_numeric(last_block) or last_block in ('latest', 'pending', 'earliest') self.first_block = first_block @@ -1328,7 +1345,7 @@ def check(self): block_id_dict = { 'earliest': 0, 'latest': self.chain.head.number, - 'pending': self.chain.head_candidate.number + 'pending': self.chainservice.head_candidate.number } if is_numeric(self.first_block): first = self.first_block @@ -1350,12 +1367,12 @@ def check(self): blocks_to_check = [] for n in range(first, last): - blocks_to_check.append(self.chain.index.get_block_by_number(n)) + blocks_to_check.append(self.chain.get_block_by_number(n)) # last block may be head candidate, which cannot be retrieved via get_block_by_number - if last == self.chain.head_candidate.number: - blocks_to_check.append(self.chain.head_candidate) + if last == self.chainservice.head_candidate.number: + blocks_to_check.append(self.chainservice.head_candidate) else: - blocks_to_check.append(self.chain.get(self.chain.index.get_block_by_number(last))) + blocks_to_check.append(self.chain.get_block_by_number(last)) logger.debug('obtained block hashes to check with filter', numhashes=len(blocks_to_check)) # go through all receipts of all blocks @@ -1363,8 +1380,8 @@ def check(self): new_logs = {} for i, block in enumerate(blocks_to_check): - if not isinstance(block, (ethereum.blocks.Block, ethereum.blocks.CachedBlock)): - _bloom = self.chain.get_bloom(block) + if not isinstance(block, Block): + _bloom = self.chain.get_block(block).bloom # Check that the bloom for this block contains at least one of the desired # addresses if self.addresses: @@ -1399,11 +1416,11 @@ def check(self): _topic_bloom = bloom.bloom_from_list(map(int32.serialize, self.topics or [])) if bloom.bloom_combine(_bloom, _topic_bloom) != _bloom: continue - block = self.chain.get(block) + block = self.chain.get_block(block) print 'bloom filter passed' logger.debug('-') logger.debug('with block', block=block) - receipts = block.get_receipts() + receipts = self.chainservice.get_receipts(block) logger.debug('receipts', block=block, receipts=receipts) for r_idx, receipt in enumerate(receipts): # one receipt per tx for l_idx, log in enumerate(receipt.logs): @@ -1428,21 +1445,21 @@ def check(self): if self.addresses is not None and log.address not in self.addresses: continue # still here, so match was successful => add to log list - tx = block.get_transaction(r_idx) - id_ = ethereum.utils.sha3(''.join((tx.hash, str(l_idx)))) - pending = block == self.chain.head_candidate + tx = block.transactions[r_idx] + id_ = sha3(''.join((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')) 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] - if blocks_to_check[-1] != self.chain.head_candidate: + if blocks_to_check[-1] != self.chainservice.head_candidate: self.last_block_checked = blocks_to_check[-1] else: 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, (ethereum.blocks.Block, ethereum.blocks.CachedBlock)): - self.last_block_checked = self.chain.get(self.last_block_checked) + 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() self.log_dict.update(new_logs) return {id_: new_logs[id_] for id_ in actually_new_ids} @@ -1483,7 +1500,7 @@ def check(self): block = self.chain.head while block.number > self.latest_block.number: new_blocks.append(block) - block = block.get_parent() + block = self.chain.get_parent(block) assert block.number == self.latest_block.number if block != self.latest_block: log.warning('previous latest block not in current chain', @@ -1505,24 +1522,25 @@ class PendingTransactionFilter(object): :ivar reported_txs: txs from the (formerly) pending block which don't have to be reported again """ - def __init__(self, chain): - self.chain = chain - self.latest_block = self.chain.head_candidate + def __init__(self, chainservice): + self.chainservice = chainservice + self.chain = chainservice.chain + self.latest_block = self.chainservice.head_candidate self.reported_txs = [] # needs only to contain txs from latest block def check(self): """Check for new transactions and return them.""" # check current pending block first - pending_txs = self.chain.head_candidate.get_transactions() + pending_txs = self.chainservice.head_candidate.transactions new_txs = list(reversed([tx for tx in pending_txs if tx not in self.reported_txs])) # now check all blocks which have already been finalized - block = self.chain.head_candidate.get_parent() + block = self.chain.get_parent(self.chainservice.head_candidate) while block.number >= self.latest_block.number: - for tx in reversed(block.get_transactions()): + for tx in reversed(block.transactions): if tx not in self.reported_txs: new_txs.append(tx) - block = block.get_parent() - self.latest_block = self.chain.head_candidate + block = self.chain.get_parent(block) + self.latest_block = self.chainservice.head_candidate self.reported_txs = pending_txs return reversed(new_txs) @@ -1539,7 +1557,7 @@ def __init__(self): @public @encode_res(quantity_encoder) def newFilter(self, filter_dict): - filter_ = filter_decoder(filter_dict, self.chain.chain) + filter_ = filter_decoder(filter_dict, self.chain) self.filters[self.next_id] = filter_ self.next_id += 1 return self.next_id - 1 @@ -1555,7 +1573,7 @@ def newBlockFilter(self): @public @encode_res(quantity_encoder) def newPendingTransactionFilter(self): - filter_ = PendingTransactionFilter(self.chain.chain) + filter_ = PendingTransactionFilter(self.chain) self.filters[self.next_id] = filter_ self.next_id += 1 return self.next_id - 1 @@ -1597,18 +1615,18 @@ def getFilterLogs(self, id_): @public @encode_res(loglist_encoder) def getLogs(self, filter_dict): - filter_ = filter_decoder(filter_dict, self.chain.chain) + filter_ = filter_decoder(filter_dict, self.chain) return filter_.logs # ########### Trace ############ def _get_block_before_tx(self, txhash): - tx, blk, i = self.app.services.chain.chain.index.get_transaction(txhash) + tx, blk, i = self.app.services.chain.chain.get_transaction(txhash) # get the state we had before this transaction - test_blk = ethereum.blocks.Block.init_from_parent(blk.get_parent(), - blk.coinbase, - extra_data=blk.extra_data, - timestamp=blk.timestamp, - uncles=blk.uncles) + test_blk = Block.init_from_parent(self.chain.get_parent(blk), + blk.coinbase, + extra_data=blk.extra_data, + timestamp=blk.timestamp, + uncles=blk.uncles) pre_state = test_blk.state_root for i in range(blk.transaction_count): tx_lst_serialized, sr, _ = blk.get_transaction(i) @@ -1629,7 +1647,7 @@ def _get_trace(self, txhash): recorder = LogRecorder() # apply tx (thread? we don't want logs from other invocations) self.app.services.chain.add_transaction_lock.acquire() - processblock.apply_transaction(test_blk, tx) # FIXME deactivate tx context switch or lock + apply_transaction(test_blk, tx) # FIXME deactivate tx context switch or lock self.app.services.chain.add_transaction_lock.release() return dict(tx=txhash, trace=recorder.pop_records()) @@ -1660,12 +1678,12 @@ def trace_block(self, blockhash, exclude=[]): @decode_arg('tx_hash', tx_hash_decoder) def getTransactionReceipt(self, tx_hash): try: - tx, block, index = self.chain.chain.index.get_transaction(tx_hash) + tx, block, index = self.chain.chain.get_transaction(tx_hash) except KeyError: return None - if not self.chain.chain.in_main_branch(block): + if block not in self.chain.chain: return None - receipt = block.get_receipt(index) + receipt = self.chain.get_receipts(block)[index] response = { 'transactionHash': data_encoder(tx.hash), 'transactionIndex': quantity_encoder(index), @@ -1677,7 +1695,7 @@ def getTransactionReceipt(self, tx_hash): if index == 0: response['gasUsed'] = quantity_encoder(receipt.gas_used) else: - prev_receipt = block.get_receipt(index - 1) + prev_receipt = temp_state.receipts[index - 1] assert prev_receipt.gas_used < receipt.gas_used response['gasUsed'] = quantity_encoder(receipt.gas_used - prev_receipt.gas_used) diff --git a/pyethapp/pow_service.py b/pyethapp/pow_service.py index 98b8919c..a66bec66 100644 --- a/pyethapp/pow_service.py +++ b/pyethapp/pow_service.py @@ -3,9 +3,9 @@ import gipc import random from devp2p.service import BaseService -from devp2p.app import BaseApp -from ethereum.ethpow import mine, TT64M1 +from ethereum.pow.ethpow import mine, TT64M1 from ethereum.slogging import get_logger +from ethereum.utils import encode_hex log = get_logger('pow') log_sub = get_logger('pow.subprocess') @@ -23,7 +23,6 @@ def __init__(self, mining_hash, block_number, difficulty, nonce_callback, self.nonce_callback = nonce_callback self.hashrate_callback = hashrate_callback self.cpu_pct = cpu_pct - self.last = time.time() self.is_stopped = False super(Miner, self).__init__() @@ -122,28 +121,26 @@ def __init__(self, app): self.cpipe, self.ppipe = gipc.pipe(duplex=True) self.worker_process = gipc.start_process( target=powworker_process, args=(self.cpipe, cpu_pct)) - self.app.services.chain.on_new_head_candidate_cbs.append(self.on_new_head_candidate) + self.chain = app.services.chain + self.chain.on_new_head_cbs.append(self.mine_head_candidate) self.hashrate = 0 @property def active(self): return self.app.config['pow']['activated'] - def on_new_head_candidate(self, block): - log.debug('new head candidate', block_number=block.number, - mining_hash=block.mining_hash.encode('hex'), activated=self.active) - if not self.active: + def mine_head_candidate(self, _=None): + hc = self.chain.head_candidate + if not self.active or self.chain.is_syncing: return - if self.app.services.chain.is_syncing: - return - if (block.transaction_count == 0 and - not self.app.config['pow']['mine_empty_blocks']): + elif (hc.transaction_count == 0 and + not self.app.config['pow']['mine_empty_blocks']): return - log.debug('mining', difficulty=block.difficulty) - self.ppipe.put(('mine', dict(mining_hash=block.mining_hash, - block_number=block.number, - difficulty=block.difficulty))) + log.debug('mining', difficulty=hc.difficulty) + self.ppipe.put(('mine', dict(mining_hash=hc.mining_hash, + block_number=hc.number, + difficulty=hc.difficulty))) def recv_hashrate(self, hashrate): log.trace('hashrate updated', hashrate=hashrate) @@ -151,17 +148,20 @@ def recv_hashrate(self, hashrate): def recv_found_nonce(self, bin_nonce, mixhash, mining_hash): log.info('nonce found', mining_hash=mining_hash.encode('hex')) - blk = self.app.services.chain.chain.head_candidate - if blk.mining_hash != mining_hash: + block = self.chain.head_candidate + if block.mining_hash != mining_hash: log.warn('mining_hash does not match') - self.mine_head_candidate() - return - blk.mixhash = mixhash - blk.nonce = bin_nonce - self.app.services.chain.add_mined_block(blk) - - def mine_head_candidate(self): - self.on_new_head_candidate(self.app.services.chain.chain.head_candidate) + return False + block.header.mixhash = mixhash + block.header.nonce = bin_nonce + if self.chain.add_mined_block(block): + log.debug('mined block %d (%s) added to chain' % ( + block.number, encode_hex(block.hash[:8]))) + return True + else: + log.debug('failed to add mined block %d (%s) to chain' % ( + block.number, encode_hex(block.hash[:8]))) + return False def _run(self): self.mine_head_candidate() @@ -174,8 +174,3 @@ def stop(self): self.worker_process.terminate() self.worker_process.join() super(PoWService, self).stop() - -if __name__ == "__main__": - app = BaseApp() - PoWService.register_with_app(app) - app.start() diff --git a/pyethapp/rpc_client.py b/pyethapp/rpc_client.py index 81eab672..fe964e20 100644 --- a/pyethapp/rpc_client.py +++ b/pyethapp/rpc_client.py @@ -1,15 +1,14 @@ """ A simple way of interacting to a ethereum node through JSON RPC commands. """ import logging -import time import warnings import json import gevent from ethereum.abi import ContractTranslator -from ethereum.keys import privtoaddr +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._solidity import solidity_unresolved_symbols, solidity_library_symbol, solidity_resolve_symbols +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 from tinyrpc.transports.http import HttpPostClientTransport @@ -118,7 +117,16 @@ class JSONRPCClient(object): def __init__(self, host='127.0.0.1', port=4000, print_communication=True, privkey=None, sender=None, use_ssl=False, transport=None): - "specify privkey for local signing" + """ + Args: + host (str): host address to connect to. + port (int): port number to connect to. + print_communication (bool): True to print the rpc communication. + privkey: specify privkey for local signing + sender (address): the sender address, computed from privkey if provided. + use_ssl (bool): Use https instead of http. + transport: Tiny rpc transport instance. + """ if transport is None: self.transport = HttpPostClientTransport('{}://{}:{}'.format( 'https' if use_ssl else 'http', host, port), headers={'content-type': 'application/json'}) @@ -196,10 +204,11 @@ def new_contract_proxy(self, contract_interface, address): address, self.eth_call, self.send_transaction, + self.eth_estimateGas, ) def deploy_solidity_contract(self, sender, contract_name, all_contracts, # pylint: disable=too-many-locals - libraries, constructor_parameters, timeout=None, gasprice=denoms.wei): + libraries, constructor_parameters, timeout=None, gasprice=default_gasprice): if contract_name not in all_contracts: raise ValueError('Unkonwn contract {}'.format(contract_name)) @@ -236,22 +245,23 @@ def deploy_solidity_contract(self, sender, contract_name, all_contracts, # pyli dependency_contract['bin_hex'] = hex_bytecode dependency_contract['bin'] = bytecode - transaction_hash = self.send_transaction( + transaction_hash_hex = self.send_transaction( sender, to='', data=bytecode, gasprice=gasprice, ) + transaction_hash = transaction_hash_hex.decode('hex') - self.poll(transaction_hash.decode('hex'), timeout=timeout) - receipt = self.call('eth_getTransactionReceipt', '0x' + transaction_hash) + self.poll(transaction_hash, timeout=timeout) + receipt = self.eth_getTransactionReceipt(transaction_hash) contract_address = receipt['contractAddress'] contract_address = contract_address[2:] # remove the hexadecimal prefix 0x from the address libraries[deploy_contract] = contract_address - deployed_code = self.call('eth_getCode', contract_address, 'latest') + deployed_code = self.eth_getCode(contract_address.decode('hex')) if deployed_code == '0x': raise RuntimeError("Contract address has no code, check gas usage.") @@ -269,30 +279,28 @@ def deploy_solidity_contract(self, sender, contract_name, all_contracts, # pyli else: bytecode = contract['bin'] - transaction_hash = self.send_transaction( + transaction_hash_hex = self.send_transaction( sender, to='', data=bytecode, gasprice=gasprice, ) + transaction_hash = transaction_hash_hex.decode('hex') - self.poll(transaction_hash.decode('hex'), timeout=timeout) - receipt = self.call('eth_getTransactionReceipt', '0x' + transaction_hash) + self.poll(transaction_hash, timeout=timeout) + receipt = self.eth_getTransactionReceipt(transaction_hash) contract_address = receipt['contractAddress'] - deployed_code = self.call('eth_getCode', contract_address, 'latest') + deployed_code = self.eth_getCode(contract_address[2:].decode('hex')) if deployed_code == '0x': raise RuntimeError("Deployment of {} failed. Contract address has no code, check gas usage.".format( contract_name )) - return ContractProxy( - sender, + return self.new_contract_proxy( contract_interface, contract_address, - self.eth_call, - self.send_transaction, ) def find_block(self, condition): @@ -410,6 +418,8 @@ def send_transaction(self, sender, to, value=0, data='', startgas=0, tx.sign(self.privkey) tx_dict = tx.to_dict() + # Transaction.to_dict() encodes 'data', so we need to decode it here. + tx_dict['data'] = data_decoder(tx_dict['data']) # rename the fields to match the eth_sendTransaction signature tx_dict.pop('hash') @@ -481,6 +491,32 @@ def eth_sendTransaction(self, nonce=None, sender='', to='', value=0, data='', return data_decoder(res) + def _format_call(self, sender='', to='', value=0, data='', + startgas=default_startgas, gasprice=default_gasprice): + """ Helper to format the transaction data. """ + + json_data = dict() + + if sender is not None: + json_data['from'] = address_encoder(sender) + + if to is not None: + json_data['to'] = data_encoder(to) + + if value is not None: + json_data['value'] = quantity_encoder(value) + + if gasprice is not None: + json_data['gasPrice'] = quantity_encoder(gasprice) + + if startgas is not None: + json_data['gas'] = quantity_encoder(startgas) + + if data is not None: + json_data['data'] = data_encoder(data) + + return json_data + def eth_call(self, sender='', to='', value=0, data='', startgas=default_startgas, gasprice=default_gasprice, block_number='latest'): @@ -501,29 +537,117 @@ def eth_call(self, sender='', to='', value=0, data='', call. """ - json_data = dict() + json_data = self._format_call( + sender, + to, + value, + data, + startgas, + gasprice, + ) + res = self.call('eth_call', json_data, block_number) - if sender is not None: - json_data['from'] = address_encoder(sender) + return data_decoder(res) - if to is not None: - json_data['to'] = data_encoder(to) + def eth_estimateGas(self, sender='', to='', value=0, data='', + startgas=default_startgas, gasprice=default_gasprice): + """ Makes a call or transaction, which won't be added to the blockchain + and returns the used gas, which can be used for estimating the used + gas. - if value is not None: - json_data['value'] = quantity_encoder(value) + Args: + from: The address the transaction is send from. + to: The address the transaction is directed to. + gas (int): Gas provided for the transaction execution. eth_call + consumes zero gas, but this parameter may be needed by some + executions. + gasPrice (int): gasPrice used for each paid gas. + value (int): Integer of the value send with this transaction. + data (bin): Hash of the method signature and encoded parameters. + For details see Ethereum Contract ABI. + block_number: Determines the state of ethereum used in the + call. + """ - if gasprice is not None: - json_data['gasPrice'] = quantity_encoder(gasprice) + json_data = self._format_call( + sender, + to, + value, + data, + startgas, + gasprice, + ) + res = self.call('eth_estimateGas', json_data) - if startgas is not None: - json_data['gas'] = quantity_encoder(startgas) + return quantity_decoder(res) - if data is not None: - json_data['data'] = data_encoder(data) + def eth_getTransactionReceipt(self, transaction_hash): + """ Returns the receipt of a transaction by transaction hash. - res = self.call('eth_call', json_data, block_number) + Args: + transaction_hash: Hash of a transaction. - return data_decoder(res) + Returns: + A dict representing the transaction receipt object, or null when no + receipt was found. + """ + if transaction_hash.startswith('0x'): + warnings.warn( + 'transaction_hash seems to be already encoded, this will' + ' result in unexpected behavior' + ) + + if len(transaction_hash) != 32: + raise ValueError( + 'transaction_hash length must be 32 (it might be hex encode)' + ) + + transaction_hash = data_encoder(transaction_hash) + return self.call('eth_getTransactionReceipt', transaction_hash) + + def eth_getCode(self, address, block='latest'): + """ Returns code at a given address. + + Args: + address: An address. + block_number: Integer block number, or the string "latest", + "earliest" or "pending". + """ + if address.startswith('0x'): + warnings.warn( + 'address seems to be already encoded, this will result ' + 'in unexpected behavior' + ) + + if len(address) != 20: + raise ValueError( + 'address length must be 20 (it might be hex encode)' + ) + + return self.call( + 'eth_getCode', + address_encoder(address), + block, + ) + + def eth_getTransactionByHash(self, transaction_hash): + """ Returns the information about a transaction requested by + transaction hash. + """ + + if transaction_hash.startswith('0x'): + warnings.warn( + 'transaction_hash seems to be already encoded, this will' + ' result in unexpected behavior' + ) + + if len(transaction_hash) != 32: + raise ValueError( + 'transaction_hash length must be 32 (it might be hex encode)' + ) + + transaction_hash = data_encoder(transaction_hash) + return self.call('eth_getTransactionByHash', transaction_hash) def poll(self, transaction_hash, confirmations=None, timeout=None): """ Wait until the `transaction_hash` is applied or rejected. @@ -538,41 +662,66 @@ def poll(self, transaction_hash, confirmations=None, timeout=None): """ if transaction_hash.startswith('0x'): warnings.warn( - 'transaction_hash seems to be already encoded, this will result ' - 'in unexpected behavior' + 'transaction_hash seems to be already encoded, this will' + ' result in unexpected behavior' ) if len(transaction_hash) != 32: - raise ValueError('transaction_hash length must be 32 (it might be hex encode)') + raise ValueError( + 'transaction_hash length must be 32 (it might be hex encode)' + ) + + transaction_hash = data_encoder(transaction_hash) deadline = None if timeout: - deadline = time.time() + timeout + deadline = gevent.Timeout(timeout) + deadline.start() - transaction_hash = data_encoder(transaction_hash) + try: + # used to check if the transaction was removed, this could happen + # if gas price is to low: + # + # > Transaction (acbca3d6) below gas price (tx=1 Wei ask=18 + # > Shannon). All sequential txs from this address(7d0eae79) + # > will be ignored + # + last_result = None + + while True: + # Could return None for a short period of time, until the + # transaction is added to the pool + transaction = self.call('eth_getTransactionByHash', transaction_hash) + + # if the transaction was added to the pool and then removed + if transaction is None and last_result is not None: + raise Exception('invalid transaction, check gas price') + + # the transaction was added to the pool and mined + if transaction and transaction['blockNumber'] is not None: + break - transaction = self.call('eth_getTransactionByHash', transaction_hash) - while transaction is None or transaction["blockNumber"] is None: - if deadline and time.time() > deadline: - raise Exception('timeout when polling for transaction') + last_result = transaction - gevent.sleep(.5) - transaction = self.call('eth_getTransactionByHash', transaction_hash) + gevent.sleep(.5) - if confirmations is None: - return + if confirmations: + # this will wait for both APPLIED and REVERTED transactions + transaction_block = quantity_decoder(transaction['blockNumber']) + confirmation_block = transaction_block + confirmations - # this will wait for both APPLIED and REVERTED transactions - transaction_block = quantity_decoder(transaction['blockNumber']) - confirmation_block = transaction_block + confirmations + block_number = self.blocknumber() - block_number = self.blocknumber() - while confirmation_block > block_number: - if deadline and time.time() > deadline: - raise Exception('timeout when waiting for confirmation') + while block_number < confirmation_block: + gevent.sleep(.5) + block_number = self.blocknumber() - gevent.sleep(.5) - block_number = self.blocknumber() + except gevent.Timeout: + raise Exception('timeout when polling for transaction') + + finally: + if deadline: + deadline.cancel() class MethodProxy(object): @@ -580,13 +729,14 @@ class MethodProxy(object): valid_kargs = set(('gasprice', 'startgas', 'value')) def __init__(self, sender, contract_address, function_name, translator, - call_function, transaction_function): + call_function, transaction_function, estimate_function=None): self.sender = sender self.contract_address = contract_address self.function_name = function_name self.translator = translator self.call_function = call_function self.transaction_function = transaction_function + self.estimate_function = estimate_function def transact(self, *args, **kargs): assert set(kargs.keys()).issubset(self.valid_kargs) @@ -619,6 +769,23 @@ def call(self, *args, **kargs): res = res[0] if len(res) == 1 else res return res + def estimate_gas(self, *args, **kargs): + if not self.estimate_function: + raise RuntimeError('estimate_function wasnt supplied.') + + assert set(kargs.keys()).issubset(self.valid_kargs) + data = self.translator.encode(self.function_name, args) + + res = self.estimate_function( + sender=self.sender, + to=self.contract_address, + value=kargs.pop('value', 0), + data=data, + **kargs + ) + + return res + def __call__(self, *args, **kargs): if self.translator.function_data[self.function_name]['is_constant']: return self.call(*args, **kargs) @@ -634,7 +801,7 @@ class ContractProxy(object): translation. """ - def __init__(self, sender, abi, address, call_func, transact_func): + def __init__(self, sender, abi, address, call_func, transact_func, estimate_function=None): sender = normalize_address(sender) self.abi = abi @@ -649,6 +816,7 @@ def __init__(self, sender, abi, address, call_func, transact_func): self.translator, call_func, transact_func, + estimate_function, ) type_argument = self.translator.function_data[function_name]['signature'] diff --git a/pyethapp/synchronizer.py b/pyethapp/synchronizer.py index d9d5e925..f29c38ae 100644 --- a/pyethapp/synchronizer.py +++ b/pyethapp/synchronizer.py @@ -3,7 +3,7 @@ import gevent import time from eth_protocol import TransientBlockBody, TransientBlock -from ethereum.blocks import BlockHeader +from ethereum.block import BlockHeader from ethereum.slogging import get_logger import ethereum.utils as utils import traceback @@ -42,31 +42,32 @@ class SyncTask(object): initial_blockheaders_per_request = 32 max_blockheaders_per_request = 192 max_skeleton_size = 128 - #max_blocks_per_request = 128 max_blocks_per_request = 192 - max_retries = 5 - - retry_delay = 1. - blocks_request_timeout = 8. blockheaders_request_timeout = 15. + max_blocks_per_request = 128 + max_retries = 3 + retry_delay = 2. + blocks_request_timeout = 16. + block_buffer_size = 4096 def __init__(self, synchronizer, proto, blockhash, chain_difficulty=0, originator_only=False): self.synchronizer = synchronizer self.chain = synchronizer.chain self.chainservice = synchronizer.chainservice + self.last_proto = None self.originating_proto = proto self.skeleton_peer = None self.originator_only = originator_only self.blockhash = blockhash self.chain_difficulty = chain_difficulty self.requests = dict() # proto: Event - self.header_request = None self.header_processed = 0 self.batch_requests = [] #batch header request self.batch_result= [None]*self.max_skeleton_size*self.max_blockheaders_per_request self.headertask_queue = Q.PriorityQueue() self.pending_headerRequests = dict() + self.header_requests = dict() # proto: Event self.start_block_number = self.chain.head.number self.end_block_number = self.start_block_number + 1 # minimum synctask self.max_block_revert = 3600*24 / self.chainservice.config['eth']['block']['DIFF_ADJUSTMENT_CUTOFF'] @@ -95,8 +96,13 @@ def exit(self, success=False): @property def protocols(self): if self.originator_only: - return [self.originating_proto] - return self.synchronizer.protocols + protos = [] if self.originating_proto.is_stopped else [self.originating_proto] + else: + protos = self.synchronizer.protocols + if self.last_proto and not self.last_proto.is_stopped: + protos.remove(self.last_proto) + protos.insert(0, self.last_proto) + return protos def fetch_hashchain(self): # local_blockhash= @@ -316,7 +322,7 @@ def verify_headers(self,proto,headers): # if request: # return 0, errNoFetchesPending - if len(headers) != self.max_blocks_per_request: + if len(headers) != self.max_blockheaders_per_request: log_st.debug('headers batch count', count=len(headers)) return False @@ -356,7 +362,7 @@ class SyncBody(object): max_blocks_per_request = 128 max_blocks_process= 2048 - blocks_request_timeout = 8. + blocks_request_timeout = 15. max_retries = 5 retry_delay = 3. def __init__(self, synchronizer, blockhash, chain_difficulty=0, originator_only=False): @@ -502,7 +508,8 @@ def fetch_blocks(self): if not fetching and not task_empty and not throttled: log_body_st.warn('no protocols available') return self.exit(success=False) - if task_empty or throttled and pending==len(self.pending_bodyRequests): + #if task_empty or throttled and pending==len(self.pending_bodyRequests): + if throttled and pending==len(self.pending_bodyRequests): continue try: proto_received = deferred.get(timeout=self.blocks_request_timeout)['proto'] @@ -531,6 +538,7 @@ def fetch_blocks(self): headernum=self.pending_bodyRequests[proto_received].start, bodyrequest=self.pending_bodyRequests[proto_received].headers) del self.pending_bodyRequests[proto_received] + if not bodies: log_st.warn('empty getblockbodies reply, trying next proto') continue @@ -577,6 +585,11 @@ def fetch_blocks(self): self.exit(success = True) #return True + if self.chain_difficulty >= self.chain.get_score(self.chain.head): + self.chainservice.broadcast_newblock(last_block, self.chain_difficulty, origin=proto) + + self.exit(success=True) + def receive_blockbodies(self, proto, bodies): log.debug('block bodies received', proto=proto, num=len(bodies)) if proto not in self.requests: @@ -607,7 +620,7 @@ class Synchronizer(object): which has a fixed size queue, the synchronization blocks if the queue is full on_status: - if peer.head.chain_difficulty > chain.head.chain_difficulty + if peer.head.chain_difficulty > chain.get_score(head) fetch peer.head and handle as newblock on_newblock: if block.parent: @@ -669,8 +682,8 @@ def receive_newblock(self, proto, t_block, chain_difficulty): log.debug('newblock', proto=proto, block=t_block, chain_difficulty=chain_difficulty, client=proto.peer.remote_client_version) - if t_block.header.hash in self.chain: - assert chain_difficulty == self.chain.get(t_block.header.hash).chain_difficulty() + if self.chain.has_blockhash(t_block.header.hash): + assert chain_difficulty == self.chain.get_score(self.chain.get_block(t_block.header.hash)) # memorize proto with difficulty self._protocols[proto] = chain_difficulty @@ -679,13 +692,13 @@ def receive_newblock(self, proto, t_block, chain_difficulty): log.debug('known block') return - # check pow - if not t_block.header.check_pow(): - log.warn('check pow failed, should ban!') + # check header + if not self.chainservice.check_header(t_block.header): + log.warn('header check failed, should ban!') return - expected_difficulty = self.chain.head.chain_difficulty() + t_block.header.difficulty - if chain_difficulty >= self.chain.head.chain_difficulty(): + expected_difficulty = self.chain.get_score(self.chain.head) + t_block.header.difficulty + if chain_difficulty >= self.chain.get_score(self.chain.head): # broadcast duplicates filtering is done in eth_service log.debug('sufficient difficulty, broadcasting', client=proto.peer.remote_client_version) @@ -714,7 +727,10 @@ def receive_newblock(self, proto, t_block, chain_difficulty): if not self.syncbody: self.syncbody = SyncBody(self, chain_difficulty) else: - log.debug('already syncing, won\'t start new sync task') + log.debug('received newblock but already syncing, won\'t start new sync task', + proto=proto, + block=t_block, + chain_difficulty=chain_difficulty) def receive_status(self, proto, blockhash, chain_difficulty): "called if a new peer is connected" @@ -732,12 +748,13 @@ def receive_status(self, proto, blockhash, chain_difficulty): log.debug('starting forced syctask', blockhash=blockhash.encode('hex')) self.synctask = SyncTask(self, proto, blockhash, chain_difficulty) - elif chain_difficulty > self.chain.head.chain_difficulty(): + elif chain_difficulty > self.chain.get_score(self.chain.head): log.debug('sufficient difficulty') self.synctask = SyncTask(self, proto, blockhash, chain_difficulty) if not self.syncbody: self.syncbody = SyncBody(self, chain_difficulty) + def receive_newblockhashes(self, proto, newblockhashes): """ no way to check if this really an interesting block at this point. @@ -752,7 +769,7 @@ def receive_newblockhashes(self, proto, newblockhashes): return if len(newblockhashes) != 1: log.warn('supporting only one newblockhash', num=len(newblockhashes)) - if not self.chainservice.is_syncing: + if not self.synctask: blockhash = newblockhashes[0] log.debug('starting synctask for newblockhashes', blockhash=blockhash.encode('hex')) self.synctask = SyncTask(self, proto, blockhash, 0, originator_only=True) diff --git a/pyethapp/tests/blocks256.hex.rlp b/pyethapp/tests/blocks256.hex.rlp index ef02b2bd..4eb18171 100644 --- a/pyethapp/tests/blocks256.hex.rlp +++ b/pyethapp/tests/blocks256.hex.rlp @@ -1 +1 @@ -f97fc0f901fcf901f7a0fd4af92a79c7fc2fd8bf0d342f2e832e1d4f485c85b9152d2039e03bc604fdcaa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0ad5dcee4e0ceb7b1b88f9462521cec84b41d31e375a99df4bf8922826d0076bba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8808455078f8c80a0551fecbfa391fcaa35eab2dd6f508ab99e6dbef7834fe1edc443386e075140f3885b83bb72c83ff668c0c0f901fcf901f7a0fc34558a7eb62591b8f0cfc56fd1429340ba3f5f7a647fa68fd0b7ab87dc8046a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0bc08a420514e97f6ebb570dc6442992fed7a8ee2a47763ee4df7ba41bdb60a8ca056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302004002832fefd8808455078f8d80a0e8caa97b24c81cc036c024d24636c48d2e97cf6f9a70bb698b9d62983cce85ef88660a562da4f772dcc0c0f901fcf901f7a0ed8f324f7473b5ef45b00add6414523c5236a5b2aad73b6d12c2fc41ce898e01a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea00943dc33144ae926494d156f19899e22445236df467e3ffb8f47e25cbd4ad806a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302008003832fefd8808455078f8f80a08459903b0455880e94d87f563f12c63ad2997f0def4ac00c1a67a7986223821d8850bb87023d2eee03c0c0f901fcf901f7a0956c52d90649d83f5e34cc16698d2800666519aa45736bdfab0dfba4812b77eda01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0134b4155a46d686eefc053da6a6a437576de91fa82ea7a44b394c52f84cb51dea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830200c004832fefd8808455078f9280a0825968528586aff915ba32ce3d758d4ce919557e8b1061fd1aba58f2425a6904881525dc60cfa62847c0c0f901fcf901f7a0a7fff3979ac51e84f906f5db54677820b1b99e89069d9d738f5de9bd4c1c1d0ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea07d314df54e76fd5c0a62cf49a2747659b33094ce5fe1c6a23799b795436d8a3da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302010005832fefd8808455078f9480a0c6d8eb152cf04ac30dc7ea9f2fbb9c60da0d1c99c67ed21120854b9bbca573a18838632bca74debee2c0c0f901fcf901f7a0c34ebf7c1ef971fbf8a69353cd95163bbe4e7da4cdc84ea8b4ea9dac52aa5215a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0ac632b32a6d7b5aaff583ef7cd6b684de9b27f833a39a583b81845be22a8b437a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302014006832fefd8808455078f9580a087bd3e292bdc7be0da54c4dfb799282d52607a95dbf77bdd88209dfdcab6fa74885bc3067200c9969ac0c0f901fcf901f7a0335cb07a70030f7a01adf81b6f4c4f25a46ec2aa7b7534527e232646778368d7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea05e9ce43a12cd2b559e9b3f1560b0beab1af4e9b2b22027eb13e45d1afee47648a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302018007832fefd8808455078f9780a078e86286734268c12dc1d48b94a9ec8a5163116f58a6a4288476291a0a011c1d8804d3e32bfe6a650ec0c0f901fcf901f7a0ca89f747e7f2e6ad83c5a03d061f2a37a496bfd2f27a85ed6a66103f77cf4d1da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea016cb625dc2d85fe8aa2fe50d3ab8a97ad04c1f583b6b4479150f3dac109e44f9a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830201c008832fefd8808455078f9d80a0c12a4d04f6d7300471de17470d63a61b01c558451ee24f51d856d5b7bd6d93fd881399d91fca852822c0c0f901fcf901f7a066564c19a879202d9d0a9dae5e88d74e78d7ee4ae94b0ee2bbfef3e3a30dc288a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea01af279b7b4d5002e88b6c2caf70017bfbdf5b16d5c5888e6c265bcdbd3556935a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302020009832fefd8808455078f9e80a0a73b66fb3eac0543f631d309aa314cde5bb839e93ef44d8abe45b411d378a746880df76bf0fe10aa88c0c0f901fcf901f7a06f2bd00740973ebd96f71ab87d4f20a4d4adb000e36d86e343aad092d7359160a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0e2a70d425a68c1ec3646dbb8a50b3d66fa7300aa78524fb65003ce89452a60cca056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830202400a832fefd8808455078fa280a0cbc4698c185fa19c6e10960d2095ad4a10e94a090cc0f69d7353632fdc3e27088817095cf61cb33a59c0c0f901fcf901f7a0d68aa8c06e92dc2f47b690b982293716bf1482fc3609552ee544c30c144b05f3a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0ff076322e518c725ae74bfd9758e5a88fd1c49a2676c9b176a99ea970093b8e2a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830202800b832fefd8808455078fa780a016ad6a63216a85b3bfcb59bc8e3b48186b806c4e4173812fd6e4d80069a32742881fb18e9c4c5cb92ac0c0f901fcf901f7a0f709410020e29056962f09c01a2855f4be55ff99f0ecbc7e19178961e7509a44a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0e7d3b71a82c8946c2b231d3825f9fba6f9d78e9e6e46d88ca4891df9658ad720a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830202c00c832fefd8808455078fa980a05b9b342763d6866141299eb59d38715fa1ef07a75d5a443c347126b55e89aecb8845e1d2b3a1f2a7e0c0c0f901fcf901f7a089d9c0689f10bd5269a7239b4f14f35a07caf04a970d3c180dffe96a24c920cea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0cb82498ca4dcc849a7b5dae2007680cb9ae28776789eb7a3d7269100d1d345c6a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830203000d832fefd8808455078faa80a0904d3fe79058d1bc5cccdcadd15466df4c22bb364b14dbc753df2e665ab9d13a88299b7466d42eb786c0c0f901fcf901f7a02ba076f9a0dc9981722eea0fafff1d7c169ab4ff4935883d9269db4f1401c695a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea03258428f97c5b35b3b06fb06898b83cc5d50f18afc2ed4f8b16a9edc71f82a94a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830202c00e832fefd8808455078fb280a07f00a9c910ed044de81b81c9cd794ac3703a7e0685cbf51afc57208392042e07882867be7d2c07dafec0c0f901fcf901f7a07801ae3862ad6497a8776712595a0e71d7f58740c5993e1d3c154594a1ee731ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0881b5a457cd263d26dd5cb28e878bbe2d8db5647c3680b1ce12b2e0ce6a4a9e5a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830202800f832fefd8808455078fbe80a01b0aa1bc72d2ed5fe767edee533fa637a23105877bdb2f0118c97a0eed27c7088841575b69073c9647c0c0f901fcf901f7a08d72546136df21a0c0ade804b793b0363314d1dc5dbaaf54dc511bb97c3309a7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea05dd1273edae1a4c1189f68cc95361e4405f366926e8fb8b697d10222e68f877ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830202c010832fefd8808455078fbf80a07c8394fede358b90573b84a370d0a64362330e45343f04b3800073e81755a14b884aa3a51da23a9b0ec0c0f901fcf901f7a07b3709e9bf6abf76b63654bec67149afce801380659aef08ed01ec2746c677bea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0bb4d16c945a20d06b3882463c10508fb33365f3bc548737bc95e938b24c06c06a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302030011832fefd8808455078fc280a0600ae46199f018822f8405bbfb49d8a1bee9201e829db2135a7bb68e0dcb4a658877b4d0826d1d8843c0c0f901fcf901f7a0c7f3e009c5b1cde06379f975e529e5b3ad5cc07e9abe804b5cfbe6364eb6c758a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea06feb7fa6a0256361ce0f1f5c76689c1c3789396a3a71b26ac7ae26996139770ba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302034012832fefd8808455078fc480a08ce0651288a73f2d732393cfc0e396e5b865f51effc8ac2a0b03ebd6ed7ddacf881903f6241b229080c0c0f901fcf901f7a01e7da10719ccdb20e4ffa1b4d6408f02c2024d8b84d8d8feaf1790f85a58b3a2a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0643bd80f0187673bdfcbd669ffdd9f8f57d0dc5b013c7fd052c96ca39945d363a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302038013832fefd8808455078fc780a05374340c36b46ee2a8cffe3555db1e05b73d886ae018848d0930dedad9794cd88854fe2abe7692a12ac0c0f901fcf901f7a04d6c250c18d6a5794e3cf2be26850daca61934f67b4006a7bc4bc4bf9f49d677a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0018a02c4d40d33445e715de99b64685c64b61c37fd6055ef2b07f091b9337652a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830203c014832fefd8808455078fc980a00a6c1f550bc56b700fe5949199937c5a5ecb7cae40c19dd128a9da2bc312865c886ef8ef56649cb6ecc0c0f901fcf901f7a0af099cc423dbc60a266e1d68723d2b451667ac21f6f678e5b16d930721675925a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea031272320b9cabe267aa56c88c5d047f736711a916475fbab48259819fd8952d8a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302040015832fefd8808455078fcc80a0e5074a64927e0263e8c68c8323ccd9aa1b08e0993e6863f29d130e1c08d65170882fb33e73b3e7562fc0c0f901fcf901f7a048433307000c187e9eecfcd89aee6e538b80630f801d7410af2298ef3e25a5c9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea027ef1e4a7ac39127cdaf1316aa780bb8e3ced78945502e5976a0dc168e3ba8c8a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302044016832fefd8808455078fcf80a0ea8473e68c60bdda8db3264e78cd6edbf35176e74e8647e258624ae5590d4583885ac6bd86a122135ec0c0f901fcf901f7a03631ca325c90c173fb3ac9c4ed85f4d43d399c08e6649968898978365b3e3d3fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea09c305bed362ec8cdb70a1b1145cd40ae2bde66fa7df433c322e22776788681e9a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302048017832fefd8808455078fd280a0262fc08e1c553727bb8740e4d1a6f61295b3f13e59455a7636378df9ea49db0988711373faa11301a2c0c0f901fcf901f7a0d31fd4529e2e51b53237e2d577e91bb8f1b6ea5c847665e5d9ab9b8eeeacdf67a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea00b027b12839880b5f2b0163628c69ce48f44498eb9b73bb7262d28b7a022f5caa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830204c018832fefd8808455078fd380a060139fb9f392d3786ecf84fc48b86689378ec609a8063840b907f0f21a599350882f681216de1354d9c0c0f901fcf901f7a0a68c4d4dcd26cacd006813bcb1835f82ad933b6909e0fa4ecdff14fa824bb7d8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0ccc5198b65bf87b12808f4617b729ef4a2943ccbe9572497604d7542f1865473a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302050019832fefd8808455078fd780a01893062e27efcc9f9b30c50910d83ed91579d1552aa63166c6772cd0e17f7bfe884ee14e83b3b44da4c0c0f901fcf901f7a004ad9379ce3718d4dbbe5fb3eb001d7ee2dc56be55237e8e9a06c344d529ed61a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0cf6b624e5b34441edda94b0e60f83fe3604c410a0e3acf86a4e6f22e71519eeea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830204c01a832fefd880845507902880a0c3c4f859ddc320b942d3b6f1ba721ad559ec778591b4849ca70962aa2d88084d881dce4980ba17111ac0c0f901fcf901f7a0362e4d0892f5a3bf6b352ee01eee2b9d0ba6f67e48d3543fa900efdc4f687e4ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea05d2f6cedddccd770069c78b55899e9ecce2b33534b5982a87ab6bc0cfe759e2ba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830205001b832fefd880845507902a80a079432511548c4c79c924b794cf542fd9a1d9feed8ab8982f8acdb3018cdfc081881a6b272357b74c39c0c0f901fcf901f7a0ca2d9e6ffb9a9688e4a3a3498f07ab43b0ff20a9b3ce4739a91de1c033b19130a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0015d7ff2816c5d7ec051dba33fe35f6b217282eba8c49b7a53239723e8766e2ba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830205401c832fefd880845507902b80a075186e01b727c1df61d0b267269cf68ddfb68b50254300b361b66fea3a92deb58851a4d397f2703fa0c0c0f901fcf901f7a0aeb39365a8c78d1fb494d558f7a5c9867a4a86ab799dded720654e9ef5316969a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea08e04f11bb1ce75a5ece3e5fd361a88994fbed59807e271566f99088f94888a67a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830205801d832fefd880845507902e80a0b2ac0ed00bc5c293121f7f65820006dc35a0c48fae0da9eb4fa1aa7c3521e298885c1df2981ade5054c0c0f901fcf901f7a058efcae5d7a161f96ec968af7456d46333fc651e4d7f5470b9384d6a4cef12ada01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea06562908763d77dca1d050a581cec730642f8870bc8c9c3a49c09832fca2766baa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830205c01e832fefd880845507903180a02d72111e70319f51e4ac2eee455a656d290b07a1d1d43dc60797c7d76be902c688672d13d1cec32917c0c0f901fcf901f7a0dacf2d33d1f037d1e1fcb35d84950985c307f5aac51aaad9e2dc847b33e9879fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea036fa5f7c2082b02ff970bae13d90eaf4d1d9ca82be2d5e794ee4e7ac59ef4a4da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830206001f832fefd880845507903780a0691fa8307186b1b6a4f82fded91e04e4281b1377bc1fdae0faf54352cef54fc7883d2dcefc473eb734c0c0f901fcf901f7a0ce484d5035a7e6cbff78ebd8c8340d7bef5339b44f04dcef27c6b7ad405bbc05a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0f08bb3a6620d51e22b052791dffc878938ae06d4ab8eea77f0281e2dce539682a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830205c020832fefd880845507904580a03e460f6c9c0f26108507ccfee87f71377dee2a76d1aeb4e50247a7035f6ac8248859a3115545145c97c0c0f901fcf901f7a0911d05b2b9bbf4cc8efe48f5d0f34181b1aba44b50b8feb2ad52e9e496335e3fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0c861459243929f356c5ffc8033a7ec162617a9a042aa4b0c4c19235b9bb9d7eaa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302060021832fefd880845507904b80a0a9e22518ffce6292a2b799a897c6a00877fc5dddd6813ba710feef39e2c2b4918869d8e78eb5d48dd0c0c0f901fcf901f7a05a8bf98d13d8c2bcba52b0f3d96ffdcbb2ad5fb604407d72aac987fdf3fbe130a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea05371d13d736820a9c2914f6e346cc48f411a7396e6eb36f64d62e95e59daf245a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302064022832fefd880845507904e80a0eda8d1f48e5f9b86e21b68a58bac48f9625f6299fb7f71c330ae9ea413de06a6887a522393154a5048c0c0f901fcf901f7a0bb7034741b63f245fe98ec15576e883e8e4fa4e15a78ee63179ba5ca56a00243a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea083e49ad29822bb6aebd7710e4f5be64bb34a30349c663d5fb51da7f758b06500a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302068023832fefd880845507904f80a08139068727b9d76281b72c70f2cee30655d7e756b341ea1ee6866872a6028efd881096f6ddb05d3e60c0c0f901fcf901f7a0716d42eab22a366015ab19dad5c23b759fd9b3840aadab6b0a6a24eee6c9b46fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0ff44c9b3d61b02a3cceb5cf39cc895c3b196154f9534a8a3734f69f9dd310288a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830206c024832fefd880845507905080a05faac7ac6c26058e0b144782e5d0e9d1b391294eaeeabd038620f7d849aab5d0886a7480d9430c9d5bc0c0f901fcf901f7a0130a387889c5a2c63a9b58a0ab0cd1396e5d16ab8848bd5ff7a8cfd719fd64e5a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0acfc44fb4f0bbe3b2b6087674087dfce602697dc0f6f429f7d7a7188a6df7689a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302070025832fefd880845507905180a08bce2ef1d6707b9606eb8e7fb92b778552da9eea8d8ce0d26a003182ed1042078818bd5f70ae5819afc0c0f901fcf901f7a086cfb345dc3ff5e475f4924a38123c147f41b3a8f638cf3144514f0ff6e8f104a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0d6f303f9116c5f57a310b04f06daefb04fa274114be419d1a6a0253763952a90a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830206c026832fefd880845507905b80a085b5af8d26f8434c6be497431f4fadf410c40a2cef410f5de66d9984142b4b3d887e296e56f12fbeaec0c0f901fcf901f7a00c54ea0a6dc6d8347c2275262f8b59bed0ac4306834e6fc2054a98a511d87d68a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0368b07c7139fbcc2ff2611390c53996bc144cf562d848c433cf6e9e8129d8ae1a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302070027832fefd880845507906180a081b433d1cbdf93f0bdf7eba668ed4cd1a418f3945077f975ddfe5b7690185522884a1b97d268210133c0c0f901fcf901f7a0bc6742e92a772fed0a9e3d867f58e3501a407676419fcc551e9ceafd774b7feda01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea06b472c2b0df9ac132e721bf0b045aad20489db9af9b7a5ec46871b665d98fad0a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302074028832fefd880845507906380a02afdb6eb8e30959888ea2d19440141382af5b6cedba386c7b942b6394e1c5e0a88330f7c8d775ff0ebc0c0f901fcf901f7a00dd6ef9fe90a342f645f8026e8c897543aace0e678f887ddddb4028979fa11b5a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea042023c3eecf0365f7fa97ec338ef3481b9efff9af734fe66bf58a41ddc5ecc8da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302078029832fefd880845507906780a0d849a5fc1352aa1081dd333a50efee82648db15f7ea49b13679f118fea09ec9388761ab36a729f56f1c0c0f901fcf901f7a0a6389d7a790be98217ae4d381b51804e1446565282ca9c429872e0bdd9857e0aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea058650430b6877814e0854d6f306614b2a670c3e47d77e868a905d3d1450c5514a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830207c02a832fefd880845507906980a0ba74fb9fe07a4c661b719e790593d45b852d03ded7b1545ac7e074047c311615887f36fa92a0e9cc83c0c0f901fcf901f7a0825b68ce33fa1ca94638df4f6a85475420d67fdb9c9ebe77b89287f18f82fd3aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea04b91a56333f68d3098d7850c6b736c8ec712cbab8830c3cff4b2482af6b2c081a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830207802b832fefd880845507907180a04f9d4690b46610212f002a4bbf92b8f62a88baba3cfecf2c9b4561b368bcd6ba883f3e235ce13ba896c0c0f901fcf901f7a06462dc68b0cb1bfed14b1a68a442dd7f33b3a96787f465fe421b2c62cfe32158a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0cc1e8e4202e61897f52935c910633957966779c1364d7b0956b97d4e1db291ada056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830207c02c832fefd880845507907280a0f95a8c3765a3f4a88055975d8e43587711331c47c38ed105b2a1c0c7790eb624883730befbd867ed37c0c0f901fcf901f7a017c5b3f9ee3b1f130463aeb41914651130ad550400e61183ecc8d5b9ae4d1a24a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0d7fe3ef599742572ef907f55f93df9037ca7ca330b777a7d33e6a66b62502105a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830208002d832fefd880845507907880a09253332f188019a21593f5baaf06ab7651b55e622c48e87cb2b98a44f94dd02988177ce4ae02ed46f9c0c0f901fcf901f7a0e70768e4425d8af221763e0129d9840dd1b81d21d613361e1d0ff82d020321b6a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea00fa3aecfa3f1ee9c49a80322bfdbbeb58ca1541b4c53b707b9920d381cdce005a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830208412e832fefd880845507907980a0c25837e8b5a7b8143e1c3b0626df9ef5a5b76891639c5a459a4af06e7998d2e98800ba744acf68aaaec0c0f901fcf901f7a09ad2ff787a448a42caeec3899f24a2b8e499f91878546b55401bd04ba2d8b200a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea04a457a7f6c8dc9b2458193eacbcce50e3e02a7d6886b7385ab7ab504c908d22aa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830208822f832fefd880845507907a80a095cc704ee6a7ea94c538b315294c8597b510ae147c0d095a6f9e60651c8452e5885a6a868c137c5536c0c0f901fcf901f7a052a4536cda0ca7eddc0d72cb8b841465e398af13b37f7e7da2a60ace3f7b92e2a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0a256e924896847003d8fa82f5d26120bdb53ef0c9319172851f5136136247266a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830208c330832fefd880845507907b80a096fed26abd9a33c36972140a2fa5181a1d74d448803ac787a057c5df328d85d58864736fdcc8e4fbd3c0c0f901fcf901f7a0e300bbff76c335a76ee8ece5d2e929e57eb980dc3bab4274fe3b461bff0bc71fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea01b07ab22e6bf1a097d32052558f6e9ca97cd3954c82b3422a64a0a0fea133867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302090431832fefd880845507907e80a0c9d3bd8bfefa7e1252755d08ed4ef5bfbf6d487523e2e3711614bef84f5a956d8830c7da48ce5fcd8bc0c0f901fcf901f7a0aa16aac485a246b4dfcf65f56035f6c3fa79510f34977bc09a165c3cda617a3ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0868a25e057cfd5eee3ecf45cbb53b3193645034168b619a379a66953a202f606a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302094532832fefd880845507907f80a0c74f81d658abbdf7dffcd6413b19472c3b091a7554846cb2ad7f0dadcfe6aa77883364abba49694468c0c0f901fcf901f7a0868e112d820e08108a0298dba78e1435169193e098ecd6662c6bb1521568a49ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea09b94af89c1f2954afb0dd0bdefe905df00eba83e665892f6c7817b653f775370a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302098633832fefd880845507908080a0b05baff7875915352bae45ead0f4936207132a405d04240eae7fbbb28c4338cc881bc214b0306ad86ac0c0f901fcf901f7a094c8e22292bda2f202acf5e0bddbb09209e5b7a4288bb1e01c062f60554081bba01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea07060173417f81fb10766f01ee233c21c526e642179a2d7fc8dff3cacb5fb150fa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830209c734832fefd880845507908380a040ba56fe3e16c4b5b6f0d36f0c1ab15c68ee4a101c091303fec40bcab095b9eb8844285ce3d91c73dec0c0f901fcf901f7a05b442e4d30f0904a2871a07639d9cf3e798ea22da4e3b41e7fd7483a20470590a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0dbeb30eb15bfe2384317e3aed0c0043c1164a794f627b61b12c70a5cf601621aa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020a0835832fefd880845507908580a06827162ef0c753b3de63d44ebdfcffe812ecb2231fad948b5fb6507e79ce00ee884f80ef66236d24d1c0c0f901fcf901f7a0a82a189f3aab3396ec9f9738407d105b8d1b75df6c90414e7c18b28c89be604ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea085cd99fc3f2cdf7d6ec204abb6e7512e02b011ed5013ab63d1349d81496eb590a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020a4936832fefd880845507908c80a05cf0e1c2828b84a4dad1156e5dc2c56249deb6f0c489c1be7ca03a3a623f83af8821866ac6af4086e6c0c0f901fcf901f7a05dc8ce6ddd7c01e4c87f24bb4ffb8e8a94f079afe59b43575d218dd01bf7b3d7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea04eb7b0f8c33f72ef43ee942b2285bd0e0919b0019780b250d4a970f327517c80a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020a8a37832fefd880845507909080a0b59196af15a5011c6e6a0d0c8161b2225962dfa0e23fde8f05a07c809d97c1a788218296332384a480c0c0f901fcf901f7a09c01b3b0fa16262d9af60c702b7722a8ec88c00aea2baedd9ff08307dba1a0a1a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0ae6cd97c4cf417bbce24320931c3b3a5804c3fe79bf52cef008f0368a798fa3fa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020acb38832fefd880845507909380a078186187f869b6da37172f26228834ad4ec2cb1f2857518dd8cd3da75ac674c28840be6fa323adb244c0c0f901fcf901f7a0b20b52657de7ab0bb218999f72e46f7546bc6adc63f8162bed9df0b1bff8fd7da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0beaebeda2b28309ed2a404e1a86eb65f62d6cd708f4b4bb8ef6f1a7e0f12f0bca056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020b0c39832fefd880845507909680a087bea1de5a9f0766bba67ba4e6874d2a1165285195733e1c8f034c9f97d526e68805d0b715d89a89eec0c0f901fcf901f7a09032840b141d27beaa1774a1557a1f83aee4310915ea3608bfcfcbc7ad130db2a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0e32e4bb770978f832701500fe1168c1cccdf50c64305d682ef1985b3861654caa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020b4d3a832fefd880845507909880a0bac28b0e84b08494f6a65f715512c92e722c5a4b9182e7175bdbaf9a1e2de4958853df300f65330178c0c0f901fcf901f7a0673ddc42f8b723d55f03cb30a635adce6f612127c104daa4ec3c3eb0237f5b4ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0f1fa605974e7566a9aeee17c2bafdd4589d040c0a4289bb13ed537b68d06629fa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020b8e3b832fefd880845507909980a0d4837da3d59dd679398c412b3bbdc9b4efec48599c580b6864f35a7d32ca52fc883269d59ac9c1c4abc0c0f901fcf901f7a0e4d677a4fc7ce560e4c2e09d9534f2e2c4a60e2d94f84d4aecb97fbc31fd749aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0a8e478836399e1622084c1319a8fbe1cceecc46017a7925f65ae8f860f5367b4a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020bcf3c832fefd880845507909c80a0e07c509c5dd6a69d554b8c15930141cbbdc8606336ceae6f9c122f4f0d2507f8885d69cfc1173abb31c0c0f901fcf901f7a0d2236d4f0094097ba4c1b7e61301c9408b277489f74d6ce82f33862b538becb4a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea06c29772c717600948b2db214f1505544546e497938ad8b188fd7be3251db438ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020c103d832fefd880845507909e80a0cb060cf141d422d6253a978924786c2e564a02b0b41b20783eeae272ba51864688504e5e7a7b53eb12c0c0f901fcf901f7a08b845d330bb5301d8353e319910decc9f6f069b4cceaa191e2a9eee8a2548cd5a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0c42371e4224f2b66a68c658e3c68760f38a1578f62b46ac6811f8cb0bcf34aa2a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020c513e832fefd88084550790a480a0d7062ff6e3eca3433adbca1338c46c06b636d07a68d19a3d8241f7f4f93656a08863243c8d28e578f2c0c0f901fcf901f7a0c8b0dcc5d63f3403a3a688bd498178fb98a7a2d5548f40d70b05ee37ac1c1b49a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea035fa930db87c6c3542c3652c976d20721afbad149ea6652e9b8d3661f75f2b6aa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020c923f832fefd88084550790a780a05f8454cd4e95a9474d4e2206c2a18facd86cf2a667b9be79a4ee0026a05dd353882150fab90c3f5c54c0c0f901fcf901f7a00bae39b9e308c9c1a2ada328e9527bb5aeea6d9c8b2531a2802181838ba9dc7fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea03a0fdb8dc51b4ccc06dc4b92f592c367297de7442c33e71a4c30bee1d65a8163a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020cd340832fefd88084550790ab80a0cddd2d2b6f6cf909c33cddcb5e6ec0c5b5cc61f759626a5f4a1499b0d2f523de88203a21d7532c50c7c0c0 \ No newline at end of file +f97e80f901f7a0fd4af92a79c7fc2fd8bf0d342f2e832e1d4f485c85b9152d2039e03bc604fdcaa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0ad5dcee4e0ceb7b1b88f9462521cec84b41d31e375a99df4bf8922826d0076bba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8808455078f8c80a0551fecbfa391fcaa35eab2dd6f508ab99e6dbef7834fe1edc443386e075140f3885b83bb72c83ff668f901f7a0fc34558a7eb62591b8f0cfc56fd1429340ba3f5f7a647fa68fd0b7ab87dc8046a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0bc08a420514e97f6ebb570dc6442992fed7a8ee2a47763ee4df7ba41bdb60a8ca056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302004002832fefd8808455078f8d80a0e8caa97b24c81cc036c024d24636c48d2e97cf6f9a70bb698b9d62983cce85ef88660a562da4f772dcf901f7a0ed8f324f7473b5ef45b00add6414523c5236a5b2aad73b6d12c2fc41ce898e01a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea00943dc33144ae926494d156f19899e22445236df467e3ffb8f47e25cbd4ad806a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302008003832fefd8808455078f8f80a08459903b0455880e94d87f563f12c63ad2997f0def4ac00c1a67a7986223821d8850bb87023d2eee03f901f7a0956c52d90649d83f5e34cc16698d2800666519aa45736bdfab0dfba4812b77eda01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0134b4155a46d686eefc053da6a6a437576de91fa82ea7a44b394c52f84cb51dea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830200c004832fefd8808455078f9280a0825968528586aff915ba32ce3d758d4ce919557e8b1061fd1aba58f2425a6904881525dc60cfa62847f901f7a0a7fff3979ac51e84f906f5db54677820b1b99e89069d9d738f5de9bd4c1c1d0ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea07d314df54e76fd5c0a62cf49a2747659b33094ce5fe1c6a23799b795436d8a3da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302010005832fefd8808455078f9480a0c6d8eb152cf04ac30dc7ea9f2fbb9c60da0d1c99c67ed21120854b9bbca573a18838632bca74debee2f901f7a0c34ebf7c1ef971fbf8a69353cd95163bbe4e7da4cdc84ea8b4ea9dac52aa5215a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0ac632b32a6d7b5aaff583ef7cd6b684de9b27f833a39a583b81845be22a8b437a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302014006832fefd8808455078f9580a087bd3e292bdc7be0da54c4dfb799282d52607a95dbf77bdd88209dfdcab6fa74885bc3067200c9969af901f7a0335cb07a70030f7a01adf81b6f4c4f25a46ec2aa7b7534527e232646778368d7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea05e9ce43a12cd2b559e9b3f1560b0beab1af4e9b2b22027eb13e45d1afee47648a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302018007832fefd8808455078f9780a078e86286734268c12dc1d48b94a9ec8a5163116f58a6a4288476291a0a011c1d8804d3e32bfe6a650ef901f7a0ca89f747e7f2e6ad83c5a03d061f2a37a496bfd2f27a85ed6a66103f77cf4d1da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea016cb625dc2d85fe8aa2fe50d3ab8a97ad04c1f583b6b4479150f3dac109e44f9a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830201c008832fefd8808455078f9d80a0c12a4d04f6d7300471de17470d63a61b01c558451ee24f51d856d5b7bd6d93fd881399d91fca852822f901f7a066564c19a879202d9d0a9dae5e88d74e78d7ee4ae94b0ee2bbfef3e3a30dc288a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea01af279b7b4d5002e88b6c2caf70017bfbdf5b16d5c5888e6c265bcdbd3556935a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302020009832fefd8808455078f9e80a0a73b66fb3eac0543f631d309aa314cde5bb839e93ef44d8abe45b411d378a746880df76bf0fe10aa88f901f7a06f2bd00740973ebd96f71ab87d4f20a4d4adb000e36d86e343aad092d7359160a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0e2a70d425a68c1ec3646dbb8a50b3d66fa7300aa78524fb65003ce89452a60cca056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830202400a832fefd8808455078fa280a0cbc4698c185fa19c6e10960d2095ad4a10e94a090cc0f69d7353632fdc3e27088817095cf61cb33a59f901f7a0d68aa8c06e92dc2f47b690b982293716bf1482fc3609552ee544c30c144b05f3a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0ff076322e518c725ae74bfd9758e5a88fd1c49a2676c9b176a99ea970093b8e2a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830202800b832fefd8808455078fa780a016ad6a63216a85b3bfcb59bc8e3b48186b806c4e4173812fd6e4d80069a32742881fb18e9c4c5cb92af901f7a0f709410020e29056962f09c01a2855f4be55ff99f0ecbc7e19178961e7509a44a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0e7d3b71a82c8946c2b231d3825f9fba6f9d78e9e6e46d88ca4891df9658ad720a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830202c00c832fefd8808455078fa980a05b9b342763d6866141299eb59d38715fa1ef07a75d5a443c347126b55e89aecb8845e1d2b3a1f2a7e0f901f7a089d9c0689f10bd5269a7239b4f14f35a07caf04a970d3c180dffe96a24c920cea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0cb82498ca4dcc849a7b5dae2007680cb9ae28776789eb7a3d7269100d1d345c6a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830203000d832fefd8808455078faa80a0904d3fe79058d1bc5cccdcadd15466df4c22bb364b14dbc753df2e665ab9d13a88299b7466d42eb786f901f7a02ba076f9a0dc9981722eea0fafff1d7c169ab4ff4935883d9269db4f1401c695a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea03258428f97c5b35b3b06fb06898b83cc5d50f18afc2ed4f8b16a9edc71f82a94a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830202c00e832fefd8808455078fb280a07f00a9c910ed044de81b81c9cd794ac3703a7e0685cbf51afc57208392042e07882867be7d2c07dafef901f7a07801ae3862ad6497a8776712595a0e71d7f58740c5993e1d3c154594a1ee731ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0881b5a457cd263d26dd5cb28e878bbe2d8db5647c3680b1ce12b2e0ce6a4a9e5a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830202800f832fefd8808455078fbe80a01b0aa1bc72d2ed5fe767edee533fa637a23105877bdb2f0118c97a0eed27c7088841575b69073c9647f901f7a08d72546136df21a0c0ade804b793b0363314d1dc5dbaaf54dc511bb97c3309a7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea05dd1273edae1a4c1189f68cc95361e4405f366926e8fb8b697d10222e68f877ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830202c010832fefd8808455078fbf80a07c8394fede358b90573b84a370d0a64362330e45343f04b3800073e81755a14b884aa3a51da23a9b0ef901f7a07b3709e9bf6abf76b63654bec67149afce801380659aef08ed01ec2746c677bea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0bb4d16c945a20d06b3882463c10508fb33365f3bc548737bc95e938b24c06c06a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302030011832fefd8808455078fc280a0600ae46199f018822f8405bbfb49d8a1bee9201e829db2135a7bb68e0dcb4a658877b4d0826d1d8843f901f7a0c7f3e009c5b1cde06379f975e529e5b3ad5cc07e9abe804b5cfbe6364eb6c758a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea06feb7fa6a0256361ce0f1f5c76689c1c3789396a3a71b26ac7ae26996139770ba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302034012832fefd8808455078fc480a08ce0651288a73f2d732393cfc0e396e5b865f51effc8ac2a0b03ebd6ed7ddacf881903f6241b229080f901f7a01e7da10719ccdb20e4ffa1b4d6408f02c2024d8b84d8d8feaf1790f85a58b3a2a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0643bd80f0187673bdfcbd669ffdd9f8f57d0dc5b013c7fd052c96ca39945d363a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302038013832fefd8808455078fc780a05374340c36b46ee2a8cffe3555db1e05b73d886ae018848d0930dedad9794cd88854fe2abe7692a12af901f7a04d6c250c18d6a5794e3cf2be26850daca61934f67b4006a7bc4bc4bf9f49d677a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0018a02c4d40d33445e715de99b64685c64b61c37fd6055ef2b07f091b9337652a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830203c014832fefd8808455078fc980a00a6c1f550bc56b700fe5949199937c5a5ecb7cae40c19dd128a9da2bc312865c886ef8ef56649cb6ecf901f7a0af099cc423dbc60a266e1d68723d2b451667ac21f6f678e5b16d930721675925a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea031272320b9cabe267aa56c88c5d047f736711a916475fbab48259819fd8952d8a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302040015832fefd8808455078fcc80a0e5074a64927e0263e8c68c8323ccd9aa1b08e0993e6863f29d130e1c08d65170882fb33e73b3e7562ff901f7a048433307000c187e9eecfcd89aee6e538b80630f801d7410af2298ef3e25a5c9a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea027ef1e4a7ac39127cdaf1316aa780bb8e3ced78945502e5976a0dc168e3ba8c8a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302044016832fefd8808455078fcf80a0ea8473e68c60bdda8db3264e78cd6edbf35176e74e8647e258624ae5590d4583885ac6bd86a122135ef901f7a03631ca325c90c173fb3ac9c4ed85f4d43d399c08e6649968898978365b3e3d3fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea09c305bed362ec8cdb70a1b1145cd40ae2bde66fa7df433c322e22776788681e9a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302048017832fefd8808455078fd280a0262fc08e1c553727bb8740e4d1a6f61295b3f13e59455a7636378df9ea49db0988711373faa11301a2f901f7a0d31fd4529e2e51b53237e2d577e91bb8f1b6ea5c847665e5d9ab9b8eeeacdf67a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea00b027b12839880b5f2b0163628c69ce48f44498eb9b73bb7262d28b7a022f5caa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830204c018832fefd8808455078fd380a060139fb9f392d3786ecf84fc48b86689378ec609a8063840b907f0f21a599350882f681216de1354d9f901f7a0a68c4d4dcd26cacd006813bcb1835f82ad933b6909e0fa4ecdff14fa824bb7d8a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0ccc5198b65bf87b12808f4617b729ef4a2943ccbe9572497604d7542f1865473a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302050019832fefd8808455078fd780a01893062e27efcc9f9b30c50910d83ed91579d1552aa63166c6772cd0e17f7bfe884ee14e83b3b44da4f901f7a004ad9379ce3718d4dbbe5fb3eb001d7ee2dc56be55237e8e9a06c344d529ed61a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0cf6b624e5b34441edda94b0e60f83fe3604c410a0e3acf86a4e6f22e71519eeea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830204c01a832fefd880845507902880a0c3c4f859ddc320b942d3b6f1ba721ad559ec778591b4849ca70962aa2d88084d881dce4980ba17111af901f7a0362e4d0892f5a3bf6b352ee01eee2b9d0ba6f67e48d3543fa900efdc4f687e4ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea05d2f6cedddccd770069c78b55899e9ecce2b33534b5982a87ab6bc0cfe759e2ba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830205001b832fefd880845507902a80a079432511548c4c79c924b794cf542fd9a1d9feed8ab8982f8acdb3018cdfc081881a6b272357b74c39f901f7a0ca2d9e6ffb9a9688e4a3a3498f07ab43b0ff20a9b3ce4739a91de1c033b19130a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0015d7ff2816c5d7ec051dba33fe35f6b217282eba8c49b7a53239723e8766e2ba056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830205401c832fefd880845507902b80a075186e01b727c1df61d0b267269cf68ddfb68b50254300b361b66fea3a92deb58851a4d397f2703fa0f901f7a0aeb39365a8c78d1fb494d558f7a5c9867a4a86ab799dded720654e9ef5316969a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea08e04f11bb1ce75a5ece3e5fd361a88994fbed59807e271566f99088f94888a67a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830205801d832fefd880845507902e80a0b2ac0ed00bc5c293121f7f65820006dc35a0c48fae0da9eb4fa1aa7c3521e298885c1df2981ade5054f901f7a058efcae5d7a161f96ec968af7456d46333fc651e4d7f5470b9384d6a4cef12ada01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea06562908763d77dca1d050a581cec730642f8870bc8c9c3a49c09832fca2766baa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830205c01e832fefd880845507903180a02d72111e70319f51e4ac2eee455a656d290b07a1d1d43dc60797c7d76be902c688672d13d1cec32917f901f7a0dacf2d33d1f037d1e1fcb35d84950985c307f5aac51aaad9e2dc847b33e9879fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea036fa5f7c2082b02ff970bae13d90eaf4d1d9ca82be2d5e794ee4e7ac59ef4a4da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830206001f832fefd880845507903780a0691fa8307186b1b6a4f82fded91e04e4281b1377bc1fdae0faf54352cef54fc7883d2dcefc473eb734f901f7a0ce484d5035a7e6cbff78ebd8c8340d7bef5339b44f04dcef27c6b7ad405bbc05a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0f08bb3a6620d51e22b052791dffc878938ae06d4ab8eea77f0281e2dce539682a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830205c020832fefd880845507904580a03e460f6c9c0f26108507ccfee87f71377dee2a76d1aeb4e50247a7035f6ac8248859a3115545145c97f901f7a0911d05b2b9bbf4cc8efe48f5d0f34181b1aba44b50b8feb2ad52e9e496335e3fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0c861459243929f356c5ffc8033a7ec162617a9a042aa4b0c4c19235b9bb9d7eaa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302060021832fefd880845507904b80a0a9e22518ffce6292a2b799a897c6a00877fc5dddd6813ba710feef39e2c2b4918869d8e78eb5d48dd0f901f7a05a8bf98d13d8c2bcba52b0f3d96ffdcbb2ad5fb604407d72aac987fdf3fbe130a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea05371d13d736820a9c2914f6e346cc48f411a7396e6eb36f64d62e95e59daf245a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302064022832fefd880845507904e80a0eda8d1f48e5f9b86e21b68a58bac48f9625f6299fb7f71c330ae9ea413de06a6887a522393154a5048f901f7a0bb7034741b63f245fe98ec15576e883e8e4fa4e15a78ee63179ba5ca56a00243a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea083e49ad29822bb6aebd7710e4f5be64bb34a30349c663d5fb51da7f758b06500a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302068023832fefd880845507904f80a08139068727b9d76281b72c70f2cee30655d7e756b341ea1ee6866872a6028efd881096f6ddb05d3e60f901f7a0716d42eab22a366015ab19dad5c23b759fd9b3840aadab6b0a6a24eee6c9b46fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0ff44c9b3d61b02a3cceb5cf39cc895c3b196154f9534a8a3734f69f9dd310288a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830206c024832fefd880845507905080a05faac7ac6c26058e0b144782e5d0e9d1b391294eaeeabd038620f7d849aab5d0886a7480d9430c9d5bf901f7a0130a387889c5a2c63a9b58a0ab0cd1396e5d16ab8848bd5ff7a8cfd719fd64e5a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0acfc44fb4f0bbe3b2b6087674087dfce602697dc0f6f429f7d7a7188a6df7689a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302070025832fefd880845507905180a08bce2ef1d6707b9606eb8e7fb92b778552da9eea8d8ce0d26a003182ed1042078818bd5f70ae5819aff901f7a086cfb345dc3ff5e475f4924a38123c147f41b3a8f638cf3144514f0ff6e8f104a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0d6f303f9116c5f57a310b04f06daefb04fa274114be419d1a6a0253763952a90a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830206c026832fefd880845507905b80a085b5af8d26f8434c6be497431f4fadf410c40a2cef410f5de66d9984142b4b3d887e296e56f12fbeaef901f7a00c54ea0a6dc6d8347c2275262f8b59bed0ac4306834e6fc2054a98a511d87d68a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0368b07c7139fbcc2ff2611390c53996bc144cf562d848c433cf6e9e8129d8ae1a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302070027832fefd880845507906180a081b433d1cbdf93f0bdf7eba668ed4cd1a418f3945077f975ddfe5b7690185522884a1b97d268210133f901f7a0bc6742e92a772fed0a9e3d867f58e3501a407676419fcc551e9ceafd774b7feda01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea06b472c2b0df9ac132e721bf0b045aad20489db9af9b7a5ec46871b665d98fad0a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302074028832fefd880845507906380a02afdb6eb8e30959888ea2d19440141382af5b6cedba386c7b942b6394e1c5e0a88330f7c8d775ff0ebf901f7a00dd6ef9fe90a342f645f8026e8c897543aace0e678f887ddddb4028979fa11b5a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea042023c3eecf0365f7fa97ec338ef3481b9efff9af734fe66bf58a41ddc5ecc8da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302078029832fefd880845507906780a0d849a5fc1352aa1081dd333a50efee82648db15f7ea49b13679f118fea09ec9388761ab36a729f56f1f901f7a0a6389d7a790be98217ae4d381b51804e1446565282ca9c429872e0bdd9857e0aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea058650430b6877814e0854d6f306614b2a670c3e47d77e868a905d3d1450c5514a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830207c02a832fefd880845507906980a0ba74fb9fe07a4c661b719e790593d45b852d03ded7b1545ac7e074047c311615887f36fa92a0e9cc83f901f7a0825b68ce33fa1ca94638df4f6a85475420d67fdb9c9ebe77b89287f18f82fd3aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea04b91a56333f68d3098d7850c6b736c8ec712cbab8830c3cff4b2482af6b2c081a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830207802b832fefd880845507907180a04f9d4690b46610212f002a4bbf92b8f62a88baba3cfecf2c9b4561b368bcd6ba883f3e235ce13ba896f901f7a06462dc68b0cb1bfed14b1a68a442dd7f33b3a96787f465fe421b2c62cfe32158a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0cc1e8e4202e61897f52935c910633957966779c1364d7b0956b97d4e1db291ada056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830207c02c832fefd880845507907280a0f95a8c3765a3f4a88055975d8e43587711331c47c38ed105b2a1c0c7790eb624883730befbd867ed37f901f7a017c5b3f9ee3b1f130463aeb41914651130ad550400e61183ecc8d5b9ae4d1a24a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0d7fe3ef599742572ef907f55f93df9037ca7ca330b777a7d33e6a66b62502105a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830208002d832fefd880845507907880a09253332f188019a21593f5baaf06ab7651b55e622c48e87cb2b98a44f94dd02988177ce4ae02ed46f9f901f7a0e70768e4425d8af221763e0129d9840dd1b81d21d613361e1d0ff82d020321b6a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea00fa3aecfa3f1ee9c49a80322bfdbbeb58ca1541b4c53b707b9920d381cdce005a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830208412e832fefd880845507907980a0c25837e8b5a7b8143e1c3b0626df9ef5a5b76891639c5a459a4af06e7998d2e98800ba744acf68aaaef901f7a09ad2ff787a448a42caeec3899f24a2b8e499f91878546b55401bd04ba2d8b200a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea04a457a7f6c8dc9b2458193eacbcce50e3e02a7d6886b7385ab7ab504c908d22aa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830208822f832fefd880845507907a80a095cc704ee6a7ea94c538b315294c8597b510ae147c0d095a6f9e60651c8452e5885a6a868c137c5536f901f7a052a4536cda0ca7eddc0d72cb8b841465e398af13b37f7e7da2a60ace3f7b92e2a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0a256e924896847003d8fa82f5d26120bdb53ef0c9319172851f5136136247266a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830208c330832fefd880845507907b80a096fed26abd9a33c36972140a2fa5181a1d74d448803ac787a057c5df328d85d58864736fdcc8e4fbd3f901f7a0e300bbff76c335a76ee8ece5d2e929e57eb980dc3bab4274fe3b461bff0bc71fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea01b07ab22e6bf1a097d32052558f6e9ca97cd3954c82b3422a64a0a0fea133867a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302090431832fefd880845507907e80a0c9d3bd8bfefa7e1252755d08ed4ef5bfbf6d487523e2e3711614bef84f5a956d8830c7da48ce5fcd8bf901f7a0aa16aac485a246b4dfcf65f56035f6c3fa79510f34977bc09a165c3cda617a3ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0868a25e057cfd5eee3ecf45cbb53b3193645034168b619a379a66953a202f606a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302094532832fefd880845507907f80a0c74f81d658abbdf7dffcd6413b19472c3b091a7554846cb2ad7f0dadcfe6aa77883364abba49694468f901f7a0868e112d820e08108a0298dba78e1435169193e098ecd6662c6bb1521568a49ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea09b94af89c1f2954afb0dd0bdefe905df00eba83e665892f6c7817b653f775370a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302098633832fefd880845507908080a0b05baff7875915352bae45ead0f4936207132a405d04240eae7fbbb28c4338cc881bc214b0306ad86af901f7a094c8e22292bda2f202acf5e0bddbb09209e5b7a4288bb1e01c062f60554081bba01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea07060173417f81fb10766f01ee233c21c526e642179a2d7fc8dff3cacb5fb150fa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000830209c734832fefd880845507908380a040ba56fe3e16c4b5b6f0d36f0c1ab15c68ee4a101c091303fec40bcab095b9eb8844285ce3d91c73def901f7a05b442e4d30f0904a2871a07639d9cf3e798ea22da4e3b41e7fd7483a20470590a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0dbeb30eb15bfe2384317e3aed0c0043c1164a794f627b61b12c70a5cf601621aa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020a0835832fefd880845507908580a06827162ef0c753b3de63d44ebdfcffe812ecb2231fad948b5fb6507e79ce00ee884f80ef66236d24d1f901f7a0a82a189f3aab3396ec9f9738407d105b8d1b75df6c90414e7c18b28c89be604ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea085cd99fc3f2cdf7d6ec204abb6e7512e02b011ed5013ab63d1349d81496eb590a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020a4936832fefd880845507908c80a05cf0e1c2828b84a4dad1156e5dc2c56249deb6f0c489c1be7ca03a3a623f83af8821866ac6af4086e6f901f7a05dc8ce6ddd7c01e4c87f24bb4ffb8e8a94f079afe59b43575d218dd01bf7b3d7a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea04eb7b0f8c33f72ef43ee942b2285bd0e0919b0019780b250d4a970f327517c80a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020a8a37832fefd880845507909080a0b59196af15a5011c6e6a0d0c8161b2225962dfa0e23fde8f05a07c809d97c1a788218296332384a480f901f7a09c01b3b0fa16262d9af60c702b7722a8ec88c00aea2baedd9ff08307dba1a0a1a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0ae6cd97c4cf417bbce24320931c3b3a5804c3fe79bf52cef008f0368a798fa3fa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020acb38832fefd880845507909380a078186187f869b6da37172f26228834ad4ec2cb1f2857518dd8cd3da75ac674c28840be6fa323adb244f901f7a0b20b52657de7ab0bb218999f72e46f7546bc6adc63f8162bed9df0b1bff8fd7da01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0beaebeda2b28309ed2a404e1a86eb65f62d6cd708f4b4bb8ef6f1a7e0f12f0bca056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020b0c39832fefd880845507909680a087bea1de5a9f0766bba67ba4e6874d2a1165285195733e1c8f034c9f97d526e68805d0b715d89a89eef901f7a09032840b141d27beaa1774a1557a1f83aee4310915ea3608bfcfcbc7ad130db2a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0e32e4bb770978f832701500fe1168c1cccdf50c64305d682ef1985b3861654caa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020b4d3a832fefd880845507909880a0bac28b0e84b08494f6a65f715512c92e722c5a4b9182e7175bdbaf9a1e2de4958853df300f65330178f901f7a0673ddc42f8b723d55f03cb30a635adce6f612127c104daa4ec3c3eb0237f5b4ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0f1fa605974e7566a9aeee17c2bafdd4589d040c0a4289bb13ed537b68d06629fa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020b8e3b832fefd880845507909980a0d4837da3d59dd679398c412b3bbdc9b4efec48599c580b6864f35a7d32ca52fc883269d59ac9c1c4abf901f7a0e4d677a4fc7ce560e4c2e09d9534f2e2c4a60e2d94f84d4aecb97fbc31fd749aa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0a8e478836399e1622084c1319a8fbe1cceecc46017a7925f65ae8f860f5367b4a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020bcf3c832fefd880845507909c80a0e07c509c5dd6a69d554b8c15930141cbbdc8606336ceae6f9c122f4f0d2507f8885d69cfc1173abb31f901f7a0d2236d4f0094097ba4c1b7e61301c9408b277489f74d6ce82f33862b538becb4a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea06c29772c717600948b2db214f1505544546e497938ad8b188fd7be3251db438ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020c103d832fefd880845507909e80a0cb060cf141d422d6253a978924786c2e564a02b0b41b20783eeae272ba51864688504e5e7a7b53eb12f901f7a08b845d330bb5301d8353e319910decc9f6f069b4cceaa191e2a9eee8a2548cd5a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea0c42371e4224f2b66a68c658e3c68760f38a1578f62b46ac6811f8cb0bcf34aa2a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020c513e832fefd88084550790a480a0d7062ff6e3eca3433adbca1338c46c06b636d07a68d19a3d8241f7f4f93656a08863243c8d28e578f2f901f7a0c8b0dcc5d63f3403a3a688bd498178fb98a7a2d5548f40d70b05ee37ac1c1b49a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea035fa930db87c6c3542c3652c976d20721afbad149ea6652e9b8d3661f75f2b6aa056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020c923f832fefd88084550790a780a05f8454cd4e95a9474d4e2206c2a18facd86cf2a667b9be79a4ee0026a05dd353882150fab90c3f5c54f901f7a00bae39b9e308c9c1a2ada328e9527bb5aeea6d9c8b2531a2802181838ba9dc7fa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479429d77f87c78ccff9d8ffc639d9d4151b7d33659ea03a0fdb8dc51b4ccc06dc4b92f592c367297de7442c33e71a4c30bee1d65a8163a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020cd340832fefd88084550790ab80a0cddd2d2b6f6cf909c33cddcb5e6ec0c5b5cc61f759626a5f4a1499b0d2f523de88203a21d7532c50c7 \ No newline at end of file diff --git a/pyethapp/tests/test_account_service.py b/pyethapp/tests/test_account_service.py index 64ea5d3f..97a339b7 100644 --- a/pyethapp/tests/test_account_service.py +++ b/pyethapp/tests/test_account_service.py @@ -2,7 +2,7 @@ import shutil import tempfile from uuid import uuid4 -import ethereum.keys +import ethereum.tools.keys from ethereum.slogging import get_logger from ethereum.utils import decode_hex, remove_0x_head from devp2p.app import BaseApp @@ -11,7 +11,7 @@ # reduce key derivation iterations -ethereum.keys.PBKDF2_CONSTANTS['c'] = 100 +ethereum.tools.keys.PBKDF2_CONSTANTS['c'] = 100 log = get_logger('tests.account_service') @@ -50,6 +50,9 @@ def uuid(): def account(privkey, password, uuid): return Account.new(password, privkey, uuid) +@pytest.fixture() +def patched_logger_warning(mocker): + return mocker.patch('logging.Logger.warning') def test_empty(app): s = app.services.accounts @@ -357,3 +360,35 @@ def test_coinbase(app, account): app.config['accounts']['must_include_coinbase'] = True with pytest.raises(ValueError): s.coinbase + +def test_get_by_address(app, account): + """ + Makes sure we can retrieve existing account by address. + """ + s = app.services.accounts + s.add_account(account, store=False) + assert account == s.get_by_address(account.address) + +def test_get_by_address_no_match(app, account): + """ + Makes sure an exception is raised when trying to + retrieve an account that's not available. + """ + s = app.services.accounts + try: + 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" + +def test_get_by_address_multiple_match(app, account, patched_logger_warning): + """ + Verifies a warning is sent on get_by_address() call with multiple accounts match. + """ + s = app.services.accounts + s.add_account(account, store=False) + account.uuid = None + 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')) diff --git a/pyethapp/tests/test_accounts.py b/pyethapp/tests/test_accounts.py index 13f1ad6f..d27d4e43 100644 --- a/pyethapp/tests/test_accounts.py +++ b/pyethapp/tests/test_accounts.py @@ -1,13 +1,13 @@ import json from uuid import uuid4 -import ethereum.keys -from ethereum.keys import privtoaddr +import ethereum.tools.keys +from ethereum.tools.keys import privtoaddr from ethereum.transactions import Transaction from pyethapp.accounts import Account import pytest # reduce key derivation iterations -ethereum.keys.PBKDF2_CONSTANTS['c'] = 100 +ethereum.tools.keys.PBKDF2_CONSTANTS['c'] = 100 @pytest.fixture() diff --git a/pyethapp/tests/test_config.py b/pyethapp/tests/test_config.py index ac418453..1d282817 100644 --- a/pyethapp/tests/test_config.py +++ b/pyethapp/tests/test_config.py @@ -1,7 +1,6 @@ from devp2p.peermanager import PeerManager from devp2p.discovery import NodeDiscovery from devp2p.app import BaseApp -from py._path.local import LocalPath from pyethapp.eth_service import ChainService from pyethapp.jsonrpc import JSONRPCServer from pyethapp.db_service import DBService diff --git a/pyethapp/tests/test_console_service.py b/pyethapp/tests/test_console_service.py index 709882b3..ca6b03e1 100644 --- a/pyethapp/tests/test_console_service.py +++ b/pyethapp/tests/test_console_service.py @@ -3,11 +3,12 @@ import serpent from devp2p.peermanager import PeerManager import ethereum -from ethereum import tester -from ethereum.ethpow import mine -import ethereum.keys +from ethereum.tools import tester +from ethereum.pow.ethpow import mine +import ethereum.tools.keys import ethereum.config -from ethereum.slogging import get_logger +from ethereum.slogging import get_logger, configure_logging +from ethereum.state import State 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 @@ -17,9 +18,9 @@ from pyethapp.console_service import Console # reduce key derivation iterations -ethereum.keys.PBKDF2_CONSTANTS['c'] = 100 - +ethereum.tools.keys.PBKDF2_CONSTANTS['c'] = 100 +configure_logging(':trace') log = get_logger('test.console_service') @@ -47,7 +48,9 @@ def mine_next_block(self): :returns: the new head """ log.debug('mining next block') - block = self.services.chain.chain.head_candidate + block = self.services.chain.head_candidate + chain = self.services.chain.chain + head_number = chain.head.number delta_nonce = 10**6 for start_nonce in count(0, delta_nonce): bin_nonce, mixhash = mine(block.number, block.difficulty, block.mining_hash, @@ -55,9 +58,20 @@ def mine_next_block(self): if bin_nonce: break self.services.pow.recv_found_nonce(bin_nonce, mixhash, block.mining_hash) + if len(chain.time_queue) > 0: + # If we mine two blocks within one second, pyethereum will + # force the new block's timestamp to be in the future (see + # ethereum1_setup_block()), and when we try to add that block + # to the chain (via Chain.add_block()), it will be put in a + # queue for later processing. Since we need to ensure the + # block has been added before we continue the test, we + # have to manually process the time queue. + log.debug('block mined too fast, processing time queue') + chain.process_time_queue(new_time=block.timestamp) log.debug('block mined') - assert self.services.chain.chain.head.difficulty == 1 - return self.services.chain.chain.head + assert chain.head.difficulty == 1 + assert chain.head.number == head_number + 1 + return chain.head config = { 'data_dir': str(tmpdir), @@ -112,21 +126,23 @@ def main(a,b): tx_to = b'' evm_code = serpent.compile(serpent_code) chain = test_app.services.chain.chain - assert chain.head_candidate.get_balance(tx_to) == 0 + chainservice = test_app.services.chain + hc_state = State(chainservice.head_candidate.state_root, chain.env) sender = test_app.services.accounts.unlocked_accounts[0].address - assert chain.head_candidate.get_balance(sender) > 0 + assert hc_state.get_balance(sender) > 0 eth = test_app.services.console.console_locals['eth'] tx = eth.transact(to='', data=evm_code, startgas=500000, sender=sender) - code = chain.head_candidate.account_to_dict(tx.creates)['code'] + hc_state_dict = State(chainservice.head_candidate.state_root, chain.env).to_dict() + code = hc_state_dict[tx.creates.encode('hex')]['code'] assert len(code) > 2 assert code != '0x' test_app.mine_next_block() - creates = chain.head.get_transaction(0).creates - code = chain.head.account_to_dict(creates)['code'] + creates = chain.head.transactions[0].creates + code = chain.state.to_dict()[creates.encode('hex')]['code'] assert len(code) > 2 assert code != '0x' @@ -153,30 +169,33 @@ def test_console_name_reg_contract(test_app): } """ - import ethereum._solidity - solidity = ethereum._solidity.get_solidity() + import ethereum.tools._solidity + solidity = ethereum.tools._solidity.get_solidity() if solidity is None: pytest.xfail("solidity not installed, not tested") else: # create the NameReg contract tx_to = b'' evm_code = solidity.compile(solidity_code) + chainservice = test_app.services.chain chain = test_app.services.chain.chain - assert chain.head_candidate.get_balance(tx_to) == 0 + hc_state = State(chainservice.head_candidate.state_root, chain.env) sender = test_app.services.accounts.unlocked_accounts[0].address - assert chain.head_candidate.get_balance(sender) > 0 + assert hc_state.get_balance(sender) > 0 eth = test_app.services.console.console_locals['eth'] tx = eth.transact(to='', data=evm_code, startgas=500000, sender=sender) - code = chain.head_candidate.account_to_dict(tx.creates)['code'] + hc_state_dict = State(chainservice.head_candidate.state_root, chain.env).to_dict() + code = hc_state_dict[tx.creates.encode('hex')]['code'] assert len(code) > 2 assert code != '0x' test_app.mine_next_block() - creates = chain.head.get_transaction(0).creates - code = chain.head.account_to_dict(creates)['code'] + creates = chain.head.transactions[0].creates + state_dict = chain.state.to_dict() + code = state_dict[creates.encode('hex')]['code'] assert len(code) > 2 assert code != '0x' diff --git a/pyethapp/tests/test_eth_protocol.py b/pyethapp/tests/test_eth_protocol.py index 450aea07..2386370f 100644 --- a/pyethapp/tests/test_eth_protocol.py +++ b/pyethapp/tests/test_eth_protocol.py @@ -1,8 +1,8 @@ -from pyethapp.eth_protocol import ETHProtocol, TransientBlock +from pyethapp.eth_protocol import ETHProtocol, TransientBlockBody from devp2p.service import WiredService from devp2p.protocol import BaseProtocol from devp2p.app import BaseApp -from ethereum import tester +from ethereum.tools import tester import rlp @@ -18,7 +18,7 @@ def setup(): peer = PeerMock() proto = ETHProtocol(peer, WiredService(BaseApp())) proto.service.app.config['eth'] = dict(network_id=1337) - chain = tester.state() + chain = tester.Chain() cb_data = [] def cb(proto, **data): @@ -42,11 +42,11 @@ def test_basics(): def test_status(): peer, proto, chain, cb_data, cb = setup() - genesis = head = chain.blocks[-1] + genesis = head = chain.chain.get_descendants(chain.chain.get_block_by_number(0))[-1] # test status proto.send_status( - chain_difficulty=head.chain_difficulty(), + chain_difficulty=chain.chain.get_score(head), chain_head_hash=head.hash, genesis_hash=genesis.hash ) @@ -57,7 +57,7 @@ def test_status(): _p, _d = cb_data.pop() assert _p == proto assert isinstance(_d, dict) - assert _d['chain_difficulty'] == head.chain_difficulty() + assert _d['chain_difficulty'] == chain.chain.get_score(head) print _d assert _d['chain_head_hash'] == head.hash assert _d['genesis_hash'] == genesis.hash @@ -69,27 +69,28 @@ def test_blocks(): peer, proto, chain, cb_data, cb = setup() # test blocks - chain.mine(n=2) - assert len(chain.blocks) == 3 - payload = [rlp.encode(b) for b in chain.blocks] - proto.send_blocks(*payload) + chain.mine(number_of_blocks=2) + assert chain.block.number == 3 + # monkey patch to make "blocks" attribute available + chain.blocks = chain.chain.get_descendants(chain.chain.get_block_by_number(0)) + proto.send_blockbodies(*chain.blocks) packet = peer.packets.pop() assert len(rlp.decode(packet.payload)) == 3 def list_cb(proto, blocks): # different cb, as we expect a list of blocks cb_data.append((proto, blocks)) - proto.receive_blocks_callbacks.append(list_cb) - proto._receive_blocks(packet) + proto.receive_blockbodies_callbacks.append(list_cb) + proto._receive_blockbodies(packet) _p, blocks = cb_data.pop() - assert isinstance(blocks, list) + assert isinstance(blocks, tuple) for block in blocks: - assert isinstance(block, TransientBlock) - assert isinstance(block.transaction_list, tuple) + assert isinstance(block, TransientBlockBody) + assert isinstance(block.transactions, tuple) assert isinstance(block.uncles, tuple) # assert that transactions and uncles have not been decoded - assert len(block.transaction_list) == 0 + assert len(block.transactions) == 0 assert len(block.uncles) == 0 # newblock @@ -104,8 +105,8 @@ def list_cb(proto, blocks): # different cb, as we expect a list of blocks assert 'chain_difficulty' in _d assert _d['chain_difficulty'] == approximate_difficulty assert _d['block'].header == chain.blocks[-1].header - assert isinstance(_d['block'].transaction_list, tuple) + assert isinstance(_d['block'].transactions, tuple) assert isinstance(_d['block'].uncles, tuple) # assert that transactions and uncles have not been decoded - assert len(_d['block'].transaction_list) == 0 + assert len(_d['block'].transactions) == 0 assert len(_d['block'].uncles) == 0 diff --git a/pyethapp/tests/test_eth_service.py b/pyethapp/tests/test_eth_service.py index c4f4d794..e9602c30 100644 --- a/pyethapp/tests/test_eth_service.py +++ b/pyethapp/tests/test_eth_service.py @@ -1,31 +1,24 @@ import os -from pyethapp import monkeypatches +import pytest from ethereum.db import EphemDB +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 eth_protocol from ethereum import slogging +from ethereum.tools import tester from ethereum import config as eth_config +from ethereum.transactions import Transaction import rlp import tempfile -slogging.configure(config_string=':info') +slogging.configure(config_string=':debug') empty = object() class AppMock(object): - config = dict( - app=dict(dir=tempfile.mkdtemp()), - db=dict(path='_db'), - eth=dict( - pruning=-1, - network_id=1, - block=eth_config.default_config - ), - ) - class Services(dict): class accounts: @@ -37,9 +30,19 @@ class peermanager: def broadcast(*args, **kwargs): pass - def __init__(self, db=None): + def __init__(self, db=None, config={}): self.services = self.Services() self.services.db = EphemDB() + if 'app' not in config: + config['app'] = dict(dir=tempfile.mkdtemp()) + if 'db' not in config: + config['db'] = dict(path='_db') + if 'eth' not in config: + config['eth'] = dict( + pruning=-1, + network_id=1, + block=eth_config.default_config) + self.config = config class PeerMock(object): @@ -66,20 +69,20 @@ def __init__(self, app): "5f0b5d2a84fef43716c1f16c71d9a32193d881c2ea8eea335e950c0c08502595559e2") block_1 = ( - "f901fcf901f7a0fd4af92a79c7fc2fd8bf0d342f2e832e1d4f485c85b9152d2039e03bc604f" - "dcaa01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479415ca" - "a04a9407a2f242b2859005a379655bfb9b11a00298b547b494ff85b4750d90ad212269cf642" - "f4fb7e6b205e461f3e10d18a950a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cad" - "c001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb" - "5e363b421b90100000000000000000000000000000000000000000000000000000000000000" - "000000000000000000000000000000000000000000000000000000000000000000000000000" + "f901f7a0fd4af92a79c7fc2fd8bf0d342f2e832e1d4f485c85b9152d2039e03bc604fdcaa01" + "dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493479415caa04a94" + "07a2f242b2859005a379655bfb9b11a00298b547b494ff85b4750d90ad212269cf642f4fb7e" + "6b205e461f3e10d18a950a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc00162" + "2fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b" + "421b90100000000000000000000000000000000000000000000000000000000000000000000" "000000000000000000000000000000000000000000000000000000000000000000000000000" "000000000000000000000000000000000000000000000000000000000000000000000000000" "000000000000000000000000000000000000000000000000000000000000000000000000000" "000000000000000000000000000000000000000000000000000000000000000000000000000" "000000000000000000000000000000000000000000000000000000000000000000000000000" - "008302000001832fefd880845504456080a0839bc994837a59595159fb15605b6db119237c7" - "504edf5c5853b248700e0789c8872cf25e7727307bac0c0") + "000000000000000000000000000000000000000000000000000000000000000000000008302" + "000001832fefd880845504456080a0839bc994837a59595159fb15605b6db119237c7504edf" + "5c5853b248700e0789c8872cf25e7727307ba") fn = 'blocks256.hex.rlp' @@ -95,7 +98,7 @@ def test_receive_newblock(): eth.on_receive_newblock(proto, **d) -def receive_blocks(rlp_data, leveldb=False, codernitydb=False): +def receive_blockheaders(rlp_data, leveldb=False, codernitydb=False): app = AppMock() if leveldb: app.db = leveldb_service.LevelDB( @@ -106,18 +109,64 @@ def receive_blocks(rlp_data, leveldb=False, codernitydb=False): eth = eth_service.ChainService(app) proto = eth_protocol.ETHProtocol(PeerMock(app), eth) - b = eth_protocol.ETHProtocol.blocks.decode_payload(rlp_data) - eth.on_receive_blocks(proto, b) + b = eth_protocol.ETHProtocol.blockheaders.decode_payload(rlp_data) + eth.on_receive_blockheaders(proto, b) def test_receive_block1(): rlp_data = rlp.encode([rlp.decode(block_1.decode('hex'))]) - receive_blocks(rlp_data) - - -def test_receive_blocks_256(): - receive_blocks(data256.decode('hex')) - - -def test_receive_blocks_256_leveldb(): - receive_blocks(data256.decode('hex'), leveldb=True) + receive_blockheaders(rlp_data) + + +def test_receive_blockheaders_256(): + receive_blockheaders(data256.decode('hex')) + + +def test_receive_blockheaders_256_leveldb(): + receive_blockheaders(data256.decode('hex'), leveldb=True) + + +@pytest.fixture +def test_app(tmpdir): + config = { + 'eth': { + 'pruning': -1, + 'network_id': 1, + 'block': { # reduced difficulty, increased gas limit, allocations to test accounts + 'ACCOUNT_INITIAL_NONCE': 0, + 'GENESIS_DIFFICULTY': 1, + 'BLOCK_DIFF_FACTOR': 2, # greater than difficulty, thus difficulty is constant + 'GENESIS_GAS_LIMIT': 3141592, + 'GENESIS_INITIAL_ALLOC': { + tester.accounts[0].encode('hex'): {'balance': 10 ** 24}, + tester.accounts[1].encode('hex'): {'balance': 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}, + } + } + } + } + update_config_with_defaults(config, {'eth': {'block': eth_config.default_config}}) + app = AppMock(config=config) + app.chain = eth_service.ChainService(app) + return app + + +def test_head_candidate(test_app): + chainservice = test_app.chain + assert len(chainservice.head_candidate.transactions) == 0 + for i in range(5): + tx = make_transaction(tester.keys[i], 0, 0, tester.accounts[2]) + chainservice.add_transaction(tx) + assert len(chainservice.head_candidate.transactions) == i + 1 + + +def make_transaction(key, nonce, value, to): + gasprice = 20 * 10**9 + startgas = 500 * 1000 + v, r, s = 0, 0, 0 + data = "foo" + tx = Transaction(nonce, gasprice, startgas, to, value, data, v, r, s) + tx.sign(key) + return tx diff --git a/pyethapp/tests/test_export.py b/pyethapp/tests/test_export.py index 862a1e6c..962c4bd4 100644 --- a/pyethapp/tests/test_export.py +++ b/pyethapp/tests/test_export.py @@ -1,8 +1,5 @@ -from StringIO import StringIO import subprocess -from pyethapp.app import app -from click.testing import CliRunner -from ethereum.blocks import BlockHeader +from ethereum.block import BlockHeader import rlp import pytest diff --git a/pyethapp/tests/test_genesis.py b/pyethapp/tests/test_genesis.py index 2fb30e91..f846eaf1 100644 --- a/pyethapp/tests/test_genesis.py +++ b/pyethapp/tests/test_genesis.py @@ -1,8 +1,9 @@ from pprint import pprint import pytest -from ethereum import blocks from ethereum.db import DB -from ethereum.config import Env +from ethereum.config import Env, default_config +from ethereum.genesis_helpers import mk_genesis_block +from ethereum.state import State from pyethapp.utils import merge_dict from pyethapp.config import update_config_from_genesis_json import pyethapp.config as konfig @@ -16,7 +17,7 @@ def test_genesis_config(): '3' * 20: {'balance': 3}, # 20 bytes } config = dict(eth=dict(genesis=dict(alloc=alloc))) - konfig.update_config_with_defaults(config, {'eth': {'block': blocks.default_config}}) + konfig.update_config_with_defaults(config, {'eth': {'block': default_config}}) # Load genesis config update_config_from_genesis_json(config, config['eth']['genesis']) @@ -25,17 +26,18 @@ def test_genesis_config(): pprint(bc) env = Env(DB(), bc) - genesis = blocks.genesis(env) + genesis = mk_genesis_block(env) + state = State(genesis.state_root, env) for address, value_dict in alloc.items(): value = value_dict.values()[0] - assert genesis.get_balance(address) == value + assert state.get_balance(address) == value @pytest.mark.parametrize('profile', PROFILES.keys()) def test_profile(profile): config = dict(eth=dict()) - konfig.update_config_with_defaults(config, {'eth': {'block': blocks.default_config}}) + konfig.update_config_with_defaults(config, {'eth': {'block': default_config}}) # Set config values based on profile selection merge_dict(config, PROFILES[profile]) @@ -47,5 +49,5 @@ def test_profile(profile): pprint(bc) env = Env(DB(), bc) - genesis = blocks.genesis(env) + genesis = mk_genesis_block(env) assert genesis.hash.encode('hex') == config['eth']['genesis_hash'] diff --git a/pyethapp/tests/test_jsonrpc.py b/pyethapp/tests/test_jsonrpc.py index 6b980055..39ae6bd6 100644 --- a/pyethapp/tests/test_jsonrpc.py +++ b/pyethapp/tests/test_jsonrpc.py @@ -11,12 +11,12 @@ import serpent import ethereum import ethereum.config -import ethereum.keys -from ethereum.ethpow import mine -from ethereum import tester -from ethereum.slogging import get_logger +import ethereum.tools.keys +from ethereum.pow.ethpow import mine +from ethereum.tools import tester +from ethereum.slogging import get_logger, configure_logging +from ethereum.state import State from devp2p.peermanager import PeerManager -import ethereum._solidity from pyethapp.accounts import Account, AccountsService, mk_random_privkey from pyethapp.app import EthApp @@ -29,12 +29,13 @@ from tinyrpc.protocols.jsonrpc import JSONRPCProtocol from pyethapp.profiles import PROFILES from pyethapp.pow_service import PoWService -from ethereum import _solidity +from ethereum.tools import _solidity from ethereum.abi import event_id, normalize_name from pyethapp.rpc_client import ContractProxy -ethereum.keys.PBKDF2_CONSTANTS['c'] = 100 # faster key derivation +ethereum.tools.keys.PBKDF2_CONSTANTS['c'] = 100 # faster key derivation log = get_logger('test.jsonrpc') # pylint: disable=invalid-name +configure_logging(':trace') SOLIDITY_AVAILABLE = 'solidity' in Compilers().compilers # EVM code corresponding to the following solidity code: @@ -76,7 +77,7 @@ def test_compile_solidity(): with open(path.join(path.dirname(__file__), 'contracts', 'multiply.sol')) as handler: solidity_code = handler.read() - solidity = ethereum._solidity.get_solidity() # pylint: disable=protected-access + solidity = ethereum.tools._solidity.get_solidity() # pylint: disable=protected-access abi = solidity.mk_full_signature(solidity_code) code = data_encoder(solidity.compile(solidity_code)) @@ -148,11 +149,14 @@ def start(self): log.debug('adding test accounts') # high balance account self.services.accounts.add_account(Account.new('', tester.keys[0]), store=False) + log.debug('added unlocked account %s' % tester.keys[0]) # low balance account self.services.accounts.add_account(Account.new('', tester.keys[1]), store=False) + log.debug('added unlocked account %s' % tester.keys[1]) # locked account locked_account = Account.new('', tester.keys[2]) locked_account.lock() + log.debug('added locked account %s' % tester.keys[1]) self.services.accounts.add_account(locked_account, store=False) self.privkey = None assert set(acct.address for acct in self.services.accounts) == set(tester.accounts[:3]) @@ -165,17 +169,31 @@ def mine_next_block(self): :returns: the new head """ log.debug('mining next block') - block = self.services.chain.chain.head_candidate + chain = self.services.chain.chain + head_number = chain.head.number + block = self.services.chain.head_candidate delta_nonce = 10 ** 6 for start_nonce in count(0, delta_nonce): bin_nonce, mixhash = mine(block.number, block.difficulty, block.mining_hash, start_nonce=start_nonce, rounds=delta_nonce) if bin_nonce: break - self.services.pow.recv_found_nonce(bin_nonce, mixhash, block.mining_hash) + self.services.pow.recv_found_nonce( + bin_nonce, mixhash, block.mining_hash) + if len(chain.time_queue) > 0: + # If we mine two blocks within one second, pyethereum will + # force the new block's timestamp to be in the future (see + # ethereum1_setup_block()), and when we try to add that block + # to the chain (via Chain.add_block()), it will be put in a + # queue for later processing. Since we need to ensure the + # block has been added before we continue the test, we + # have to manually process the time queue. + log.debug('block mined too fast, processing time queue') + chain.process_time_queue(new_time=block.timestamp) log.debug('block mined') - assert self.services.chain.chain.head.difficulty == 1 - return self.services.chain.chain.head + assert chain.head.difficulty == 1 + assert chain.head.number == head_number + 1 + return chain.head def rpc_request(self, method, *args, **kwargs): """Simulate an incoming JSON RPC request and return the result. @@ -319,14 +337,16 @@ def get_eventname_types(event_description): def test_logfilters_topics(test_app): - # slogging.configure(':trace') sample_compiled = _solidity.compile_code( sample_sol_code, combined='bin,abi', ) - theabi = sample_compiled['SampleContract']['abi'] - theevm = sample_compiled['SampleContract']['bin_hex'] + filepath = None + contract_data = _solidity.solidity_get_contract_data( + sample_compiled, filepath, 'SampleContract') + theabi = contract_data['abi'] + theevm = contract_data['bin_hex'] sender_address = test_app.services.accounts.unlocked_accounts[0].address sender = address_encoder(sender_address) @@ -526,26 +546,28 @@ def test_logfilters_topics(test_app): def test_send_transaction(test_app): - chain = test_app.services.chain.chain - assert chain.head_candidate.get_balance('\xff' * 20) == 0 + chainservice = test_app.services.chain + chain = chainservice.chain + hc = chainservice.head_candidate + state = State(hc.state_root, chain.env) + assert state.get_balance('\xff' * 20) == 0 sender = test_app.services.accounts.unlocked_accounts[0].address - assert chain.head_candidate.get_balance(sender) > 0 + assert state.get_balance(sender) > 0 tx = { 'from': address_encoder(sender), 'to': address_encoder('\xff' * 20), 'value': quantity_encoder(1) } tx_hash = data_decoder(test_app.client.call('eth_sendTransaction', tx)) - assert tx_hash == chain.head_candidate.get_transaction(0).hash - assert chain.head_candidate.get_balance('\xff' * 20) == 1 test_app.mine_next_block() - assert tx_hash == chain.head.get_transaction(0).hash - assert chain.head.get_balance('\xff' * 20) == 1 + assert len(chain.head.transactions) == 1 + assert tx_hash == chain.head.transactions[0].hash + assert chain.state.get_balance('\xff' * 20) == 1 # send transactions from account which can't pay gas tx['from'] = address_encoder(test_app.services.accounts.unlocked_accounts[1].address) tx_hash = data_decoder(test_app.client.call('eth_sendTransaction', tx)) - assert chain.head_candidate.get_transactions() == [] + assert chainservice.head_candidate.transactions == [] def test_send_transaction_with_contract(test_app): @@ -555,26 +577,31 @@ def main(a,b): ''' tx_to = b'' evm_code = serpent.compile(serpent_code) + chainservice = test_app.services.chain chain = test_app.services.chain.chain - assert chain.head_candidate.get_balance(tx_to) == 0 + state = State(chainservice.head_candidate.state_root, chain.env) sender = test_app.services.accounts.unlocked_accounts[0].address - assert chain.head_candidate.get_balance(sender) > 0 + assert state.get_balance(sender) > 0 tx = { 'from': address_encoder(sender), 'to': address_encoder(tx_to), 'data': evm_code.encode('hex') } data_decoder(test_app.client.call('eth_sendTransaction', tx)) - creates = chain.head_candidate.get_transaction(0).creates + assert len(chainservice.head_candidate.transactions) == 1 + creates = chainservice.head_candidate.transactions[0].creates - code = chain.head_candidate.account_to_dict(creates)['code'] + candidate_state_dict = State(chainservice.head_candidate.state_root, chain.env).to_dict() + code = candidate_state_dict[creates.encode('hex')]['code'] assert len(code) > 2 assert code != '0x' test_app.mine_next_block() - creates = chain.head.get_transaction(0).creates - code = chain.head.account_to_dict(creates)['code'] + 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'] assert len(code) > 2 assert code != '0x' @@ -586,25 +613,30 @@ def main(a,b): ''' tx_to = b'' evm_code = serpent.compile(serpent_code) + chainservice = test_app.services.chain chain = test_app.services.chain.chain - assert chain.head_candidate.get_balance(tx_to) == 0 + state = State(chainservice.head_candidate.state_root, chain.env) sender = test_app.services.accounts.unlocked_accounts[0].address - assert chain.head_candidate.get_balance(sender) > 0 - nonce = chain.head_candidate.get_nonce(sender) + assert state.get_balance(sender) > 0 + nonce = state.get_nonce(sender) tx = ethereum.transactions.Transaction(nonce, default_gasprice, default_startgas, tx_to, 0, evm_code, 0, 0, 0) test_app.services.accounts.sign_tx(sender, tx) raw_transaction = data_encoder(rlp.codec.encode(tx, ethereum.transactions.Transaction)) data_decoder(test_app.client.call('eth_sendRawTransaction', raw_transaction)) - creates = chain.head_candidate.get_transaction(0).creates + assert len(chainservice.head_candidate.transactions) == 1 + creates = chainservice.head_candidate.transactions[0].creates - code = chain.head_candidate.account_to_dict(creates)['code'] + candidate_state_dict = State(chainservice.head_candidate.state_root, chain.env).to_dict() + code = candidate_state_dict[creates.encode('hex')]['code'] assert len(code) > 2 assert code != '0x' test_app.mine_next_block() - creates = chain.head.get_transaction(0).creates - code = chain.head.account_to_dict(creates)['code'] + 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'] assert len(code) > 2 assert code != '0x' @@ -617,7 +649,17 @@ def test_pending_transaction_filter(test_app): 'to': address_encoder('\xff' * 20) } - def test_sequence(s): + sequences = [ + 't', + 'b', + 'ttt', + 'tbt', + 'ttbttt', + 'bttbtttbt', + 'bttbtttbttbb', + ] + for s in sequences: + log.debug('testing sequence "%s"' % s) tx_hashes = [] for c in s: if c == 't': @@ -629,17 +671,6 @@ def test_sequence(s): assert test_app.client.call('eth_getFilterChanges', filter_id) == tx_hashes assert test_app.client.call('eth_getFilterChanges', filter_id) == [] - sequences = [ - 't', - 'b', - 'ttt', - 'tbt', - 'ttbttt', - 'bttbtttbt', - 'bttbtttbttbb', - ] - map(test_sequence, sequences) - def test_new_block_filter(test_app): filter_id = test_app.client.call('eth_newBlockFilter') @@ -846,18 +877,23 @@ def test_eth_nonce(test_app): :param test_app: :return: """ - assert test_app.client.call('eth_getTransactionCount', address_encoder(tester.accounts[0])) == '0x0' + assert test_app.client.call( + 'eth_getTransactionCount', address_encoder(tester.accounts[0])) == '0x0' assert ( int(test_app.client.call('eth_nonce', address_encoder(tester.accounts[0])), 16) == test_app.config['eth']['block']['ACCOUNT_INITIAL_NONCE']) - assert test_app.client.call('eth_sendTransaction', dict(sender=address_encoder(tester.accounts[0]), to='')) - assert test_app.client.call('eth_getTransactionCount', address_encoder(tester.accounts[0])) == '0x1' + assert test_app.client.call( + 'eth_sendTransaction', dict(sender=address_encoder(tester.accounts[0]), to='')) + assert test_app.client.call( + 'eth_getTransactionCount', address_encoder(tester.accounts[0])) == '0x1' assert ( int(test_app.client.call('eth_nonce', address_encoder(tester.accounts[0])), 16) == test_app.config['eth']['block']['ACCOUNT_INITIAL_NONCE'] + 1) - assert test_app.client.call('eth_sendTransaction', dict(sender=address_encoder(tester.accounts[0]), to='')) - assert test_app.client.call('eth_getTransactionCount', address_encoder(tester.accounts[0])) == '0x2' + assert test_app.client.call( + 'eth_sendTransaction', dict(sender=address_encoder(tester.accounts[0]), to='')) + assert test_app.client.call( + 'eth_getTransactionCount', address_encoder(tester.accounts[0])) == '0x2' test_app.mine_next_block() assert test_app.services.chain.chain.head.number == 1 assert ( diff --git a/pyethapp/tests/test_pow_service.py b/pyethapp/tests/test_pow_service.py index 680e186d..ae03e97a 100644 --- a/pyethapp/tests/test_pow_service.py +++ b/pyethapp/tests/test_pow_service.py @@ -4,9 +4,9 @@ from devp2p.app import BaseApp from devp2p.service import BaseService from ethereum import slogging -from ethereum.blocks import Block, BlockHeader -from ethereum.config import Env +from ethereum.block import Block, BlockHeader from ethereum.db import DB +from ethereum.transaction_queue import TransactionQueue from pyethapp.pow_service import PoWService @@ -17,24 +17,20 @@ class ChainServiceMock(BaseService): name = 'chain' - class ChainMock: - def __init__(self): - env = Env(DB()) - header = BlockHeader(difficulty=DIFFICULTY) - self.head_candidate = Block(header, env=env) - def __init__(self, app): super(ChainServiceMock, self).__init__(app) - self.on_new_head_candidate_cbs = [] + self.on_new_head_cbs = [] + self.transaction_queue = TransactionQueue() self.is_syncing = False - self.chain = self.ChainMock() self.mined_block = None self.block_mined_event = Event() + self.head_candidate = Block(BlockHeader(difficulty=DIFFICULTY), db=DB()) def add_mined_block(self, block): assert self.mined_block is None self.mined_block = block self.block_mined_event.set() + return True @pytest.fixture @@ -53,7 +49,7 @@ def test_pow_default(app): assert pow assert chain - assert len(chain.on_new_head_candidate_cbs) == 1 + assert len(chain.on_new_head_cbs) == 1 assert not pow.active assert not app.config['pow']['activated'] assert app.config['pow']['cpu_pct'] == 100 diff --git a/pyethapp/tools.py b/pyethapp/tools.py new file mode 100644 index 00000000..2940a722 --- /dev/null +++ b/pyethapp/tools.py @@ -0,0 +1,132 @@ +import os +import sys +import time +import json +import yaml + +from ethereum import utils +from ethereum.state_transition import apply_const_message +from ethereum.casper_utils import RandaoManager, generate_validation_code, make_casper_genesis, casper_config, call_casper +from devp2p.crypto import privtopub + +def generate_data_dirs(num_participants, prefix='v'): + privkeys = [utils.sha3(str(i)) for i in range(num_participants)] + addrs = [utils.privtoaddr(k) for k in privkeys] + genesis = generate_genesis(None, num_participants) + + for i in range(num_participants): + privkey = privkeys[i] + addr = addrs[i] + port = 40000+i + jsonrpc_port = 4000+i + deposit_size = 500 + 500*i + + bootstrap_nodes = 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) + try: + os.stat(dir) + except: + os.mkdir(dir) + genesis_path = dir + '/genesis.json' + config_path = dir + '/config.yaml' + + config = { + "node": { + "privkey_hex": utils.encode_hex(privkey) + }, + "validator": { + "privkey_hex": utils.encode_hex(privkey), + "deposit_size": deposit_size + }, + "eth": { + "genesis": genesis_path, + "network_id": 42 + }, + "p2p": { + "num_peers": num_participants-1, + "listen_host": '0.0.0.0', + "listen_port": port + }, + "discovery": { + "listen_host": '0.0.0.0', + "listen_port": port, + "bootstrap_nodes": bootstrap_nodes + }, + "jsonrpc": { + "listen_host": '0.0.0.0', + "listen_port": jsonrpc_port + } + } + + with open(genesis_path, 'w') as f: + json.dump(genesis, f, sort_keys=False, indent=4, separators=(',', ': ')) + 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 + + +def generate_genesis(path=None, num_participants=1): + privkeys = [utils.sha3(str(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] + + validators = [(generate_validation_code(a), ds * 10**18, r.get(9999), a) for a, ds, r in zip(addrs, deposit_sizes, randaos)] + s = make_casper_genesis(validators=validators, + alloc={a: {'balance': 10**18} for a in addrs}, + timestamp=int(time.time()), + epoch_length=100) + genesis_hash = apply_const_message(s, + sender=casper_config['METROPOLIS_ENTRY_POINT'], + 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]) + + snapshot = s.to_snapshot() + header = s.prev_headers[0] + genesis = { + "nonce": "0x" + utils.encode_hex(header.nonce), + "difficulty": utils.int_to_hex(header.difficulty), + "mixhash": "0x" + utils.encode_hex(header.mixhash), + "coinbase": "0x" + utils.encode_hex(header.coinbase), + "timestamp": utils.int_to_hex(header.timestamp), + "parentHash": "0x" + utils.encode_hex(header.prevhash), + "extraData": "0x" + utils.encode_hex(header.extra_data), + "gasLimit": utils.int_to_hex(header.gas_limit), + "alloc": snapshot["alloc"] + } + + if path: + with open(path, 'w') as f: + json.dump(genesis, f, sort_keys=False, indent=4, separators=(',', ': ')) + 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" + +if __name__ == "__main__": + if len(sys.argv) == 1: + usage() + sys.exit(0) + + if sys.argv[1] == "genesis": + generate_genesis(sys.argv[2], int(sys.argv[3])) + elif sys.argv[1] == "datadir": + generate_data_dirs(int(sys.argv[2])) + else: + print "unknown command: %s" % sys.argv[1] + usage() + sys.exit(1) diff --git a/pyethapp/utils.py b/pyethapp/utils.py index f96080f4..b677195e 100644 --- a/pyethapp/utils.py +++ b/pyethapp/utils.py @@ -7,11 +7,12 @@ import ethereum import gevent from IPython.core import ultratb -from ethereum.blocks import Block, genesis +from ethereum.block import Block from devp2p.service import BaseService import rlp import sys from ethereum import slogging +from ethereum.genesis_helpers import mk_genesis_block from ethereum.slogging import bcolors import types @@ -105,7 +106,8 @@ def load_block_tests(data, db): 'storage': acct_state['storage'] } env = ethereum.config.Env(db=db) - genesis(env, start_alloc=initial_alloc) # builds the state trie + mk_genesis_block(env, start_alloc=initial_alloc) + env.db.commit() genesis_block = rlp.decode(ethereum.utils.decode_hex(data['genesisRLP'][2:]), Block, env=env) blocks = {genesis_block.hash: genesis_block} for blk in data['blocks']: diff --git a/pyethapp/validator_service.py b/pyethapp/validator_service.py new file mode 100644 index 00000000..b693bf1f --- /dev/null +++ b/pyethapp/validator_service.py @@ -0,0 +1,168 @@ +import time +import random +import gevent +from devp2p.service import BaseService +from ethereum.slogging import get_logger +from ethereum.utils import privtoaddr, encode_hex, sha3 +from ethereum.casper_utils import generate_validation_code, call_casper, check_skips, \ + get_timestamp, \ + get_casper_ct, get_dunkle_candidates, sign_block, \ + make_withdrawal_signature, RandaoManager + +log = get_logger('validator') + +BLOCK_TIME = 3 + +global_block_counter = 0 + +casper_ct = get_casper_ct() + +class ValidatorService(BaseService): + + name = 'validator' + default_config = dict(validator=dict( + activated=False, + privkey='', + deposit_size=0, + seed='' + )) + + def __init__(self, app): + super(ValidatorService, self).__init__(app) + + self.config = app.config + + self.chainservice = app.services.chain + self.chain = self.chainservice.chain + self.chain.time = lambda: int(time.time()) + + self.key = self.config['validator']['privkey'] + 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) + + # TODO: allow configure seed? + seed = sha3(self.key) + self.randao = RandaoManager(seed) + + self.received_objects = {} + self.used_parents = {} + + self.next_skip_count = 0 + self.next_skip_timestamp = 0 + self.epoch_length = self.call_casper('getEpochLength') + self.active = False + self.activated = self.app.config['validator']['activated'] + + app.services.chain.on_new_head_cbs.append(self.on_new_head) + self.update_activity_status() + self.cached_head = self.chain.head_hash + + def on_new_head(self, block): + if not self.activated: + return + if self.app.services.chain.is_syncing: + return + self.update() + + def update_activity_status(self): + start_epoch = self.call_casper('getStartEpoch', [self.validation_code_hash]) + now_epoch = self.call_casper('getEpoch') + end_epoch = self.call_casper('getEndEpoch', [self.validation_code_hash]) + if start_epoch <= now_epoch < end_epoch: + self.active = True + self.next_skip_count = 0 + self.next_skip_timestamp = get_timestamp(self.chain, self.next_skip_count) + else: + self.active = False + + def tick(self): + delay = 0 + # Try to create a block + # Conditions: + # (i) you are an active validator, + # (ii) you have not yet made a block with this parent + if self.active and self.chain.head_hash not in self.used_parents: + t = time.time() + # Is it early enough to create the block? + if t >= self.next_skip_timestamp and (not self.chain.head or t > self.chain.head.header.timestamp): + # Wrong validator; in this case, just wait for the next skip count + if not check_skips(self.chain, self.validation_code_hash, self.next_skip_count): + self.next_skip_count += 1 + self.next_skip_timestamp = get_timestamp(self.chain, self.next_skip_count) + log.debug('Not my turn, wait', + next_skip_count=self.next_skip_count, + next_skip_timestamp=self.next_skip_timestamp, + now=int(time.time())) + return + self.used_parents[self.chain.head_hash] = True + blk = self.make_block() + assert blk.timestamp >= self.next_skip_timestamp + if self.chainservice.add_mined_block(blk): + self.received_objects[blk.hash] = True + log.debug('0x%s made and added block %d (%s) to chain' % (encode_hex(self.address[:8]), blk.header.number, encode_hex(blk.header.hash[:8]))) + else: + log.debug('0x%s failed to make and add block %d (%s) to chain' % (encode_hex(self.address[:8]), blk.header.number, encode_hex(blk.header.hash[:8]))) + self.update() + else: + delay = max(self.next_skip_timestamp - t, 0) + # Sometimes we received blocks too early or out of order; + # run an occasional loop that processes these + if random.random() < 0.02: + self.chain.process_time_queue() + self.chain.process_parent_queue() + self.update() + return delay + + def make_block(self): + pre_dunkle_count = self.call_casper('getTotalDunklesIncluded') + dunkle_txs = get_dunkle_candidates(self.chain, self.chain.state) + blk = self.chainservice.head_candidate + randao = self.randao.get_parent(self.call_casper('getRandao', [self.validation_code_hash])) + blk = sign_block(blk, self.key, randao, self.validation_code_hash, self.next_skip_count) + # Make sure it's valid + global global_block_counter + global_block_counter += 1 + for dtx in dunkle_txs: + assert dtx in blk.transactions, (dtx, blk.transactions) + log.debug('made block with timestamp %d and %d dunkles' % (blk.timestamp, len(dunkle_txs))) + return blk + + def update(self): + if self.cached_head == self.chain.head_hash: + return + self.cached_head = self.chain.head_hash + if self.chain.state.block_number % self.epoch_length == 0: + self.update_activity_status() + 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)) + + def withdraw(self, gasprice=20 * 10**9): + sigdata = make_withdrawal_signature(self.key) + txdata = casper_ct.encode('startWithdrawal', [self.validation_code_hash, sigdata]) + tx = Transaction(self.chain.state.get_nonce(self.address), gasprice, 650000, self.chain.config['CASPER_ADDR'], 0, txdata).sign(self.key) + self.chainservice.add_transaction(tx, force=True) + + def deposit(self, gasprice=20 * 10**9): + assert value * 10**18 >= self.chain.state.get_balance(self.address) + gasprice * 1000000 + tx = Transaction(self.chain.state.get_nonce(self.address) * 10**18, gasprice, 1000000, + casper_config['CASPER_ADDR'], value * 10**18, + ct.encode('deposit', [self.validation_code, self.randao.get(9999)])) + + def call_casper(self, fun, args=[]): + return call_casper(self.chain.state, fun, args) + + def _run(self): + while True: + if self.activated: + delay = self.tick() + gevent.sleep(delay) + + def stop(self): + super(ValidatorService, self).stop() + diff --git a/requirements.txt b/requirements.txt index b5fb20ec..6de0682c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,12 +8,14 @@ decorator wheel pyyaml werkzeug -ipython>=3.0.0,<5.0.0 +ipython>=5.4.0,<6.0.0 statistics requests -rlp>=0.4.4 +rlp>=0.5.1,<0.6.0 devp2p>=0.8.0 ethereum>=1.5.1 pbkdf2 scrypt pexpect +pyelliptic==1.5.7 +tinyrpc[gevent,httpclient,jsonext,websocket,wsgi] diff --git a/setup.cfg b/setup.cfg index 510a6cbb..d2cc175a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,6 +2,17 @@ current_version = 1.5.0 commit = True tag = True +parse = (?P\d+)\.(?P\d+)\.(?P\d+)((?P[a-z]+)(?P\d+))? +serialize = + {major}.{minor}.{patch}{release}{num} + {major}.{minor}.{patch} + +[bumpversion:part:release] +optional_value = final +values = + a + rc + final [bumpversion:file:setup.py] search = version = "{current_version}" diff --git a/setup.py b/setup.py index fc947f44..8f6f1c48 100755 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- import codecs +import os from setuptools import setup from setuptools.command.test import test as TestCommand @@ -45,7 +46,17 @@ def run_tests(self): INSTALL_REQUIRES = list(set(INSTALL_REQUIRES)) -# *IMPORTANT*: Don't manually change the version here. Use the 'bumpversion' utility. +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, + ] + +# *IMPORTANT*: Don't manually change the version here. Use the 'bump2version' utility. # see: https://github.com/ethereum/pyethapp/wiki/Development:-Versions-and-Releases version = '1.5.0' @@ -76,8 +87,11 @@ def run_tests(self): ], cmdclass={'test': PyTest}, install_requires=INSTALL_REQUIRES, + dependency_links=DEPENDENCY_LINKS, tests_require=[ 'ethereum-serpent>=1.8.1', + 'mock==2.0.0', + 'pytest-mock==1.6.0', ], entry_points=''' [console_scripts] diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 00000000..b2636c18 --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,24 @@ +name: pyethapp +version: master +summary: python based client implementing the Ethereum cryptoeconomic state machine +description: | + Ethereum as a platform is focussed on enabling people to build new ideas + using blockchain technology. + + The python implementation aims to provide an easily hackable and extendable + codebase. + +grade: devel # must be 'stable' to release into candidate/stable channels +confinement: strict + +apps: + pyethapp: + command: pyethapp + plugs: [network, network-bind] + +parts: + pyethapp: + source: . + plugin: python + python-version: python2 + build-packages: [libssl-dev] \ No newline at end of file diff --git a/tox.ini b/tox.ini index 81763471..5e8bd1a7 100644 --- a/tox.ini +++ b/tox.ini @@ -8,9 +8,7 @@ setenv = deps = -r{toxinidir}/requirements.txt - ethereum-serpent>=1.8.1 - pytest==2.9.1 - coverage==4.0.3 + -r{toxinidir}/dev_requirements.txt commands = coverage run --source pyethapp --branch -m py.test {posargs}