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}