From 3e3bf41e371641e6528145839c91fee15aae5be8 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Fri, 27 Jun 2025 10:47:11 +0200 Subject: [PATCH 01/51] - UP stream and CE128 WIP --- pyjamaz/cli.py | 61 ++- pyjamaz/transport/cert.py | 138 +++++-- pyjamaz/transport/jamnp_s/__init__.py | 0 .../transport/jamnp_s/connection_acceptor.py | 140 +++++++ pyjamaz/transport/jamnp_s/connection_base.py | 19 + .../transport/jamnp_s/connection_initiator.py | 60 +++ pyjamaz/transport/jamnp_s/protocol.py | 132 ++++++ pyjamaz/transport/jamnp_s/stream_0_up.py | 58 +++ .../jamnp_s/stream_128_block_request.py | 31 ++ pyjamaz/transport/jamnp_s/stream_base.py | 90 ++++ pyjamaz/transport/protocol_jamnp_s.py | 386 ------------------ 11 files changed, 674 insertions(+), 441 deletions(-) create mode 100644 pyjamaz/transport/jamnp_s/__init__.py create mode 100644 pyjamaz/transport/jamnp_s/connection_acceptor.py create mode 100644 pyjamaz/transport/jamnp_s/connection_base.py create mode 100644 pyjamaz/transport/jamnp_s/connection_initiator.py create mode 100644 pyjamaz/transport/jamnp_s/protocol.py create mode 100644 pyjamaz/transport/jamnp_s/stream_0_up.py create mode 100644 pyjamaz/transport/jamnp_s/stream_128_block_request.py create mode 100644 pyjamaz/transport/jamnp_s/stream_base.py delete mode 100644 pyjamaz/transport/protocol_jamnp_s.py diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 020cbee3..9a306c9f 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -35,7 +35,7 @@ from pyjamaz.models.state import JamState, ServiceAccount, ServiceActivityRecord from pyjamaz.transport.cert import generate_cert, write_cert from pyjamaz.transport.protocol_fs import FSProtocol -from pyjamaz.transport.protocol_jamnp_s import JAMNPS +from pyjamaz.transport.jamnp_s.protocol import JAMNPS from pyjamaz.transport.pubsub import PubSub, PubSubSignal from pyjamaz.utils import format_hash, quic_peer_id @@ -240,24 +240,48 @@ async def main(ctx, seed, port, ts, culprit, block_dir, record_traces, custom_db # Start WebSocket server tg.start_soon(start_rpc_server, rpc_server) - if block_dir: - logging.info(f"👀 Watching directory: {block_dir} for new blocks...") - fs_protocol = FSProtocol(block_dir, app) - app.protocol = fs_protocol - app.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, wrap_produced_block_fs(app, record_traces, fs_protocol)) - app.pubsub.subscribe(MESSAGE_TYPES.RECEIVED_BLOCK, app.import_block_from_json) - app.pubsub.subscribe(MESSAGE_TYPES.REQUESTED_BLOCKS, app.requested_blocks_from_json) - tg.start_soon(fs_protocol.listen) + # if block_dir: + # logging.info(f"👀 Watching directory: {block_dir} for new blocks...") + # fs_protocol = FSProtocol(block_dir, app) + # app.protocol = fs_protocol + # app.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, wrap_produced_block_fs(app, record_traces, fs_protocol)) + # app.pubsub.subscribe(MESSAGE_TYPES.RECEIVED_BLOCK, app.import_block_from_json) + # app.pubsub.subscribe(MESSAGE_TYPES.REQUESTED_BLOCKS, app.requested_blocks_from_json) + # tg.start_soon(fs_protocol.listen) + # else: + certificate_file = os.path.join(db_path, "cert.pem") + pk_file = os.path.join(db_path, "cert.key") + #nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app) + # (host, port, certificate, private_key, app, initial_slot_nr, initial_block_hash): + initial_block_hash = app.retrieve_block_hash(0).hex() + nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app, 0, initial_block_hash, None) + app.protocol = nps_protocol + app.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, wrap_produced_block_jamnp(app, record_traces, nps_protocol)) + app.pubsub.subscribe(MESSAGE_TYPES.RECEIVED_BLOCK, app.import_block_from_bytes) + app.pubsub.subscribe(MESSAGE_TYPES.REQUESTED_BLOCKS, app.requested_blocks_from_bytes) + tg.start_soon(nps_protocol.listen) + + if bootnode: + #logging.debug(f'Connecting to node {validator_address}:{validator_port}') + #tg.start_soon(nps_protocol.connect, validator_address, validator_port) + + #ecjn4brac2kgu25kiykefww6p6ai7noueo6p5af5tnwjgra4eisya@172.16.238.11:40001 + conn = re.match( + r"^(?P[A-Za-z0-9]+)" + r"@" + r"(?P(?:\d{1,3}\.){3}\d{1,3})" + r":" + r"(?P\d{1,5})$", + bootnode, + ) + + logging.debug(f'Connecting to bootnode {conn["key"]} at {conn["addr"]}:{conn["port"]}') + #tg.start_soon(nps_protocol.connect, conn["addr"], conn["port"]) + try: + await nps_protocol.connect(conn["addr"], conn["port"]) + except Exception as exc: + traceback.print_exc() else: - certificate_file = os.path.join(db_path, "cert.pem") - pk_file = os.path.join(db_path, "cert.key") - nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app) - app.protocol = nps_protocol - app.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, wrap_produced_block_jamnp(app, record_traces, nps_protocol)) - app.pubsub.subscribe(MESSAGE_TYPES.RECEIVED_BLOCK, app.import_block_from_bytes) - app.pubsub.subscribe(MESSAGE_TYPES.REQUESTED_BLOCKS, app.requested_blocks_from_bytes) - tg.start_soon(nps_protocol.listen) - for validator in app.state.safrole.validators: # The validators' IP-layer endpoints are given as IPv6/port combinations, # to be found in the first 18 bytes of validator metadata, with the first 16 bytes being the IPv6 address and @@ -415,7 +439,6 @@ async def init_certificate(db_path, seed): pk_pem, cert_pem = generate_cert( keys, ips="127.0.0.1", #TODO: hardcoded for now - alternative_name="e3r2oc62zwfj3crnuifuvsxvbtlzetk4o5qyhetkhagsc2fgl2oka", ) pk_file = os.path.join(db_path, "cert.key") pem_file = os.path.join(db_path, "cert.pem") diff --git a/pyjamaz/transport/cert.py b/pyjamaz/transport/cert.py index d46e490d..e54db302 100644 --- a/pyjamaz/transport/cert.py +++ b/pyjamaz/transport/cert.py @@ -1,3 +1,72 @@ +# from cryptography import x509 +# from cryptography.x509.oid import ExtendedKeyUsageOID as EKU, NameOID +# from cryptography.hazmat.primitives.asymmetric import ed25519 +# from cryptography.hazmat.primitives import serialization +# from ipaddress import ip_address +# from datetime import datetime, timedelta +# +# +# def generate_cert(keys, ips: str, alternative_name: str): +# priv = ed25519.Ed25519PrivateKey.from_private_bytes(keys.ed25519.private_key) +# +# sans = [x509.IPAddress(ip_address(ip.strip())) +# for ip in ips.split(",") if ip.strip()] +# sans.append(x509.DNSName(alternative_name)) +# +# name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "jamnps-node")]) +# +# cert = ( +# x509.CertificateBuilder() +# .subject_name(name).issuer_name(name) +# .public_key(priv.public_key()) +# .serial_number(x509.random_serial_number()) +# .not_valid_before(datetime.utcnow() - timedelta(minutes=5)) +# .not_valid_after(datetime.utcnow() + timedelta(days=365)) +# # ---- LEAF EXTENSIONS – **none** of them critical ------------------- +# # .add_extension( +# # x509.BasicConstraints(ca=False, path_length=None), critical=False +# # ) +# # .add_extension( +# # x509.KeyUsage( +# # digital_signature=True, +# # key_encipherment=False, +# # content_commitment=False, +# # data_encipherment=False, +# # key_agreement=False, +# # key_cert_sign=False, +# # crl_sign=False, +# # encipher_only=False, +# # decipher_only=False, +# # ), +# # critical=False, +# # ) +# # .add_extension( +# # x509.ExtendedKeyUsage([EKU.SERVER_AUTH]), critical=False +# # ) +# .add_extension( +# x509.SubjectAlternativeName(sans), critical=False +# ) +# .sign(priv, algorithm=None) +# ) +# +# return ( +# priv.private_bytes( +# serialization.Encoding.PEM, +# serialization.PrivateFormat.PKCS8, +# serialization.NoEncryption(), +# ), +# cert.public_bytes(serialization.Encoding.PEM), +# ) +# +# +# def write_cert(pk_pem, pk_file, cert_pem, cert_file): +# +# with open(pk_file, 'wb') as f: +# f.write(pk_pem) +# +# with open(cert_file, 'wb') as f: +# f.write(cert_pem) + from cryptography import x509 from cryptography.x509.oid import ExtendedKeyUsageOID as EKU, NameOID from cryptography.hazmat.primitives.asymmetric import ed25519 @@ -5,50 +74,47 @@ from ipaddress import ip_address from datetime import datetime, timedelta +from cryptography import x509 +from cryptography.x509.oid import NameOID -def generate_cert(keys, ips: str, alternative_name: str): - priv = ed25519.Ed25519PrivateKey.from_private_bytes(keys.ed25519.private_key) +from pyjamaz.utils import quic_peer_id - sans = [x509.IPAddress(ip_address(ip.strip())) - for ip in ips.split(",") if ip.strip()] - sans.append(x509.DNSName(alternative_name)) - name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "jamnps-node")]) +def generate_cert(keys, ips: str): + priv = ed25519.Ed25519PrivateKey.from_private_bytes(keys.ed25519.private_key) - cert = ( + subject = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"rcgen self signed cert"), + ]) + issuer = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"rcgen self signed cert"), + ]) + serial_numer = 76354585232599687591316365089471291226684720524 + not_valid_before = datetime(1975, 1, 1, 0, 0) + not_valid_after = datetime(4096, 1, 1, 0, 0) + peer_id = quic_peer_id(keys.ed25519.public_key) + #print(peer_id) + ext = x509.SubjectAlternativeName(x509.GeneralNames([x509.DNSName(peer_id)])) + + builder = ( x509.CertificateBuilder() - .subject_name(name).issuer_name(name) + .subject_name(subject) + .issuer_name(issuer) .public_key(priv.public_key()) - .serial_number(x509.random_serial_number()) - .not_valid_before(datetime.utcnow() - timedelta(minutes=5)) - .not_valid_after(datetime.utcnow() + timedelta(days=365)) - # ---- LEAF EXTENSIONS – **none** of them critical ------------------- - # .add_extension( - # x509.BasicConstraints(ca=False, path_length=None), critical=False - # ) - # .add_extension( - # x509.KeyUsage( - # digital_signature=True, - # key_encipherment=False, - # content_commitment=False, - # data_encipherment=False, - # key_agreement=False, - # key_cert_sign=False, - # crl_sign=False, - # encipher_only=False, - # decipher_only=False, - # ), - # critical=False, - # ) - # .add_extension( - # x509.ExtendedKeyUsage([EKU.SERVER_AUTH]), critical=False - # ) - .add_extension( - x509.SubjectAlternativeName(sans), critical=False - ) - .sign(priv, algorithm=None) + .serial_number(serial_numer) + .not_valid_before(not_valid_before) + .not_valid_after(not_valid_after) + .add_extension(x509.SubjectAlternativeName( + x509.GeneralNames(ext)), critical=False) ) + cert = builder.sign(private_key=priv, algorithm=None) # Note: Ed25519 = algorithm None + + #der_bytes = cert.public_bytes(serialization.Encoding.DER) + #pem_bytes = cert.public_bytes(serialization.Encoding.PEM) + # with open("clone2.pem", "wb") as f: + # f.write(pem_bytes) + return ( priv.private_bytes( serialization.Encoding.PEM, diff --git a/pyjamaz/transport/jamnp_s/__init__.py b/pyjamaz/transport/jamnp_s/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyjamaz/transport/jamnp_s/connection_acceptor.py b/pyjamaz/transport/jamnp_s/connection_acceptor.py new file mode 100644 index 00000000..dc376b09 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/connection_acceptor.py @@ -0,0 +1,140 @@ +import logging + +from aioquic.quic.events import QuicEvent, StreamDataReceived, ConnectionTerminated, HandshakeCompleted + +from jamcodec.base import JamBytes +from jamcodec.types import Vec + +from pyjamaz.models.block import Block +from pyjamaz.transport.jamnp_s.connection_base import ConnectionBase + + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class ConnectionAcceptor(ConnectionBase): + + # #TODO: deprecated!!!!!! + # async def send_block_announcement(self, block_bytes): + # if self.stream_up is None: + # raise Exception("NO UP 0 block_announcement channel opend yet??") + # + # """ + # TODO: + # For now we only send length++, we should send: + # Final = Header Hash ++ Slot + # Leaf = Header Hash ++ Slot + # Handshake = Final ++ len++[Leaf] + # Announcement = Header ++ Final + # """ + # self._quic.send_stream_data( + # self.stream_up, + # (int(StreamType.UP0_BlockAnnouncement.value).to_bytes(length=1, byteorder='little') + + # len(block_bytes).to_bytes(length=4, byteorder='little') + + # block_bytes) + # ) + # self.transmit() + # logger.debug(f"ServerProtocol Block announcement sent to stream {self.stream_up} ({len(block_bytes)})") + + def quic_event_received(self, event: QuicEvent): + # if isinstance(event, HandshakeCompleted): + # # TODO: check client certificate and alpn + # # if self._quic.configuration.alpn_protocols[0] != "jamnp-s/0/00000000": + # # self._quic.close() + # # return + # + # self.client_id = id(self) + # self.protocol.conn_in[self.client_id] = self # Store reference for broadcasting + # + # logger.debug(f'ServerProtocol new connected client #{self.client_id}') + # + # #TODO: remove connections on connection closed/lost etc + # + # elif isinstance(event, StreamDataReceived): + # logger.debug(f'Server received data: {event.data}') + # + # if self.stream_up is None: + # self.stream_up = event.stream_id + # logger.debug(f'ServerProtocol new UP-0 stream ({self.stream_up}) for client #{self.client_id}') + # + # # if event.stream_id == self.stream_up: + # # # Process incoming data (either handshake or announcement) + # # logger.debug(f'ServerProtocol new UP-0 stream ({self.stream_up}) for client #{self.client_id}') + # + # byte_data = bytes(event.data) + # bytes_left = byte_data + # + # # Note: Parse bytes until stream data is empty: https://github.com/microsoft/msquic/discussions/2037 + # while len(bytes_left) > 0: + # + # #TODO: do this per channel + # if not self._msg_buffer: + # # Note: first message always contains expected message type & length + # # TODO: kunnen msg_type en msg_len ook opgesplitst zijn in 2 messages?? gaan nu uit dat dit atomair is + # self._msg_type = int.from_bytes(byte_data[0:1], byteorder='little') + # self._msg_offset = 5 + # self._msg_len = int.from_bytes(byte_data[1:5], byteorder='little') + self._msg_offset + # logger.debug(f'ServerProtocol new message {self._msg_type} ({self._msg_len} bytes)') + # + # nr_bytes_remaining = self._msg_len-len(self._msg_buffer) + # if nr_bytes_remaining > 0: + # self._msg_buffer += bytes_left[:nr_bytes_remaining] + # bytes_left = bytes_left[nr_bytes_remaining:] + # else: + # bytes_left = bytes() + # + # # If we assembled a new message, parse it + # if 0 < self._msg_len == len(self._msg_buffer): + # + # match self._msg_type: + # + # case StreamType.UP0_OPEN.value: + # logger.debug(f'ServerProtocol PARSED UP-0') + # self._reset_msg() + # + # case StreamType.CE128_BlockRequest.value: + # logger.debug(f'ServerProtocol RECEIVED NEW BLOCKSREQUEST') + # direction = 1#self._msg_buffer[self._msg_offset:self._msg_offset+1] + # max_blocks = 1000 #self._msg_buffer[self._msg_offset+1:self._msg_offset+1+4] + # block = Block.from_jam_bytes(JamBytes(self._msg_buffer[self._msg_offset:self._msg_len])) + # + # logger.debug( + # f"ServerProtocol Block Requests received {self.stream_up} direction: {direction}, max_blocks: {max_blocks}, block: {block.header.timeslot}") + # + # blocks = [] + # #TODO: take direction and max_blocks into account + # #TODO: we decode and serialize blocks unnecessary here, improve! + # #TODO: check the max blocks and a hardcoded max of X + # while block.header.parent != bytes(32): + # block = self.protocol.app.retrieve_block_by_hash(block.header.parent) + # if not block: + # break + # blocks.append(block) + # + # block_list = Vec(Block.to_codec_def()).new() + # #TODO: optimize!! :S + # serialized_blocks = block_list.encode([b.to_json() for b in blocks]) + # + # logger.debug( + # f"ServerProtocol Block Requests sending {len(blocks)} blocks") + # + # self._quic.send_stream_data( + # self.stream_up, + # (int(StreamType.CE128_BlockRequest.value).to_bytes(length=1, byteorder='little') + + # len(serialized_blocks).to_bytes(length=4, byteorder='little') + + # serialized_blocks.to_bytes()) + # ) + # self.transmit() + # self._reset_msg() + # + # case _: + # raise InvalidStreamType(f"Invalid JAMNPS message: {self._msg_type}") + # + # elif isinstance(event, ConnectionTerminated): + # # Handle connection termination + # if id(self) in self.protocol.conn_in: + # logger.debug(f'Client #{self.client_id} disconnected') + # # print(f'Client #{self.client_id} disconnected') + # del self.protocol.conn_in[id(self)] + pass + diff --git a/pyjamaz/transport/jamnp_s/connection_base.py b/pyjamaz/transport/jamnp_s/connection_base.py new file mode 100644 index 00000000..59eb3021 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/connection_base.py @@ -0,0 +1,19 @@ +import logging +from enum import Enum + +from aioquic.asyncio import QuicConnectionProtocol, serve + + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + + +class ConnectionBase(QuicConnectionProtocol): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.protocol = None # Note: should be set in wrap_protocol + + self.stream_up_id = None + self.streams = {} diff --git a/pyjamaz/transport/jamnp_s/connection_initiator.py b/pyjamaz/transport/jamnp_s/connection_initiator.py new file mode 100644 index 00000000..2d90f8a1 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/connection_initiator.py @@ -0,0 +1,60 @@ +import asyncio +import logging +import struct +from typing import List + +from aioquic.quic.events import QuicEvent, StreamDataReceived, ConnectionTerminated, HandshakeCompleted + +from pyjamaz.transport.jamnp_s.connection_base import ConnectionBase +from pyjamaz.transport.jamnp_s.stream_0_up import StreamUP + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class ConnectionInitiator(ConnectionBase): + + + def quic_event_received(self, event: QuicEvent) -> None: + logger.debug(f'ProtocolInitiator received data {event}') + + if isinstance(event, HandshakeCompleted): + #TODO: + # Both nodes are validators, and are neighbours in the grid structure. + # At least one of the nodes is not a validator. + if self.stream_up_id is not None: + raise Exception("There can be only one UP connection active at a time") + + self.stream_up_id = self._quic.get_next_available_stream_id() + self.streams[self.stream_up_id] = StreamUP(self.stream_up_id, self) + self.streams[self.stream_up_id].send_handshake() + + elif isinstance(event, StreamDataReceived): + + stream_id = event.stream_id + + if stream_id not in self.streams: + raise Exception(f"Stream {stream_id} not available") + + self.streams[stream_id].receive_data(bytes(event.data)) + + + # TODO: handle gracefully + # elif isinstance(event, ConnectionTerminated): + # # Handle connection termination + + + async def send_blocks_request(self, direction, max_blocks, block_bytes): + #!!!!!!!!!!!!TODO: moet over een nieuwe stream -> creeer een nieuwe stream + # data = ( + # # int(direction).to_bytes(length=1, byteorder='little') + + # # int(max_blocks).to_bytes(length=1, byteorder='little') + + # block_bytes + # ) + # self._quic.send_stream_data( + # self.stream_up, + # (int(StreamType.CE128_BlockRequest.value).to_bytes(length=1, byteorder='little') + + # len(data).to_bytes(length=4, byteorder='little') + + # data) + # ) + # self.transmit() + logger.debug(f"ClientProtocol Block Requests sent to stream {self.stream_up_id} ({len(data)})") diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py new file mode 100644 index 00000000..866023f3 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -0,0 +1,132 @@ +import logging +import ssl + +from typing import Dict +from typing import Optional, cast + +from aioquic.asyncio import serve +from aioquic.quic.configuration import QuicConfiguration +from aioquic.tls import SessionTicket + +from aioquic.asyncio.client import connect + +from pyjamaz.transport.jamnp_s.connection_initiator import ConnectionInitiator +from pyjamaz.transport.jamnp_s.connection_acceptor import ConnectionAcceptor +from pyjamaz.transport.types import ProtocolType + + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +def wrap_protocol(wrapper, protocol): + def create_protocol(*args, **kwargs): + instance = protocol(*args, **kwargs) + instance.protocol = wrapper + return instance + + return create_protocol + + +class SessionTicketStore: + + def __init__(self) -> None: + self.tickets: Dict[bytes, SessionTicket] = {} + + def add(self, ticket: SessionTicket) -> None: + self.tickets[ticket.ticket] = ticket + + def pop(self, label: bytes) -> Optional[SessionTicket]: + return self.tickets.pop(label, None) + + +class JAMNPS(ProtocolType): + + PROTOCOL_NAME = "jamnp-s/0/{}" + + #TODO: add role enum: validator, guarantors, light_client, builder, ... + def __init__(self, host, port, certificate, private_key, app, initial_slot_nr, initial_block_hash, role): + self.host = host + self.port = port + self.pubsub = app.pubsub + self.app = app + + #################TODO: haal deze info uit app ipv arg?? + self.first_block_hash = initial_block_hash + self.first_slot = initial_slot_nr + bl_hash = initial_block_hash + if bl_hash.startswith('0x'): bl_hash = bl_hash[2:] + bl_hash = bl_hash[:8] + ################## + + self.protocol_name = JAMNPS.PROTOCOL_NAME.format(bl_hash) + self.session_ticket_store = SessionTicketStore() + self.configuration = QuicConfiguration( + alpn_protocols=[self.protocol_name], + is_client=True, + #quic_logger=quic_logger, + #verify_mode=ssl.CERT_REQUIRED, + verify_mode=ssl.CERT_NONE, + idle_timeout=300000 + ) + self.cert = certificate + self.pk = private_key + self.configuration.load_cert_chain(certificate, private_key) + self.conn_in = {} # All incomming connections + self.conn_out = {} # All outgoing connections (who we connect to) + + + async def listen(self): + logger.debug(f'Listening on {self.host}:{self.port}') + await serve( + self.host, + self.port, + configuration=self.configuration, + create_protocol=wrap_protocol(self, ConnectionAcceptor), + session_ticket_fetcher=self.session_ticket_store.pop, + session_ticket_handler=self.session_ticket_store.add, + retry=True, + ) + + async def connect(self, host, port): + configuration = QuicConfiguration( + alpn_protocols=[self.protocol_name], + is_client=True, + #verify_mode=ssl.CERT_REQUIRED, + verify_mode=ssl.CERT_NONE, + idle_timeout=300000 + ) + configuration.load_cert_chain(certfile=self.cert, keyfile=self.pk) + + logger.debug(f"ClientProtocol Connecting to {host}:{port}") + try: + async with connect( + host, + port, + configuration=configuration, + # session_ticket_handler=save_session_ticket, + create_protocol=wrap_protocol(self, ConnectionInitiator), + ) as client: + client = cast(ConnectionInitiator, client) + self.conn_out[(host, port)] = client + #await client.open_stream_up() + await client.wait_closed() + del self.conn_out[(host, port)] + except ConnectionError as exc: + if (host, port) in self.conn_out: + del self.conn_out[(host, port)] + logger.warning(f"💩 ClientProtocol Cannot connect to {host}:{port} {exc}") + + + async def request_blocks(self, direction, max_blocks, block_bytes): + #TODO: temp hack, should be provided with a specific peer? + conn_key = list(self.conn_out.keys())[0] + conn = self.conn_out[conn_key] + await conn.send_blocks_request(0, 100, block_bytes) + + + async def broadcast_block(self, block): + block_bytes = block.to_jam_bytes().to_bytes() + logger.debug(f'ServerProtocol broadcasting block announcement to {len(self.conn_in)} clients') + for client_id, client in self.conn_in.items(): + logger.debug(f"ServerProtocol send block to client {client}") + await client.send_block_announcement(block_bytes) diff --git a/pyjamaz/transport/jamnp_s/stream_0_up.py b/pyjamaz/transport/jamnp_s/stream_0_up.py new file mode 100644 index 00000000..e671d556 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/stream_0_up.py @@ -0,0 +1,58 @@ +from jamcodec.base import JamBytes +from jamcodec.types import U32, VarInt64 + +from pyjamaz.models.block import Header +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType + + +class StreamUP(Stream): + + def __init__(self, stream_id: int, connection): + super().__init__(stream_id, connection) + self.stream_type = StreamType.UP0_BlockAnnouncement.value.to_bytes(length=1, byteorder='little') + self.handshake_complete = False + + def send_handshake(self): + """ + TODO: + Both sides should begin by sending a handshake message containing all known leaves (descendants of the latest finalized block with no known children). + The header hash and slot of the latest finalized block should be included in the handshake message and also in every announcement message that is sent. + """ + final = bytes.fromhex(self.conn.protocol.first_block_hash) + self.conn.protocol.first_slot.to_bytes(length=4, byteorder='little') + leafs = bytes() # [bytes.fromhex(h) + s.to_bytes(length=4, byteorder='little') for h, s in []] #TODO: vraag app naar leafs vanaf first_block + leaf_count = VarInt64.encode(len(leafs)).to_bytes() + handshake = final + leaf_count + leafs + + self.conn._quic.send_stream_data( + self.stream_id, + self.stream_type + (len(handshake)).to_bytes(length=4, byteorder='little') + handshake, + ) + + + def parse_message(self, data: bytes): + print(f"RECEIVED UP0 MSG: {len(data)}") + + if not self.handshake_complete: + header_hash = data[:32].hex() + slot = int.from_bytes(data[32:36], byteorder='little') + + jam_bytes = JamBytes(data[36:]) + leaf_count = VarInt64.decode(jam_bytes) + + for leaf_nr in range(leaf_count): + leaf_hash = jam_bytes.get_next_bytes(32).hex() + leaf_slot = U32.decode(jam_bytes) + print(f"leaf: {leaf_hash} {leaf_slot}") + + print(f"HANDSHAKE: {header_hash} {slot} {leaf_count} remaining: {jam_bytes.get_remaining_length()}") + self.handshake_complete = True + + # TODO: temp adhoc 128 stream, send notification instead!!!!! + from pyjamaz.transport.jamnp_s.stream_128_block_request import StreamBlockRequest + stream_id = self.conn._quic.get_next_available_stream_id() + self.conn.streams[stream_id] = StreamBlockRequest(stream_id, self.conn) + self.conn.streams[stream_id].send_block_request(header_hash, 1, 1) + + return + + #test = Header.from_jam_bytes(JamBytes(data)) diff --git a/pyjamaz/transport/jamnp_s/stream_128_block_request.py b/pyjamaz/transport/jamnp_s/stream_128_block_request.py new file mode 100644 index 00000000..f57a297e --- /dev/null +++ b/pyjamaz/transport/jamnp_s/stream_128_block_request.py @@ -0,0 +1,31 @@ +from jamcodec.base import JamBytes +from jamcodec.types import U32, VarInt64 + +from pyjamaz.models.block import Header +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType + + +class StreamBlockRequest(Stream): + + def __init__(self, stream_id: int, connection): + super().__init__(stream_id, connection) + self.stream_type = StreamType.CE128_BlockRequest.value.to_bytes(length=1, byteorder='little') + + + def send_block_request(self, header_hash, direction, max_blocks): + header_hash = bytes.fromhex(header_hash) + direction = direction.to_bytes(length=1, byteorder='little') + max_blocks = max_blocks.to_bytes(length=4, byteorder='little') + payload = header_hash + direction + max_blocks + + print("SENDING BLOCK REQUEST!!!!!!!") + self.conn._quic.send_stream_data( + self.stream_id, + self.stream_type + (len(payload)).to_bytes(length=4, byteorder='little') + payload, + end_stream=True + ) + + + def parse_message(self, data: bytes): + print(f"RECEIVED CE128 MSG: {len(data)}") + #test = Header.from_jam_bytes(JamBytes(data)) diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py new file mode 100644 index 00000000..71cc22c4 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -0,0 +1,90 @@ +import logging +from enum import Enum + + +logger = logging.getLogger("pyjamaz.transport.jamnp_s.streams") + + +class InvalidStreamType(Exception): + pass + + +class StreamType(Enum): + UP0_BlockAnnouncement: int = 0 + CE128_BlockRequest: int = 128 + # 129 + # 131 + # 132 + # 133 + # 134 + # 135 + # 136 + # 137 + # 138 + # 139 + # 140 + # 141 + # 142 + # 143 + # 144 + # 145 + + +class Stream: + + #TODO: typings, and is it really necesary to pass these here? + def __init__(self, stream_id: int, connection): + self.stream_id = stream_id + self.stream_type = None # Note: override in subclass + self.conn = connection + + self._msg_buffer = b"" + self._msg_len = -1 + self._msg_type = -1 + self._msg_offset = -1 + + + def _reset_msg(self): + self._msg_buffer = b"" + self._msg_len = -1 + self._msg_type = -1 + self._msg_offset = -1 + + + def parse_message(self, data: bytes): + raise Exception("Implement this method") + + + def receive_data(self, data: bytes): + + # Note: Parse bytes until stream data is empty: https://github.com/microsoft/msquic/discussions/2037 + while len(data) > 0: + + if self._msg_len == -1: + # byte_data = bytes(event.data) + self._msg_len = int.from_bytes(data[0:4], byteorder='little') + data = data[4:] + print(f"NEW MESSAGE: {self.stream_id} msg length: {len(data)} data length: {self._msg_len}") + # TODO: check message length > 0???!!!! + + + parse_message = False + if len(self._msg_buffer) + len(data) >= self._msg_len: + # If we received a full message (or more than 1 message) + end_offset = self._msg_len - len(self._msg_buffer) + self._msg_buffer += data[:end_offset] + data = data[end_offset:] + parse_message = True + print("FULL MESSAGE RECEIVED") + else: + # Otherwise append only, we expect more data to finish this message + self._msg_buffer += data + data = [] + print(f"APPENDING TO EXISTING MESSAGE: {self.stream_id} msg length: {len(data)} data length: {self._msg_len}") + + # If we assembled a new message, parse it + if parse_message: + try: + self.parse_message(self._msg_buffer) + finally: + self._reset_msg() diff --git a/pyjamaz/transport/protocol_jamnp_s.py b/pyjamaz/transport/protocol_jamnp_s.py deleted file mode 100644 index e36ad7ad..00000000 --- a/pyjamaz/transport/protocol_jamnp_s.py +++ /dev/null @@ -1,386 +0,0 @@ -import logging -import ssl -from doctest import debug -from enum import Enum - -from typing import Dict, Optional -from typing import Optional, cast - -from aioquic.asyncio import QuicConnectionProtocol, serve -from aioquic.quic.configuration import QuicConfiguration -from aioquic.quic.events import QuicEvent, StreamDataReceived, ConnectionTerminated, HandshakeCompleted -from aioquic.quic.logger import QuicFileLogger -from aioquic.tls import SessionTicket - -from aioquic.asyncio.client import connect -from jamcodec.base import JamBytes -from jamcodec.types import Vec - -from pyjamaz.constants import MESSAGE_TYPES -from pyjamaz.models.block import Block -from pyjamaz.transport.pubsub import PubSubSignal -from pyjamaz.transport.types import ProtocolType - - -logger = logging.getLogger("pyjamaz.transport") - - -def wrap_protocol(wrapper, protocol): - def create_protocol(*args, **kwargs): - instance = protocol(*args, **kwargs) - instance.wrapper = wrapper - return instance - - return create_protocol - - -class InvalidJAMNPSMessage(Exception): - pass - - -class JAMNPSProtocol(QuicConnectionProtocol): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.wrapper = None # Note: should be set in wrap_protocol - self.stream_up_0 = None - #TODO: this buffer should be made per stream_id (for now, we always assume stream_up_0) - self._msg_buffer = b"" - self._msg_len = -1 - self._msg_type = -1 - self._msg_offset = -1 - - def _reset_msg(self): - self._msg_buffer = b"" - self._msg_len = -1 - self._msg_type = -1 - self._msg_offset = -1 - - def build_handshake_message(self): - #TODO: implement handshake response according to JAMSNP - """Both sides should begin by sending a handshake message containing all known leaves (descendants of the latest finalized block with no known children).""" - logger.debug(f"Building handshake message for UP-0 stream") - return ( - int(JAMNPS.MSG.UP0_OPEN.value).to_bytes( - length=1, - byteorder='little' - ) + - int(0).to_bytes( - length=4, - byteorder='little' - ) - ) - - -class ServerProtocol(JAMNPSProtocol): - - async def send_block_announcement(self, block_bytes): - if self.stream_up_0 is None: - raise Exception("NO UP 0 block_announcement channel opend yet??") - - """ - TODO: - For now we only send length++, we should send: - Final = Header Hash ++ Slot - Leaf = Header Hash ++ Slot - Handshake = Final ++ len++[Leaf] - Announcement = Header ++ Final - """ - self._quic.send_stream_data( - self.stream_up_0, - (int(JAMNPS.MSG.UP0_BlockAnnouncement.value).to_bytes(length=1, byteorder='little') + - len(block_bytes).to_bytes(length=4, byteorder='little') + - block_bytes) - ) - self.transmit() - logger.debug(f"ServerProtocol Block announcement sent to stream {self.stream_up_0} ({len(block_bytes)})") - - def quic_event_received(self, event: QuicEvent): - if isinstance(event, HandshakeCompleted): - # TODO: check client certificate and alpn - # if self._quic.configuration.alpn_protocols[0] != "jamnp-s/0/00000000": - # self._quic.close() - # return - - self.client_id = id(self) - self.wrapper.conn_in[self.client_id] = self # Store reference for broadcasting - - logger.debug(f'ServerProtocol new connected client #{self.client_id}') - - #TODO: remove connections on connection closed/lost etc - - elif isinstance(event, StreamDataReceived): - logger.debug(f'Server received data: {event.data}') - - if self.stream_up_0 is None: - self.stream_up_0 = event.stream_id - logger.debug(f'ServerProtocol new UP-0 stream ({self.stream_up_0}) for client #{self.client_id}') - - # if event.stream_id == self.stream_up_0: - # # Process incoming data (either handshake or announcement) - # logger.debug(f'ServerProtocol new UP-0 stream ({self.stream_up_0}) for client #{self.client_id}') - - byte_data = bytes(event.data) - bytes_left = byte_data - - # Note: Parse bytes until stream data is empty: https://github.com/microsoft/msquic/discussions/2037 - while len(bytes_left) > 0: - - #TODO: do this per channel - if not self._msg_buffer: - # Note: first message always contains expected message type & length - # TODO: kunnen msg_type en msg_len ook opgesplitst zijn in 2 messages?? gaan nu uit dat dit atomair is - self._msg_type = int.from_bytes(byte_data[0:1], byteorder='little') - self._msg_offset = 5 - self._msg_len = int.from_bytes(byte_data[1:5], byteorder='little') + self._msg_offset - logger.debug(f'ServerProtocol new message {self._msg_type} ({self._msg_len} bytes)') - - nr_bytes_remaining = self._msg_len-len(self._msg_buffer) - if nr_bytes_remaining > 0: - self._msg_buffer += bytes_left[:nr_bytes_remaining] - bytes_left = bytes_left[nr_bytes_remaining:] - else: - bytes_left = bytes() - - # If we assembled a new message, parse it - if 0 < self._msg_len == len(self._msg_buffer): - - match self._msg_type: - - case JAMNPS.MSG.UP0_OPEN.value: - logger.debug(f'ServerProtocol PARSED UP-0') - self._reset_msg() - - case JAMNPS.MSG.CE128_BlockRequest.value: - logger.debug(f'ServerProtocol RECEIVED NEW BLOCKSREQUEST') - direction = 1#self._msg_buffer[self._msg_offset:self._msg_offset+1] - max_blocks = 1000 #self._msg_buffer[self._msg_offset+1:self._msg_offset+1+4] - block = Block.from_jam_bytes(JamBytes(self._msg_buffer[self._msg_offset:self._msg_len])) - - logger.debug( - f"ServerProtocol Block Requests received {self.stream_up_0} direction: {direction}, max_blocks: {max_blocks}, block: {block.header.timeslot}") - - blocks = [] - #TODO: take direction and max_blocks into account - #TODO: we decode and serialize blocks unnecessary here, improve! - #TODO: check the max blocks and a hardcoded max of X - while block.header.parent != bytes(32): - block = self.wrapper.app.retrieve_block_by_hash(block.header.parent) - if not block: - break - blocks.append(block) - - block_list = Vec(Block.to_codec_def()).new() - #TODO: optimize!! :S - serialized_blocks = block_list.encode([b.to_json() for b in blocks]) - - logger.debug( - f"ServerProtocol Block Requests sending {len(blocks)} blocks") - - self._quic.send_stream_data( - self.stream_up_0, - (int(JAMNPS.MSG.CE128_BlockRequest.value).to_bytes(length=1, byteorder='little') + - len(serialized_blocks).to_bytes(length=4, byteorder='little') + - serialized_blocks.to_bytes()) - ) - self.transmit() - self._reset_msg() - - case _: - raise InvalidJAMNPSMessage(f"Invalid JAMNPS message: {self._msg_type}") - - elif isinstance(event, ConnectionTerminated): - # Handle connection termination - if id(self) in self.wrapper.conn_in: - logger.debug(f'Client #{self.client_id} disconnected') - # print(f'Client #{self.client_id} disconnected') - del self.wrapper.conn_in[id(self)] - - -class ClientProtocol(JAMNPSProtocol): - - async def send_blocks_request(self, direction, max_blocks, block_bytes): - #TODO: moet over een nieuwe stream/connectie?? misbruiken voor nu de up0 stream - data = ( - # int(direction).to_bytes(length=1, byteorder='little') + - # int(max_blocks).to_bytes(length=1, byteorder='little') + - block_bytes - ) - self._quic.send_stream_data( - self.stream_up_0, - (int(JAMNPS.MSG.CE128_BlockRequest.value).to_bytes(length=1, byteorder='little') + - len(data).to_bytes(length=4, byteorder='little') + - data) - ) - self.transmit() - logger.debug(f"ClientProtocol Block Requests sent to stream {self.stream_up_0} ({len(data)})") - - - def quic_event_received(self, event: QuicEvent) -> None: - logger.debug(f'ClientProtocol received data') - - if isinstance(event, StreamDataReceived): - - #TODO: for now we only support 1 stream (UP-0) - #stream_id = event.stream_id - #stream = self._get_or_create_stream(stream_id) - - byte_data = bytes(event.data) - bytes_left = byte_data - - # Note: Parse bytes until stream data is empty: https://github.com/microsoft/msquic/discussions/2037 - while len(bytes_left) > 0: - - #TODO: do this per channel - if not self._msg_buffer: - # Note: first message always contains expected message type & length - self._msg_type = int.from_bytes(byte_data[0:1], byteorder='little') - self._msg_offset = 5 - self._msg_len = int.from_bytes(byte_data[1:5], byteorder='little') + self._msg_offset - logger.debug(f'ClientProtocol new message {self._msg_type} (received {len(bytes_left)-5} of {self._msg_len} bytes)') - - nr_bytes_remaining = self._msg_len-len(self._msg_buffer) - self._msg_buffer += bytes_left[:nr_bytes_remaining] - bytes_left = bytes_left[nr_bytes_remaining:] - - # If we assembled a new message, parse it - if 0 < self._msg_len == len(self._msg_buffer): - - try: - match self._msg_type: - - case JAMNPS.MSG.UP0_BlockAnnouncement.value: - logger.debug(f'ClientProtocol RECEIVED_BLOCK: {self._msg_len}') - self.wrapper.broadcaster.send_stream.send_nowait(PubSubSignal(topic=MESSAGE_TYPES.RECEIVED_BLOCK, data=self._msg_buffer[self._msg_offset:self._msg_len])) - # self.wrapper.broadcaster.send_stream.send_nowait({ - # "message_type": MESSAGE_TYPES.RECEIVED_BLOCK, - # "data": self._msg_buffer[self._msg_offset:self._msg_len] - # }) - - case JAMNPS.MSG.CE128_BlockRequest.value: - logger.debug(f'ClientProtocol RECEIVED REQUESTED BLOCKS: {self._msg_len}') - self.wrapper.broadcaster.send_stream.send_nowait(PubSubSignal(topic=MESSAGE_TYPES.REQUESTED_BLOCKS, - data=self._msg_buffer[self._msg_offset:self._msg_len])) - # self.wrapper.broadcaster.send_stream.send_nowait({ - # "message_type": MESSAGE_TYPES.REQUESTED_BLOCKS, - # "data": self._msg_buffer[self._msg_offset:self._msg_len] - # }) - - case _: - raise InvalidJAMNPSMessage(f"Invalid JAMNPS message: {self._msg_type}") - finally: - self._reset_msg() - - # TODO: handle gracefully - # elif isinstance(event, ConnectionTerminated): - # # Handle connection termination - - async def open_stream_up_0(self): - # Initiate UP 0 stream by sending the - self.stream_up_0 = self._quic.get_next_available_stream_id() - self._quic.send_stream_data( - self.stream_up_0, - self.build_handshake_message(), - ) - logger.debug(f'ClientProtocol Block announcement stream opened') - - -class SessionTicketStore: - - def __init__(self) -> None: - self.tickets: Dict[bytes, SessionTicket] = {} - - def add(self, ticket: SessionTicket) -> None: - self.tickets[ticket.ticket] = ticket - - def pop(self, label: bytes) -> Optional[SessionTicket]: - return self.tickets.pop(label, None) - - -class JAMNPS(ProtocolType): - - class MSG(Enum): - UP0_OPEN: int = 66 - UP0_BlockAnnouncement: int = 0 - CE128_BlockRequest: int = 128 - - #TODO: 00000000 -> vervang met de eerste 8 nibbles vd genesis header hash op __init__ - PROTOCOL_NAME = "jamnp-s/0/00000000" - - # (host, port, certificate_file, pk_file, app, 0, "0259fbe9b7dd6f3ce7d7027d87453e886bf39ea902b7bae59af1d3c63b2db4ec") - def __init__(self, host, port, certificate, private_key, app): - self.host = host - self.port = port - self.broadcaster = app.pubsub - self.app = app - self.session_ticket_store = SessionTicketStore() - self.configuration = QuicConfiguration( - alpn_protocols=[JAMNPS.PROTOCOL_NAME], - is_client=False, - #quic_logger=quic_logger, - #verify_mode=ssl.CERT_REQUIRED - verify_mode=ssl.CERT_NONE, - idle_timeout=300000 - ) - self.cert = certificate - self.pk = private_key - self.configuration.load_cert_chain(certificate, private_key) - self.conn_in = {} # All incomming connections - self.conn_out = {} # All outgoing connections (who we connect to) - - - async def listen(self): - logger.debug(f'Listening on {self.host}:{self.port}') - await serve( - self.host, - self.port, - configuration=self.configuration, - create_protocol=wrap_protocol(self, ServerProtocol), - session_ticket_fetcher=self.session_ticket_store.pop, - session_ticket_handler=self.session_ticket_store.add, - retry=True, - ) - - async def connect(self, host, port): - configuration = QuicConfiguration( - alpn_protocols=[JAMNPS.PROTOCOL_NAME], - is_client=True, - #verify_mode=ssl.CERT_REQUIRED - verify_mode=ssl.CERT_NONE, - idle_timeout=300000 - ) - configuration.load_cert_chain(certfile=self.cert, keyfile=self.pk) - - logger.debug(f"ClientProtocol Connecting to {host}:{port}") - try: - async with connect( - host, - port, - configuration=configuration, - # session_ticket_handler=save_session_ticket, - create_protocol=wrap_protocol(self, ClientProtocol), - ) as client: - client = cast(ClientProtocol, client) - self.conn_out[(host, port)] = client - await client.open_stream_up_0() - await client.wait_closed() - del self.conn_out[(host, port)] - except ConnectionError: - if (host, port) in self.conn_out: - del self.conn_out[(host, port)] - logger.debug(f"💩 ClientProtocol Cannot connect to {host}:{port}") - - - async def request_blocks(self, direction, max_blocks, block_bytes): - #TODO: temp hack, should be provided with a specific peer? - conn_key = list(self.conn_out.keys())[0] - conn = self.conn_out[conn_key] - await conn.send_blocks_request(0, 100, block_bytes) - - - async def broadcast_block(self, block): - block_bytes = block.to_jam_bytes().to_bytes() - logger.debug(f'ServerProtocol broadcasting block announcement to {len(self.conn_in)} clients') - for client_id, client in self.conn_in.items(): - logger.debug(f"ServerProtocol send block to client {client}") - await client.send_block_announcement(block_bytes) From b0e4ab611a828ba50bb5ef5f09caa6946bdbe894 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Fri, 27 Jun 2025 14:34:39 +0200 Subject: [PATCH 02/51] - JAMNP wip --- pyjamaz/app.py | 3 +- .../transport/jamnp_s/connection_acceptor.py | 125 ------------------ pyjamaz/transport/jamnp_s/connection_base.py | 31 ++++- .../transport/jamnp_s/connection_initiator.py | 27 +--- pyjamaz/transport/jamnp_s/protocol.py | 23 ++-- .../jamnp_s/{stream_base.py => stream.py} | 62 +++++---- pyjamaz/transport/jamnp_s/streams/__init__.py | 0 .../{stream_0_up.py => streams/stream_0.py} | 33 +++-- .../stream_128.py} | 21 +-- .../transport/jamnp_s/streams/stream_129.py | 27 ++++ .../transport/jamnp_s/streams/stream_131.py | 20 +++ .../transport/jamnp_s/streams/stream_132.py | 20 +++ .../transport/jamnp_s/streams/stream_133.py | 20 +++ .../transport/jamnp_s/streams/stream_134.py | 20 +++ .../transport/jamnp_s/streams/stream_135.py | 20 +++ .../transport/jamnp_s/streams/stream_136.py | 20 +++ .../transport/jamnp_s/streams/stream_137.py | 20 +++ .../transport/jamnp_s/streams/stream_138.py | 20 +++ .../transport/jamnp_s/streams/stream_139.py | 20 +++ .../transport/jamnp_s/streams/stream_140.py | 20 +++ .../transport/jamnp_s/streams/stream_141.py | 20 +++ 21 files changed, 358 insertions(+), 214 deletions(-) rename pyjamaz/transport/jamnp_s/{stream_base.py => stream.py} (59%) create mode 100644 pyjamaz/transport/jamnp_s/streams/__init__.py rename pyjamaz/transport/jamnp_s/{stream_0_up.py => streams/stream_0.py} (64%) rename pyjamaz/transport/jamnp_s/{stream_128_block_request.py => streams/stream_128.py} (55%) create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_129.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_131.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_132.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_133.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_134.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_135.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_136.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_137.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_138.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_139.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_140.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_141.py diff --git a/pyjamaz/app.py b/pyjamaz/app.py index a3c19054..ca287665 100644 --- a/pyjamaz/app.py +++ b/pyjamaz/app.py @@ -113,9 +113,10 @@ async def import_block_from_bytes(self, data): self.import_queue.append(block) # Note: when we receive a block announcement and we just started our node, we send out a blocks request to sync our state + #TODO: niet meer nodig??!!!!!!!!!!! if self.network_bootstrap: # TODO: app.protocol.conn_out is a temporary hack, should do this different, and also allow for sequential back requests until a certain state is reached - if self.protocol.conn_out: + if self.protocol.conn_initiated: self.network_bootstrap = False # TODO: determine peer to request blocks from using protocol grid # TODO: moeten we hier niet alleen de header meegeven ipv een heel block te serializen????? diff --git a/pyjamaz/transport/jamnp_s/connection_acceptor.py b/pyjamaz/transport/jamnp_s/connection_acceptor.py index dc376b09..884372d4 100644 --- a/pyjamaz/transport/jamnp_s/connection_acceptor.py +++ b/pyjamaz/transport/jamnp_s/connection_acceptor.py @@ -2,10 +2,6 @@ from aioquic.quic.events import QuicEvent, StreamDataReceived, ConnectionTerminated, HandshakeCompleted -from jamcodec.base import JamBytes -from jamcodec.types import Vec - -from pyjamaz.models.block import Block from pyjamaz.transport.jamnp_s.connection_base import ConnectionBase @@ -14,127 +10,6 @@ class ConnectionAcceptor(ConnectionBase): - # #TODO: deprecated!!!!!! - # async def send_block_announcement(self, block_bytes): - # if self.stream_up is None: - # raise Exception("NO UP 0 block_announcement channel opend yet??") - # - # """ - # TODO: - # For now we only send length++, we should send: - # Final = Header Hash ++ Slot - # Leaf = Header Hash ++ Slot - # Handshake = Final ++ len++[Leaf] - # Announcement = Header ++ Final - # """ - # self._quic.send_stream_data( - # self.stream_up, - # (int(StreamType.UP0_BlockAnnouncement.value).to_bytes(length=1, byteorder='little') + - # len(block_bytes).to_bytes(length=4, byteorder='little') + - # block_bytes) - # ) - # self.transmit() - # logger.debug(f"ServerProtocol Block announcement sent to stream {self.stream_up} ({len(block_bytes)})") - def quic_event_received(self, event: QuicEvent): - # if isinstance(event, HandshakeCompleted): - # # TODO: check client certificate and alpn - # # if self._quic.configuration.alpn_protocols[0] != "jamnp-s/0/00000000": - # # self._quic.close() - # # return - # - # self.client_id = id(self) - # self.protocol.conn_in[self.client_id] = self # Store reference for broadcasting - # - # logger.debug(f'ServerProtocol new connected client #{self.client_id}') - # - # #TODO: remove connections on connection closed/lost etc - # - # elif isinstance(event, StreamDataReceived): - # logger.debug(f'Server received data: {event.data}') - # - # if self.stream_up is None: - # self.stream_up = event.stream_id - # logger.debug(f'ServerProtocol new UP-0 stream ({self.stream_up}) for client #{self.client_id}') - # - # # if event.stream_id == self.stream_up: - # # # Process incoming data (either handshake or announcement) - # # logger.debug(f'ServerProtocol new UP-0 stream ({self.stream_up}) for client #{self.client_id}') - # - # byte_data = bytes(event.data) - # bytes_left = byte_data - # - # # Note: Parse bytes until stream data is empty: https://github.com/microsoft/msquic/discussions/2037 - # while len(bytes_left) > 0: - # - # #TODO: do this per channel - # if not self._msg_buffer: - # # Note: first message always contains expected message type & length - # # TODO: kunnen msg_type en msg_len ook opgesplitst zijn in 2 messages?? gaan nu uit dat dit atomair is - # self._msg_type = int.from_bytes(byte_data[0:1], byteorder='little') - # self._msg_offset = 5 - # self._msg_len = int.from_bytes(byte_data[1:5], byteorder='little') + self._msg_offset - # logger.debug(f'ServerProtocol new message {self._msg_type} ({self._msg_len} bytes)') - # - # nr_bytes_remaining = self._msg_len-len(self._msg_buffer) - # if nr_bytes_remaining > 0: - # self._msg_buffer += bytes_left[:nr_bytes_remaining] - # bytes_left = bytes_left[nr_bytes_remaining:] - # else: - # bytes_left = bytes() - # - # # If we assembled a new message, parse it - # if 0 < self._msg_len == len(self._msg_buffer): - # - # match self._msg_type: - # - # case StreamType.UP0_OPEN.value: - # logger.debug(f'ServerProtocol PARSED UP-0') - # self._reset_msg() - # - # case StreamType.CE128_BlockRequest.value: - # logger.debug(f'ServerProtocol RECEIVED NEW BLOCKSREQUEST') - # direction = 1#self._msg_buffer[self._msg_offset:self._msg_offset+1] - # max_blocks = 1000 #self._msg_buffer[self._msg_offset+1:self._msg_offset+1+4] - # block = Block.from_jam_bytes(JamBytes(self._msg_buffer[self._msg_offset:self._msg_len])) - # - # logger.debug( - # f"ServerProtocol Block Requests received {self.stream_up} direction: {direction}, max_blocks: {max_blocks}, block: {block.header.timeslot}") - # - # blocks = [] - # #TODO: take direction and max_blocks into account - # #TODO: we decode and serialize blocks unnecessary here, improve! - # #TODO: check the max blocks and a hardcoded max of X - # while block.header.parent != bytes(32): - # block = self.protocol.app.retrieve_block_by_hash(block.header.parent) - # if not block: - # break - # blocks.append(block) - # - # block_list = Vec(Block.to_codec_def()).new() - # #TODO: optimize!! :S - # serialized_blocks = block_list.encode([b.to_json() for b in blocks]) - # - # logger.debug( - # f"ServerProtocol Block Requests sending {len(blocks)} blocks") - # - # self._quic.send_stream_data( - # self.stream_up, - # (int(StreamType.CE128_BlockRequest.value).to_bytes(length=1, byteorder='little') + - # len(serialized_blocks).to_bytes(length=4, byteorder='little') + - # serialized_blocks.to_bytes()) - # ) - # self.transmit() - # self._reset_msg() - # - # case _: - # raise InvalidStreamType(f"Invalid JAMNPS message: {self._msg_type}") - # - # elif isinstance(event, ConnectionTerminated): - # # Handle connection termination - # if id(self) in self.protocol.conn_in: - # logger.debug(f'Client #{self.client_id} disconnected') - # # print(f'Client #{self.client_id} disconnected') - # del self.protocol.conn_in[id(self)] pass diff --git a/pyjamaz/transport/jamnp_s/connection_base.py b/pyjamaz/transport/jamnp_s/connection_base.py index 59eb3021..c5fd397b 100644 --- a/pyjamaz/transport/jamnp_s/connection_base.py +++ b/pyjamaz/transport/jamnp_s/connection_base.py @@ -1,13 +1,12 @@ +import asyncio import logging -from enum import Enum - from aioquic.asyncio import QuicConnectionProtocol, serve +from pyjamaz.transport.jamnp_s.stream import Stream, StreamDirection logger = logging.getLogger("pyjamaz.transport.jamnp_s") - class ConnectionBase(QuicConnectionProtocol): def __init__(self, *args, **kwargs): @@ -17,3 +16,29 @@ def __init__(self, *args, **kwargs): self.stream_up_id = None self.streams = {} + self._keepalive_task = asyncio.create_task(self._keepalive()) + + + def create_jam_stream(self, StreamCls: Stream): + stream_id = self._quic.get_next_available_stream_id() + self.streams[stream_id] = StreamCls(stream_id, connection=self, direction=StreamDirection.initiator) + return self.streams[stream_id] + + + def send(self, stream_id: int, data, end_stream=False): + self._quic.send_stream_data( + stream_id, + data, + end_stream=end_stream + ) + self.transmit() + + + async def _keepalive(self): + try: + while True: + await asyncio.sleep(4) + self._quic.send_ping(666) + self.transmit() + except asyncio.CancelledError: + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/connection_initiator.py b/pyjamaz/transport/jamnp_s/connection_initiator.py index 2d90f8a1..62928f86 100644 --- a/pyjamaz/transport/jamnp_s/connection_initiator.py +++ b/pyjamaz/transport/jamnp_s/connection_initiator.py @@ -1,12 +1,9 @@ -import asyncio import logging -import struct -from typing import List from aioquic.quic.events import QuicEvent, StreamDataReceived, ConnectionTerminated, HandshakeCompleted from pyjamaz.transport.jamnp_s.connection_base import ConnectionBase -from pyjamaz.transport.jamnp_s.stream_0_up import StreamUP +from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -24,9 +21,9 @@ def quic_event_received(self, event: QuicEvent) -> None: if self.stream_up_id is not None: raise Exception("There can be only one UP connection active at a time") - self.stream_up_id = self._quic.get_next_available_stream_id() - self.streams[self.stream_up_id] = StreamUP(self.stream_up_id, self) - self.streams[self.stream_up_id].send_handshake() + stream_up = self.create_jam_stream(StreamUP) + self.stream_up_id = stream_up.stream_id + stream_up.initiator_handshake() elif isinstance(event, StreamDataReceived): @@ -42,19 +39,3 @@ def quic_event_received(self, event: QuicEvent) -> None: # elif isinstance(event, ConnectionTerminated): # # Handle connection termination - - async def send_blocks_request(self, direction, max_blocks, block_bytes): - #!!!!!!!!!!!!TODO: moet over een nieuwe stream -> creeer een nieuwe stream - # data = ( - # # int(direction).to_bytes(length=1, byteorder='little') + - # # int(max_blocks).to_bytes(length=1, byteorder='little') + - # block_bytes - # ) - # self._quic.send_stream_data( - # self.stream_up, - # (int(StreamType.CE128_BlockRequest.value).to_bytes(length=1, byteorder='little') + - # len(data).to_bytes(length=4, byteorder='little') + - # data) - # ) - # self.transmit() - logger.debug(f"ClientProtocol Block Requests sent to stream {self.stream_up_id} ({len(data)})") diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 866023f3..1abac07f 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -66,13 +66,13 @@ def __init__(self, host, port, certificate, private_key, app, initial_slot_nr, i #quic_logger=quic_logger, #verify_mode=ssl.CERT_REQUIRED, verify_mode=ssl.CERT_NONE, - idle_timeout=300000 + #idle_timeout=300000 ) self.cert = certificate self.pk = private_key self.configuration.load_cert_chain(certificate, private_key) - self.conn_in = {} # All incomming connections - self.conn_out = {} # All outgoing connections (who we connect to) + self.conn_accepted = {} # All incomming connections + self.conn_initiated = {} # All outgoing connections (who we connect to) async def listen(self): @@ -107,26 +107,25 @@ async def connect(self, host, port): create_protocol=wrap_protocol(self, ConnectionInitiator), ) as client: client = cast(ConnectionInitiator, client) - self.conn_out[(host, port)] = client - #await client.open_stream_up() + self.conn_initiated[(host, port)] = client await client.wait_closed() - del self.conn_out[(host, port)] + del self.conn_initiated[(host, port)] except ConnectionError as exc: - if (host, port) in self.conn_out: - del self.conn_out[(host, port)] + if (host, port) in self.conn_initiated: + del self.conn_initiated[(host, port)] logger.warning(f"💩 ClientProtocol Cannot connect to {host}:{port} {exc}") async def request_blocks(self, direction, max_blocks, block_bytes): #TODO: temp hack, should be provided with a specific peer? - conn_key = list(self.conn_out.keys())[0] - conn = self.conn_out[conn_key] + conn_key = list(self.conn_initiated.keys())[0] + conn = self.conn_initiated[conn_key] await conn.send_blocks_request(0, 100, block_bytes) async def broadcast_block(self, block): block_bytes = block.to_jam_bytes().to_bytes() - logger.debug(f'ServerProtocol broadcasting block announcement to {len(self.conn_in)} clients') - for client_id, client in self.conn_in.items(): + logger.debug(f'ServerProtocol broadcasting block announcement to {len(self.conn_accepted)} clients') + for client_id, client in self.conn_accepted.items(): logger.debug(f"ServerProtocol send block to client {client}") await client.send_block_announcement(block_bytes) diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream.py similarity index 59% rename from pyjamaz/transport/jamnp_s/stream_base.py rename to pyjamaz/transport/jamnp_s/stream.py index 71cc22c4..e21ba422 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream.py @@ -5,54 +5,56 @@ logger = logging.getLogger("pyjamaz.transport.jamnp_s.streams") -class InvalidStreamType(Exception): - pass - - class StreamType(Enum): UP0_BlockAnnouncement: int = 0 CE128_BlockRequest: int = 128 - # 129 - # 131 - # 132 - # 133 - # 134 - # 135 - # 136 - # 137 - # 138 - # 139 - # 140 - # 141 + CE129_StateRequest: int = 129 + CE131_SafroleTicketDistributionStep1: int = 131 + CE132_SafroleTicketDistributionStep2: int = 132 + CE133_WorkPackageSubmission: int = 133 + CE134_WorkPackageSharing: int = 134 + CE135_WorkReportDistribution: int = 135 + CE136_WorkReportRequest: int = 136 + CE137_ShardDistribution: int = 137 + CE138_AuditShardRequest: int = 138 + CE139_SegmentShardRequest: int = 139 + CE140_SegmentShardRequestJustification: int = 140 + CE141_AssuranceDistribution: int = 141 # 142 # 143 # 144 # 145 +class StreamDirection(Enum): + initiator: int = 0 + acceptor: int = 1 + + class Stream: - #TODO: typings, and is it really necesary to pass these here? - def __init__(self, stream_id: int, connection): + #TODO: typings on connection + def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_id = stream_id self.stream_type = None # Note: override in subclass self.conn = connection + self.direction = direction self._msg_buffer = b"" self._msg_len = -1 - self._msg_type = -1 - self._msg_offset = -1 def _reset_msg(self): self._msg_buffer = b"" self._msg_len = -1 - self._msg_type = -1 - self._msg_offset = -1 - def parse_message(self, data: bytes): - raise Exception("Implement this method") + def initiator_message(self, data: bytes): + raise Exception("Override this method in Stream subclass") + + + def acceptor_message(self, data: bytes): + raise Exception("Override this method in Stream subclass") def receive_data(self, data: bytes): @@ -67,14 +69,13 @@ def receive_data(self, data: bytes): print(f"NEW MESSAGE: {self.stream_id} msg length: {len(data)} data length: {self._msg_len}") # TODO: check message length > 0???!!!! - - parse_message = False + msg_complete = False if len(self._msg_buffer) + len(data) >= self._msg_len: # If we received a full message (or more than 1 message) end_offset = self._msg_len - len(self._msg_buffer) self._msg_buffer += data[:end_offset] data = data[end_offset:] - parse_message = True + msg_complete = True print("FULL MESSAGE RECEIVED") else: # Otherwise append only, we expect more data to finish this message @@ -83,8 +84,11 @@ def receive_data(self, data: bytes): print(f"APPENDING TO EXISTING MESSAGE: {self.stream_id} msg length: {len(data)} data length: {self._msg_len}") # If we assembled a new message, parse it - if parse_message: + if msg_complete: try: - self.parse_message(self._msg_buffer) + if self.direction == StreamDirection.initiator: + self.initiator_message(self._msg_buffer) + else: + self.acceptor_message(self._msg_buffer) finally: self._reset_msg() diff --git a/pyjamaz/transport/jamnp_s/streams/__init__.py b/pyjamaz/transport/jamnp_s/streams/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/pyjamaz/transport/jamnp_s/stream_0_up.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py similarity index 64% rename from pyjamaz/transport/jamnp_s/stream_0_up.py rename to pyjamaz/transport/jamnp_s/streams/stream_0.py index e671d556..9468623d 100644 --- a/pyjamaz/transport/jamnp_s/stream_0_up.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -2,17 +2,18 @@ from jamcodec.types import U32, VarInt64 from pyjamaz.models.block import Header -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection class StreamUP(Stream): - def __init__(self, stream_id: int, connection): - super().__init__(stream_id, connection) + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) self.stream_type = StreamType.UP0_BlockAnnouncement.value.to_bytes(length=1, byteorder='little') self.handshake_complete = False - def send_handshake(self): + + def initiator_handshake(self): """ TODO: Both sides should begin by sending a handshake message containing all known leaves (descendants of the latest finalized block with no known children). @@ -23,13 +24,13 @@ def send_handshake(self): leaf_count = VarInt64.encode(len(leafs)).to_bytes() handshake = final + leaf_count + leafs - self.conn._quic.send_stream_data( + self.conn.send( self.stream_id, self.stream_type + (len(handshake)).to_bytes(length=4, byteorder='little') + handshake, ) - def parse_message(self, data: bytes): + def initiator_message(self, data: bytes): print(f"RECEIVED UP0 MSG: {len(data)}") if not self.handshake_complete: @@ -47,12 +48,20 @@ def parse_message(self, data: bytes): print(f"HANDSHAKE: {header_hash} {slot} {leaf_count} remaining: {jam_bytes.get_remaining_length()}") self.handshake_complete = True - # TODO: temp adhoc 128 stream, send notification instead!!!!! - from pyjamaz.transport.jamnp_s.stream_128_block_request import StreamBlockRequest - stream_id = self.conn._quic.get_next_available_stream_id() - self.conn.streams[stream_id] = StreamBlockRequest(stream_id, self.conn) - self.conn.streams[stream_id].send_block_request(header_hash, 1, 1) + # TODO: temp adhoc 128 stream, send notification and handle using app logic instead!!!!! + from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest + stream_req = self.conn.create_jam_stream(StreamBlockRequest) + stream_req.initiator_block_request(header_hash, 1, 1) return - #test = Header.from_jam_bytes(JamBytes(data)) + # After handshake is completed, we receive Announcement messages + jam_bytes = JamBytes(data) + header = Header.from_jam_bytes(jam_bytes) + header_hash = jam_bytes.get_next_bytes(32).hex() + slot = U32.decode(jam_bytes) + print(f"UP0 Update: {header} {header_hash} {slot}") + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/stream_128_block_request.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py similarity index 55% rename from pyjamaz/transport/jamnp_s/stream_128_block_request.py rename to pyjamaz/transport/jamnp_s/streams/stream_128.py index f57a297e..15655fe0 100644 --- a/pyjamaz/transport/jamnp_s/stream_128_block_request.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -1,31 +1,34 @@ from jamcodec.base import JamBytes -from jamcodec.types import U32, VarInt64 -from pyjamaz.models.block import Header -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType +from pyjamaz.models.block import Block +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection class StreamBlockRequest(Stream): - def __init__(self, stream_id: int, connection): - super().__init__(stream_id, connection) + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) self.stream_type = StreamType.CE128_BlockRequest.value.to_bytes(length=1, byteorder='little') - def send_block_request(self, header_hash, direction, max_blocks): + def initiator_block_request(self, header_hash, direction, max_blocks): header_hash = bytes.fromhex(header_hash) direction = direction.to_bytes(length=1, byteorder='little') max_blocks = max_blocks.to_bytes(length=4, byteorder='little') payload = header_hash + direction + max_blocks print("SENDING BLOCK REQUEST!!!!!!!") - self.conn._quic.send_stream_data( + self.conn.send( self.stream_id, self.stream_type + (len(payload)).to_bytes(length=4, byteorder='little') + payload, end_stream=True ) - def parse_message(self, data: bytes): + def initiator_message(self, data: bytes): print(f"RECEIVED CE128 MSG: {len(data)}") - #test = Header.from_jam_bytes(JamBytes(data)) + block = Block.from_jam_bytes(JamBytes(data)) + print(f"Parsed block: {block}") + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_129.py b/pyjamaz/transport/jamnp_s/streams/stream_129.py new file mode 100644 index 00000000..8cc6115b --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_129.py @@ -0,0 +1,27 @@ +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection + + +class StreamStateRequest(Stream): + + BOUNDARY_NODE = 0 + KEY_VALUE = 1 + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE129_StateRequest.value.to_bytes(length=1, byteorder='little') + self.state = self.BOUNDARY_NODE + + + def initiate_state_request(self, header_hash, direction, max_blocks): + pass + + + def initiator_message(self, data: bytes): + if self.state == self.BOUNDARY_NODE: + pass + elif self.state == self.KEY_VALUE: + pass + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_131.py b/pyjamaz/transport/jamnp_s/streams/stream_131.py new file mode 100644 index 00000000..04529262 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_131.py @@ -0,0 +1,20 @@ +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection + + +class StreamSafroleTicketDistributionStep1(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE131_SafroleTicketDistributionStep1.value.to_bytes(length=1, byteorder='little') + + def initiate_state_request(self, header_hash, direction, max_blocks): + pass + + + def initiator_message(self, data: bytes): + # Note: Sends message to one specific deterministic validator + pass + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_132.py b/pyjamaz/transport/jamnp_s/streams/stream_132.py new file mode 100644 index 00000000..22ebb6cc --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_132.py @@ -0,0 +1,20 @@ +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection + + +class StreamSafroleTicketDistributionStep2(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE132_SafroleTicketDistributionStep2.value.to_bytes(length=1, byteorder='little') + + def initiate_state_request(self, header_hash, direction, max_blocks): + # Note: Sends message to all current validators + pass + + + def initiator_message(self, data: bytes): + pass + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_133.py b/pyjamaz/transport/jamnp_s/streams/stream_133.py new file mode 100644 index 00000000..978bf70e --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_133.py @@ -0,0 +1,20 @@ +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection + + +class StreamWorkPackageSubmission(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE133_WorkPackageSubmission.value.to_bytes(length=1, byteorder='little') + + + def initiate_state_request(self, header_hash, direction, max_blocks): + pass + + + def initiator_message(self, data: bytes): + pass + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_134.py b/pyjamaz/transport/jamnp_s/streams/stream_134.py new file mode 100644 index 00000000..1907c69c --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_134.py @@ -0,0 +1,20 @@ +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection + + +class StreamWorkPackageSharing(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE134_WorkPackageSharing.value.to_bytes(length=1, byteorder='little') + + + def initiate_state_request(self, header_hash, direction, max_blocks): + pass + + + def initiator_message(self, data: bytes): + pass + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_135.py b/pyjamaz/transport/jamnp_s/streams/stream_135.py new file mode 100644 index 00000000..152a5f38 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_135.py @@ -0,0 +1,20 @@ +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection + + +class StreamWorkReportDistribution(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE135_WorkReportDistribution.value.to_bytes(length=1, byteorder='little') + + + def initiate_state_request(self, header_hash, direction, max_blocks): + pass + + + def initiator_message(self, data: bytes): + pass + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_136.py b/pyjamaz/transport/jamnp_s/streams/stream_136.py new file mode 100644 index 00000000..794ee54c --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_136.py @@ -0,0 +1,20 @@ +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection + + +class StreamWorkReportRequest(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE136_WorkReportRequest.value.to_bytes(length=1, byteorder='little') + + + def initiate_state_request(self, header_hash, direction, max_blocks): + pass + + + def initiator_message(self, data: bytes): + pass + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_137.py b/pyjamaz/transport/jamnp_s/streams/stream_137.py new file mode 100644 index 00000000..8aff66ae --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_137.py @@ -0,0 +1,20 @@ +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection + + +class StreamShardDistribution(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE137_ShardDistribution.value.to_bytes(length=1, byteorder='little') + + + def initiate_state_request(self, header_hash, direction, max_blocks): + pass + + + def initiator_message(self, data: bytes): + pass + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_138.py b/pyjamaz/transport/jamnp_s/streams/stream_138.py new file mode 100644 index 00000000..aa095509 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_138.py @@ -0,0 +1,20 @@ +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection + + +class StreamAuditShardRequest(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE138_AuditShardRequest.value.to_bytes(length=1, byteorder='little') + + + def initiate_state_request(self, header_hash, direction, max_blocks): + pass + + + def initiator_message(self, data: bytes): + pass + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_139.py b/pyjamaz/transport/jamnp_s/streams/stream_139.py new file mode 100644 index 00000000..074afefc --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_139.py @@ -0,0 +1,20 @@ +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection + + +class StreamSegmentShardRequest(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE139_SegmentShardRequest.value.to_bytes(length=1, byteorder='little') + + + def initiate_state_request(self, header_hash, direction, max_blocks): + pass + + + def initiator_message(self, data: bytes): + pass + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_140.py b/pyjamaz/transport/jamnp_s/streams/stream_140.py new file mode 100644 index 00000000..1e176895 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_140.py @@ -0,0 +1,20 @@ +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection + + +class StreamSegmentShardRequestJustification(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE140_SegmentShardRequestJustification.value.to_bytes(length=1, byteorder='little') + + + def initiate_state_request(self, header_hash, direction, max_blocks): + pass + + + def initiator_message(self, data: bytes): + pass + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_141.py b/pyjamaz/transport/jamnp_s/streams/stream_141.py new file mode 100644 index 00000000..1b0e6ff9 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_141.py @@ -0,0 +1,20 @@ +from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection + + +class StreamAssuranceDistribution(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE141_AssuranceDistribution.value.to_bytes(length=1, byteorder='little') + + + def initiate_state_request(self, header_hash, direction, max_blocks): + pass + + + def initiator_message(self, data: bytes): + pass + + + def acceptor_message(self, data: bytes): + pass \ No newline at end of file From 520a2057949f6735e5a70f1551c2affbb150baca Mon Sep 17 00:00:00 2001 From: matthijsb Date: Tue, 1 Jul 2025 13:50:55 +0200 Subject: [PATCH 03/51] - base handshake UP0 and block request CE128 working --- pyjamaz/app.py | 10 ++--- pyjamaz/cli.py | 11 +++-- pyjamaz/constants.py | 1 + pyjamaz/transport/jamnp_s/connection_base.py | 22 +++++---- .../transport/jamnp_s/connection_initiator.py | 30 ++++++++++--- pyjamaz/transport/jamnp_s/protocol.py | 36 +++++++++------ .../jamnp_s/{stream.py => stream_base.py} | 12 ++++- pyjamaz/transport/jamnp_s/streams/stream_0.py | 45 ++++++++++++------- .../transport/jamnp_s/streams/stream_128.py | 39 +++++++++++----- .../transport/jamnp_s/streams/stream_129.py | 2 +- .../transport/jamnp_s/streams/stream_131.py | 2 +- .../transport/jamnp_s/streams/stream_132.py | 2 +- .../transport/jamnp_s/streams/stream_133.py | 2 +- .../transport/jamnp_s/streams/stream_134.py | 2 +- .../transport/jamnp_s/streams/stream_135.py | 2 +- .../transport/jamnp_s/streams/stream_136.py | 2 +- .../transport/jamnp_s/streams/stream_137.py | 2 +- .../transport/jamnp_s/streams/stream_138.py | 2 +- .../transport/jamnp_s/streams/stream_139.py | 2 +- .../transport/jamnp_s/streams/stream_140.py | 2 +- .../transport/jamnp_s/streams/stream_141.py | 2 +- pyjamaz/transport/types.py | 3 +- 22 files changed, 155 insertions(+), 78 deletions(-) rename pyjamaz/transport/jamnp_s/{stream.py => stream_base.py} (88%) diff --git a/pyjamaz/app.py b/pyjamaz/app.py index ca287665..5f0b478d 100644 --- a/pyjamaz/app.py +++ b/pyjamaz/app.py @@ -113,15 +113,15 @@ async def import_block_from_bytes(self, data): self.import_queue.append(block) # Note: when we receive a block announcement and we just started our node, we send out a blocks request to sync our state - #TODO: niet meer nodig??!!!!!!!!!!! + #TODO!!!!!!NETJES MAKEN!!!!! if self.network_bootstrap: # TODO: app.protocol.conn_out is a temporary hack, should do this different, and also allow for sequential back requests until a certain state is reached if self.protocol.conn_initiated: self.network_bootstrap = False - # TODO: determine peer to request blocks from using protocol grid - # TODO: moeten we hier niet alleen de header meegeven ipv een heel block te serializen????? - await self.protocol.request_blocks(0, 100, block.to_jam_bytes().to_bytes()) - # TODO: wel dit block al opslaan en alleen blocks die we vanaf dit Block + # # TODO: determine peer to request blocks from using protocol grid + # # TODO: moeten we hier niet alleen de header meegeven ipv een heel block te serializen????? + # await self.protocol.request_blocks(0, 100, block.to_jam_bytes().to_bytes()) + # # TODO: wel dit block al opslaan en alleen blocks die we vanaf dit Block elif block.header.parent == bytes(32) or self.retrieve_block_by_hash(block.header.parent): # Note: If we are able to find the parent of this block, it means we are synced and we can process blocks await self.process_import_queue() diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 9a306c9f..27f3a523 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -175,7 +175,7 @@ async def initialize_app( @click.option('--host', 'host', type=str, default="127.0.0.1", show_default=True, help='Host address to listen on') @click.option('--bootnode', 'bootnode', type=str, default="", show_default=True, help='Specific bootnode to connect to') @click.option('--rpc-listen-ip', 'rpc_listen_ip', type=str, default="0.0.0.0", show_default=True, help='IP address for RPC server to listen on') -@click.option('--rpc-port', 'rpc_port', type=int, default=19800, show_default=True, help='Port for RPC server to listen on') +@click.option('--rpc-port', 'rpc_port', type=int, default=19666, show_default=True, help='Port for RPC server to listen on') async def main(ctx, seed, port, ts, culprit, block_dir, record_traces, custom_db_path, verbose, host, bootnode, rpc_listen_ip, rpc_port): """PyJAMaz: Python JAM Client""" @@ -253,11 +253,12 @@ async def main(ctx, seed, port, ts, culprit, block_dir, record_traces, custom_db pk_file = os.path.join(db_path, "cert.key") #nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app) # (host, port, certificate, private_key, app, initial_slot_nr, initial_block_hash): - initial_block_hash = app.retrieve_block_hash(0).hex() - nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app, 0, initial_block_hash, None) + #initial_block_hash = app.retrieve_block_hash(0).hex() + nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app) app.protocol = nps_protocol app.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, wrap_produced_block_jamnp(app, record_traces, nps_protocol)) app.pubsub.subscribe(MESSAGE_TYPES.RECEIVED_BLOCK, app.import_block_from_bytes) + app.pubsub.subscribe(MESSAGE_TYPES.REQUEST_BLOCKS, nps_protocol.request_blocks) app.pubsub.subscribe(MESSAGE_TYPES.REQUESTED_BLOCKS, app.requested_blocks_from_bytes) tg.start_soon(nps_protocol.listen) @@ -278,7 +279,9 @@ async def main(ctx, seed, port, ts, culprit, block_dir, record_traces, custom_db logging.debug(f'Connecting to bootnode {conn["key"]} at {conn["addr"]}:{conn["port"]}') #tg.start_soon(nps_protocol.connect, conn["addr"], conn["port"]) try: - await nps_protocol.connect(conn["addr"], conn["port"]) + #await nps_protocol.connect(conn["addr"], conn["port"]) + #await nps_protocol.connect("54.39.18.64", 40000) + await nps_protocol.connect("localhost", conn["port"]) except Exception as exc: traceback.print_exc() else: diff --git a/pyjamaz/constants.py b/pyjamaz/constants.py index a82d133f..dbfe015f 100644 --- a/pyjamaz/constants.py +++ b/pyjamaz/constants.py @@ -37,6 +37,7 @@ class MESSAGE_TYPES(Enum): PRODUCED_BLOCK = "PRODUCED_BLOCK" RECEIVED_BLOCK = "RECEIVED_BLOCK" + REQUEST_BLOCKS = "REQUEST_BLOCKS" REQUESTED_BLOCKS = "REQUESTED_BLOCKS" STATISTICS = "statistics" diff --git a/pyjamaz/transport/jamnp_s/connection_base.py b/pyjamaz/transport/jamnp_s/connection_base.py index c5fd397b..92037bde 100644 --- a/pyjamaz/transport/jamnp_s/connection_base.py +++ b/pyjamaz/transport/jamnp_s/connection_base.py @@ -2,7 +2,7 @@ import logging from aioquic.asyncio import QuicConnectionProtocol, serve -from pyjamaz.transport.jamnp_s.stream import Stream, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamDirection logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -13,8 +13,10 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.protocol = None # Note: should be set in wrap_protocol + self.host = None + self.port = None - self.stream_up_id = None + self.stream_up = None self.streams = {} self._keepalive_task = asyncio.create_task(self._keepalive()) @@ -35,10 +37,12 @@ def send(self, stream_id: int, data, end_stream=False): async def _keepalive(self): - try: - while True: - await asyncio.sleep(4) - self._quic.send_ping(666) - self.transmit() - except asyncio.CancelledError: - pass \ No newline at end of file + # try: + # while True: + # #TODO: what is a sane amount of time? + # await asyncio.sleep(4) + # self._quic.send_ping(id(self)) + # self.transmit() + # except asyncio.CancelledError: + # pass + pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/connection_initiator.py b/pyjamaz/transport/jamnp_s/connection_initiator.py index 62928f86..f48d6b2e 100644 --- a/pyjamaz/transport/jamnp_s/connection_initiator.py +++ b/pyjamaz/transport/jamnp_s/connection_initiator.py @@ -1,6 +1,6 @@ import logging -from aioquic.quic.events import QuicEvent, StreamDataReceived, ConnectionTerminated, HandshakeCompleted +from aioquic.quic.events import QuicEvent, StreamDataReceived, ConnectionTerminated, HandshakeCompleted, StreamReset from pyjamaz.transport.jamnp_s.connection_base import ConnectionBase from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP @@ -12,25 +12,45 @@ class ConnectionInitiator(ConnectionBase): def quic_event_received(self, event: QuicEvent) -> None: - logger.debug(f'ProtocolInitiator received data {event}') + if event and hasattr(event, "stream_id"): + if event.stream_id != 0: + logger.debug(f'ConnectionInitiator STREAM {event.stream_id} DATA {event}') + else: + logger.debug(f'ConnectionInitiator received non stream data {event}') if isinstance(event, HandshakeCompleted): #TODO: # Both nodes are validators, and are neighbours in the grid structure. # At least one of the nodes is not a validator. - if self.stream_up_id is not None: + if self.stream_up is not None: raise Exception("There can be only one UP connection active at a time") stream_up = self.create_jam_stream(StreamUP) - self.stream_up_id = stream_up.stream_id + self.stream_up = stream_up stream_up.initiator_handshake() + elif isinstance(event, StreamReset): + stream_id = event.stream_id + reset_code = event.error_code + + logger.info(f'ConnectionInitiator StreamReset {stream_id} code {reset_code}') + + if stream_id not in self.streams: + raise Exception(f"Stream {stream_id} not available") + + self.streams[stream_id].initiator_reset(reset_code) + + # if not self._quic.stream_is_closed(stream_id): + # self._quic.reset_stream(stream_id, error_code=0) + + elif isinstance(event, StreamDataReceived): stream_id = event.stream_id if stream_id not in self.streams: - raise Exception(f"Stream {stream_id} not available") + logging.warning(f"Stream {stream_id} not available") + return self.streams[stream_id].receive_data(bytes(event.data)) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 1abac07f..6d3f03e0 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -18,10 +18,12 @@ logger = logging.getLogger("pyjamaz.transport.jamnp_s") -def wrap_protocol(wrapper, protocol): +def wrap_protocol(wrapper, protocol, host, port): def create_protocol(*args, **kwargs): instance = protocol(*args, **kwargs) instance.protocol = wrapper + instance.host = host + instance.port = port return instance return create_protocol @@ -43,20 +45,19 @@ class JAMNPS(ProtocolType): PROTOCOL_NAME = "jamnp-s/0/{}" + DIRECTION_ASC = int(0).to_bytes(length=1, byteorder='little') + DIRECTION_DESC = int(1).to_bytes(length=1, byteorder='little') + #TODO: add role enum: validator, guarantors, light_client, builder, ... - def __init__(self, host, port, certificate, private_key, app, initial_slot_nr, initial_block_hash, role): + def __init__(self, host, port, certificate, private_key, app): #, initial_slot_nr, initial_block_hash, role): self.host = host self.port = port self.pubsub = app.pubsub self.app = app - #################TODO: haal deze info uit app ipv arg?? - self.first_block_hash = initial_block_hash - self.first_slot = initial_slot_nr - bl_hash = initial_block_hash + bl_hash = app.retrieve_block_hash(0).hex() if bl_hash.startswith('0x'): bl_hash = bl_hash[2:] bl_hash = bl_hash[:8] - ################## self.protocol_name = JAMNPS.PROTOCOL_NAME.format(bl_hash) self.session_ticket_store = SessionTicketStore() @@ -81,7 +82,7 @@ async def listen(self): self.host, self.port, configuration=self.configuration, - create_protocol=wrap_protocol(self, ConnectionAcceptor), + create_protocol=wrap_protocol(self, ConnectionAcceptor, self.host, self.port), session_ticket_fetcher=self.session_ticket_store.pop, session_ticket_handler=self.session_ticket_store.add, retry=True, @@ -104,7 +105,7 @@ async def connect(self, host, port): port, configuration=configuration, # session_ticket_handler=save_session_ticket, - create_protocol=wrap_protocol(self, ConnectionInitiator), + create_protocol=wrap_protocol(self, ConnectionInitiator, host, port), ) as client: client = cast(ConnectionInitiator, client) self.conn_initiated[(host, port)] = client @@ -116,11 +117,20 @@ async def connect(self, host, port): logger.warning(f"💩 ClientProtocol Cannot connect to {host}:{port} {exc}") - async def request_blocks(self, direction, max_blocks, block_bytes): - #TODO: temp hack, should be provided with a specific peer? - conn_key = list(self.conn_initiated.keys())[0] + #TODO: typings for data + async def request_blocks(self, data): + #TODO: temp hack, should extract peer from data + #conn_key = list(self.conn_initiated.keys())[0] + conn_key = (data[0], data[1]) conn = self.conn_initiated[conn_key] - await conn.send_blocks_request(0, 100, block_bytes) + block_hash = data[2] + direction = data[3] + max_blocks = data[4] + + from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest + stream_req = conn.create_jam_stream(StreamBlockRequest) + print(f"CALLING BLOCK REQUEST ON STREAMID: {stream_req.stream_id} header hash: {block_hash} direction: {direction}, max_block: {max_blocks}") + stream_req.initiator_block_request(block_hash, direction, max_blocks) async def broadcast_block(self, block): diff --git a/pyjamaz/transport/jamnp_s/stream.py b/pyjamaz/transport/jamnp_s/stream_base.py similarity index 88% rename from pyjamaz/transport/jamnp_s/stream.py rename to pyjamaz/transport/jamnp_s/stream_base.py index e21ba422..3f6ed2ab 100644 --- a/pyjamaz/transport/jamnp_s/stream.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -66,7 +66,7 @@ def receive_data(self, data: bytes): # byte_data = bytes(event.data) self._msg_len = int.from_bytes(data[0:4], byteorder='little') data = data[4:] - print(f"NEW MESSAGE: {self.stream_id} msg length: {len(data)} data length: {self._msg_len}") + #print(f"NEW MESSAGE: {self.stream_id} msg length: {len(data)} data length: {self._msg_len}") # TODO: check message length > 0???!!!! msg_complete = False @@ -76,7 +76,7 @@ def receive_data(self, data: bytes): self._msg_buffer += data[:end_offset] data = data[end_offset:] msg_complete = True - print("FULL MESSAGE RECEIVED") + #print(f"FULL MESSAGE RECEIVED {self.stream_type} stream type = {int(self.stream_id)}") else: # Otherwise append only, we expect more data to finish this message self._msg_buffer += data @@ -92,3 +92,11 @@ def receive_data(self, data: bytes): self.acceptor_message(self._msg_buffer) finally: self._reset_msg() + + + def initiator_reset(self, reset_code: int): + del self.conn.streams[self.stream_id] + + + def acceptor_reset(self, reset_code: int): + del self.conn.streams[self.stream_id] diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py index 9468623d..d96e41eb 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -1,8 +1,12 @@ +import asyncio + from jamcodec.base import JamBytes from jamcodec.types import U32, VarInt64 -from pyjamaz.models.block import Header -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.constants import MESSAGE_TYPES + +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +from pyjamaz.transport.pubsub import PubSubSignal class StreamUP(Stream): @@ -19,8 +23,12 @@ def initiator_handshake(self): Both sides should begin by sending a handshake message containing all known leaves (descendants of the latest finalized block with no known children). The header hash and slot of the latest finalized block should be included in the handshake message and also in every announcement message that is sent. """ - final = bytes.fromhex(self.conn.protocol.first_block_hash) + self.conn.protocol.first_slot.to_bytes(length=4, byteorder='little') - leafs = bytes() # [bytes.fromhex(h) + s.to_bytes(length=4, byteorder='little') for h, s in []] #TODO: vraag app naar leafs vanaf first_block + bl_hash = self.conn.protocol.app.retrieve_block_hash(0) + bl_ts = int(0).to_bytes(length=4, byteorder='little') #self.conn.protocol.app.state.timeslot.number + final = bl_hash + bl_ts + print(f"HANDSHAKE FINAL: {bl_hash} {bl_ts} {final}") + # TODO: vraag app naar leafs vanaf bl_hash + leafs = bytes() # [bytes.fromhex(h) + s.to_bytes(length=4, byteorder='little') for h, s in []] leaf_count = VarInt64.encode(len(leafs)).to_bytes() handshake = final + leaf_count + leafs @@ -31,7 +39,7 @@ def initiator_handshake(self): def initiator_message(self, data: bytes): - print(f"RECEIVED UP0 MSG: {len(data)}") + #print(f"RECEIVED UP0 MSG: {len(data)}") if not self.handshake_complete: header_hash = data[:32].hex() @@ -43,24 +51,27 @@ def initiator_message(self, data: bytes): for leaf_nr in range(leaf_count): leaf_hash = jam_bytes.get_next_bytes(32).hex() leaf_slot = U32.decode(jam_bytes) - print(f"leaf: {leaf_hash} {leaf_slot}") + #print(f"leaf: {leaf_hash} {leaf_slot}") - print(f"HANDSHAKE: {header_hash} {slot} {leaf_count} remaining: {jam_bytes.get_remaining_length()}") + #print(f"HANDSHAKE: {header_hash} {slot} {leaf_count} remaining: {jam_bytes.get_remaining_length()}") self.handshake_complete = True - # TODO: temp adhoc 128 stream, send notification and handle using app logic instead!!!!! - from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest - stream_req = self.conn.create_jam_stream(StreamBlockRequest) - stream_req.initiator_block_request(header_hash, 1, 1) - + #self.conn._quic.reset_stream(self.stream_id, error_code=0x0) + latest_block_hash = self.conn.protocol.app.retrieve_block_hash(self.conn.protocol.app.state.timeslot.number) + #TODO: check if our latest hash is older than what we received with the handshake + print(f"REQUEST BLOCKS AFTER HANDSHAKE FOR {latest_block_hash.hex()} timeslot: {self.conn.protocol.app.state.timeslot.number}") + #TODO: nette dataclass encoders/decoders maken hiervoor!!! + from pyjamaz.transport.jamnp_s.protocol import JAMNPS + asyncio.create_task(self.conn.protocol.app.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.REQUEST_BLOCKS, data=[self.conn.host, self.conn.port, latest_block_hash, JAMNPS.DIRECTION_ASC, 1]))) return # After handshake is completed, we receive Announcement messages - jam_bytes = JamBytes(data) - header = Header.from_jam_bytes(jam_bytes) - header_hash = jam_bytes.get_next_bytes(32).hex() - slot = U32.decode(jam_bytes) - print(f"UP0 Update: {header} {header_hash} {slot}") + # jam_bytes = JamBytes(data) + # header = Header.from_jam_bytes(jam_bytes) + # header_hash = jam_bytes.get_next_bytes(32).hex() + # slot = U32.decode(jam_bytes) + # #print(f"UP0 Update: {header} {header_hash} {slot}") + # #MESSAGE_TYPES.RECEIVED_BLOCK -> moet ook weer checken of we dit block niet al hebben def acceptor_message(self, data: bytes): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py index 15655fe0..b78ac9e6 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -1,7 +1,15 @@ +import asyncio +import logging + from jamcodec.base import JamBytes +from pyjamaz.constants import MESSAGE_TYPES from pyjamaz.models.block import Block -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.protocol import JAMNPS +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +from pyjamaz.transport.pubsub import PubSubSignal + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") class StreamBlockRequest(Stream): @@ -11,13 +19,16 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type = StreamType.CE128_BlockRequest.value.to_bytes(length=1, byteorder='little') - def initiator_block_request(self, header_hash, direction, max_blocks): - header_hash = bytes.fromhex(header_hash) - direction = direction.to_bytes(length=1, byteorder='little') - max_blocks = max_blocks.to_bytes(length=4, byteorder='little') - payload = header_hash + direction + max_blocks + def initiator_block_request(self, header_hash: bytes, direction: bytes, max_blocks: int): + #TODO: nette data classes maken voor messages vd streams! + #direction_byte = direction.to_bytes(length=1, byteorder='little') + max_blocks_bytes = max_blocks.to_bytes(length=4, byteorder='little') + #direction = int(1).to_bytes(length=1, byteorder='little') + #max_blocks = int(0).to_bytes(length=4, byteorder='little') + payload = header_hash + direction + max_blocks_bytes + + print(f"SENDING BLOCK REQUEST streamid {self.stream_id} payload: {self.stream_type + (len(payload)).to_bytes(length=4, byteorder='little') + payload} end_stream=True") - print("SENDING BLOCK REQUEST!!!!!!!") self.conn.send( self.stream_id, self.stream_type + (len(payload)).to_bytes(length=4, byteorder='little') + payload, @@ -26,9 +37,17 @@ def initiator_block_request(self, header_hash, direction, max_blocks): def initiator_message(self, data: bytes): - print(f"RECEIVED CE128 MSG: {len(data)}") + print(f"RECEIVED INITIATED CE128 MSG: {len(data)}") + if len(data) == 0: #int.from_bytes(b'\x00\x00\x00\x00') + logger.debug(f'!!!!!!!!!ConnectionInitiator StreamBlockRequest.initiator_message received empty data') + return + block = Block.from_jam_bytes(JamBytes(data)) - print(f"Parsed block: {block}") + latest_block_hash = block.header.hash + print(f"Parsed block: {block.header.hash.hex()} parent:{block.header.parent.hex()}") + + asyncio.create_task(self.conn.protocol.app.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.REQUEST_BLOCKS, data=[self.conn.host, self.conn.port, latest_block_hash, JAMNPS.DIRECTION_ASC, 1]))) + def acceptor_message(self, data: bytes): - pass \ No newline at end of file + raise Exception("Not implemented YET!!!!!!") diff --git a/pyjamaz/transport/jamnp_s/streams/stream_129.py b/pyjamaz/transport/jamnp_s/streams/stream_129.py index 8cc6115b..b399c324 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_129.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_129.py @@ -1,4 +1,4 @@ -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection class StreamStateRequest(Stream): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_131.py b/pyjamaz/transport/jamnp_s/streams/stream_131.py index 04529262..903e62bc 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_131.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_131.py @@ -1,4 +1,4 @@ -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection class StreamSafroleTicketDistributionStep1(Stream): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_132.py b/pyjamaz/transport/jamnp_s/streams/stream_132.py index 22ebb6cc..3b589998 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_132.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_132.py @@ -1,4 +1,4 @@ -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection class StreamSafroleTicketDistributionStep2(Stream): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_133.py b/pyjamaz/transport/jamnp_s/streams/stream_133.py index 978bf70e..1b9edbe4 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_133.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_133.py @@ -1,4 +1,4 @@ -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection class StreamWorkPackageSubmission(Stream): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_134.py b/pyjamaz/transport/jamnp_s/streams/stream_134.py index 1907c69c..915b255b 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_134.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_134.py @@ -1,4 +1,4 @@ -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection class StreamWorkPackageSharing(Stream): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_135.py b/pyjamaz/transport/jamnp_s/streams/stream_135.py index 152a5f38..6c9df6e2 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_135.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_135.py @@ -1,4 +1,4 @@ -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection class StreamWorkReportDistribution(Stream): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_136.py b/pyjamaz/transport/jamnp_s/streams/stream_136.py index 794ee54c..f1db5cff 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_136.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_136.py @@ -1,4 +1,4 @@ -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection class StreamWorkReportRequest(Stream): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_137.py b/pyjamaz/transport/jamnp_s/streams/stream_137.py index 8aff66ae..ab3feb02 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_137.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_137.py @@ -1,4 +1,4 @@ -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection class StreamShardDistribution(Stream): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_138.py b/pyjamaz/transport/jamnp_s/streams/stream_138.py index aa095509..b27e8c22 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_138.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_138.py @@ -1,4 +1,4 @@ -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection class StreamAuditShardRequest(Stream): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_139.py b/pyjamaz/transport/jamnp_s/streams/stream_139.py index 074afefc..a4bf8ea3 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_139.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_139.py @@ -1,4 +1,4 @@ -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection class StreamSegmentShardRequest(Stream): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_140.py b/pyjamaz/transport/jamnp_s/streams/stream_140.py index 1e176895..1bb9a11d 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_140.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_140.py @@ -1,4 +1,4 @@ -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection class StreamSegmentShardRequestJustification(Stream): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_141.py b/pyjamaz/transport/jamnp_s/streams/stream_141.py index 1b0e6ff9..d1bff418 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_141.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_141.py @@ -1,4 +1,4 @@ -from pyjamaz.transport.jamnp_s.stream import Stream, StreamType, StreamDirection +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection class StreamAssuranceDistribution(Stream): diff --git a/pyjamaz/transport/types.py b/pyjamaz/transport/types.py index a50d7e2d..1366d130 100644 --- a/pyjamaz/transport/types.py +++ b/pyjamaz/transport/types.py @@ -9,7 +9,8 @@ class ProtocolType(Protocol): async def listen(self): """ Starts this protocol, start listening for messages """ - async def request_blocks(self, direction: int, max_blocks: int, block_bytes:bytes): + #TODO: typings on data -> create dedicated Message dataclasses + async def request_blocks(self, data): """ Starts a Blocks Request, to fetch a range of blocks from a given peer """ async def broadcast_block(self, block:Block): From 19579f771525e84d3c2d674d7a232ecb55ae25ca Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 3 Jul 2025 13:43:26 +0200 Subject: [PATCH 04/51] - JAMSNP WIP --- bootstrap_builder.py | 2 +- pyjamaz/app.py | 57 ++--- pyjamaz/cli.py | 26 +-- pyjamaz/constants.py | 3 - .../transport/jamnp_s/connection_acceptor.py | 48 ++++- pyjamaz/transport/jamnp_s/connection_base.py | 26 ++- .../transport/jamnp_s/connection_initiator.py | 8 +- pyjamaz/transport/jamnp_s/message_types.py | 199 ++++++++++++++++++ pyjamaz/transport/jamnp_s/protocol.py | 117 ++++++++-- pyjamaz/transport/jamnp_s/stream_base.py | 11 +- pyjamaz/transport/jamnp_s/streams/stream_0.py | 117 +++++----- .../transport/jamnp_s/streams/stream_128.py | 33 +-- pyjamaz/transport/protocol_fs.py | 162 +++++++------- pyjamaz/{ => transport}/rpc/blaas_playbook.py | 2 +- pyjamaz/{ => transport}/rpc/interface.py | 0 .../{ => transport}/rpc/parity_playbook.py | 2 +- pyjamaz/{ => transport}/rpc/playbook.py | 2 +- pyjamaz/{ => transport}/rpc/rpc.py | 0 pyjamaz/{ => transport}/rpc/ws_client.py | 4 +- pyjamaz/{ => transport}/rpc/ws_server.py | 4 +- .../rpc/ws_server_subscriptions.py | 4 +- pyjamaz/transport/types.py | 2 +- 22 files changed, 549 insertions(+), 280 deletions(-) create mode 100644 pyjamaz/transport/jamnp_s/message_types.py rename pyjamaz/{ => transport}/rpc/blaas_playbook.py (99%) rename pyjamaz/{ => transport}/rpc/interface.py (100%) rename pyjamaz/{ => transport}/rpc/parity_playbook.py (99%) rename pyjamaz/{ => transport}/rpc/playbook.py (99%) rename pyjamaz/{ => transport}/rpc/rpc.py (100%) rename pyjamaz/{ => transport}/rpc/ws_client.py (97%) rename pyjamaz/{ => transport}/rpc/ws_server.py (96%) rename pyjamaz/{ => transport}/rpc/ws_server_subscriptions.py (98%) diff --git a/bootstrap_builder.py b/bootstrap_builder.py index 03103ff9..b2f6547a 100644 --- a/bootstrap_builder.py +++ b/bootstrap_builder.py @@ -6,7 +6,7 @@ from pyjamaz.hashing import blake2b_256_hash from pyjamaz.models.builder import Instruction, ServiceRegistry from pyjamaz.models.common import Authorizer, RefinementContext, WorkPackage, WorkItem, WorkItemExtrinsic, Preimage -from pyjamaz.rpc.ws_client import WebsocketClient +from pyjamaz.transport.rpc.ws_client import WebsocketClient # Bootstrap service - Helper functions diff --git a/pyjamaz/app.py b/pyjamaz/app.py index 5f0b478d..7373e5b3 100644 --- a/pyjamaz/app.py +++ b/pyjamaz/app.py @@ -71,14 +71,15 @@ def __init__(self, config: AppConfig, import_block_callback: Callable = None): self.state_db: StorageEngine = config.storage_engine.namespace(b"state") self.block_db: StorageEngine = config.storage_engine.namespace(b"block") self.app_db: StorageEngine = config.storage_engine.namespace(b"app") - self.network_bootstrap: bool = False - self.import_queue: List[Block] = [] + + self._import_queue: List[Block] = [] + self._import_queue_lock = asyncio.Lock() + self.pubsub:PubSub = None self.block_context = BlockContext() self.app_context = AppContext() - self.import_lock = asyncio.Lock() self.components = StateComponents( storage_engine=self.state_db, @@ -107,28 +108,6 @@ def __init__(self, config: AppConfig, import_block_callback: Callable = None): self.import_block = self._import_block - async def import_block_from_bytes(self, data): - block = Block.from_jam_bytes(JamBytes(data)) - logging.debug(f"📦 Importing block {block.header.timeslot} from bytes") - self.import_queue.append(block) - - # Note: when we receive a block announcement and we just started our node, we send out a blocks request to sync our state - #TODO!!!!!!NETJES MAKEN!!!!! - if self.network_bootstrap: - # TODO: app.protocol.conn_out is a temporary hack, should do this different, and also allow for sequential back requests until a certain state is reached - if self.protocol.conn_initiated: - self.network_bootstrap = False - # # TODO: determine peer to request blocks from using protocol grid - # # TODO: moeten we hier niet alleen de header meegeven ipv een heel block te serializen????? - # await self.protocol.request_blocks(0, 100, block.to_jam_bytes().to_bytes()) - # # TODO: wel dit block al opslaan en alleen blocks die we vanaf dit Block - elif block.header.parent == bytes(32) or self.retrieve_block_by_hash(block.header.parent): - # Note: If we are able to find the parent of this block, it means we are synced and we can process blocks - await self.process_import_queue() - else: - logging.info(f"Syncing in progress, current timeslot={self.state.timeslot.number}") - - async def import_block_from_json(self, data): logging.debug(f"📦 Importing block from json") block = Block.from_json(data) @@ -137,32 +116,26 @@ async def import_block_from_json(self, data): async def requested_blocks_from_json(self, data): block_list = [Block.from_json(block_data) for block_data in data] - for block in block_list: - self.import_queue.append(Block.from_codec_type(block)) - logging.info(f"📦 Queue block requested #{block.header.timeslot}") + async with self._import_queue_lock: + for block in block_list: + self._import_queue.append(Block.from_codec_type(block)) + logging.info(f"📦 Queue block requested #{block.header.timeslot}") await self.process_import_queue() - async def requested_blocks_from_bytes(self, data): - block_list = Vec(Block.to_codec_def()).new() - block_list.decode(JamBytes(data)) - for block_bytes in block_list: - block = Block.from_codec_type(block_bytes) - self.import_queue.append(block) - - await self.process_import_queue() + async def import_queue_add(self, block): + async with self._import_queue_lock: + self._import_queue.append(block) async def process_import_queue(self): - async with self.import_lock: - sorted_blocks = sorted(self.import_queue, key=lambda x: x.header.timeslot) - self.import_queue = [] + async with self._import_queue_lock: + sorted_blocks = sorted(self._import_queue, key=lambda x: x.header.timeslot) + self._import_queue.clear() for block in sorted_blocks: - # TODO: protocol should only import blocks from this point on -> fix the block_request if self.state.timeslot.number >= block.header.timeslot: - logging.debug(f" TEMP BREAK block from process_import_queue: {block.header.timeslot}") - continue + raise Exception(f" Import queue timeslot mismatch: {self.state.timeslot.number} >= {block.header.timeslot}") await self.import_block(block) logging.debug(f'✅ Block {block.header.timeslot} successfully imported from process_import_queue.') diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 27f3a523..aba97086 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -26,7 +26,7 @@ from pyjamaz.logger import setup_logging from pyjamaz.models.common import ValidatorData from pyjamaz.models.app import Trace, StateDump, ChainspecDump -from pyjamaz.rpc.ws_server import start_rpc_server, WebSocketServer +from pyjamaz.transport.rpc.ws_server import start_rpc_server, WebSocketServer from pyjamaz.settings import GP_VERSION, SOLO_MODE from pyjamaz.state.base import state_key_constructor_service_account, state_key_constructor_preimage, \ state_key_constructor_preimage_availability @@ -34,7 +34,7 @@ from pyjamaz.models.block import Block, Header, Extrinsic from pyjamaz.models.state import JamState, ServiceAccount, ServiceActivityRecord from pyjamaz.transport.cert import generate_cert, write_cert -from pyjamaz.transport.protocol_fs import FSProtocol +#from pyjamaz.transport.protocol_fs import FSProtocol from pyjamaz.transport.jamnp_s.protocol import JAMNPS from pyjamaz.transport.pubsub import PubSub, PubSubSignal @@ -103,12 +103,12 @@ async def produced_block_jamnp(block: Block): return produced_block_jamnp -def wrap_produced_block_fs(app: PyjamazApp, traces_dir, fs_protocol: FSProtocol): - async def produced_block_fs(block: Block): - await app.import_block(block) - await fs_protocol.broadcast_block(block) - - return produced_block_fs +# def wrap_produced_block_fs(app: PyjamazApp, traces_dir, fs_protocol: FSProtocol): +# async def produced_block_fs(block: Block): +# await app.import_block(block) +# await fs_protocol.broadcast_block(block) +# +# return produced_block_fs async def initialize_app( @@ -197,9 +197,7 @@ async def main(ctx, seed, port, ts, culprit, block_dir, record_traces, custom_db db_path = custom_db_path or default_db_path - network_bootstrap = ts is None - if network_bootstrap: - ts = 0 + ts = ts or 0 #TODO: currently it is not possible to provide a hard unix timestamp (only deltas) current_time = time.time() @@ -214,8 +212,6 @@ async def main(ctx, seed, port, ts, culprit, block_dir, record_traces, custom_db except StateKeyNoResult: raise BadParameter(f'DB is not yet initialized; run init first') - app.network_bootstrap = network_bootstrap - logging.info(f'🥋 PyJAMaz JAM client') logging.info(f'🧾 Graypaper version: {GP_VERSION} ') logging.info(f'💾 Storage path: {db_path}') @@ -245,7 +241,6 @@ async def main(ctx, seed, port, ts, culprit, block_dir, record_traces, custom_db # fs_protocol = FSProtocol(block_dir, app) # app.protocol = fs_protocol # app.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, wrap_produced_block_fs(app, record_traces, fs_protocol)) - # app.pubsub.subscribe(MESSAGE_TYPES.RECEIVED_BLOCK, app.import_block_from_json) # app.pubsub.subscribe(MESSAGE_TYPES.REQUESTED_BLOCKS, app.requested_blocks_from_json) # tg.start_soon(fs_protocol.listen) # else: @@ -257,9 +252,6 @@ async def main(ctx, seed, port, ts, culprit, block_dir, record_traces, custom_db nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app) app.protocol = nps_protocol app.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, wrap_produced_block_jamnp(app, record_traces, nps_protocol)) - app.pubsub.subscribe(MESSAGE_TYPES.RECEIVED_BLOCK, app.import_block_from_bytes) - app.pubsub.subscribe(MESSAGE_TYPES.REQUEST_BLOCKS, nps_protocol.request_blocks) - app.pubsub.subscribe(MESSAGE_TYPES.REQUESTED_BLOCKS, app.requested_blocks_from_bytes) tg.start_soon(nps_protocol.listen) if bootnode: diff --git a/pyjamaz/constants.py b/pyjamaz/constants.py index dbfe015f..2a041bdf 100644 --- a/pyjamaz/constants.py +++ b/pyjamaz/constants.py @@ -36,9 +36,6 @@ class MESSAGE_TYPES(Enum): PRODUCED_BLOCK = "PRODUCED_BLOCK" - RECEIVED_BLOCK = "RECEIVED_BLOCK" - REQUEST_BLOCKS = "REQUEST_BLOCKS" - REQUESTED_BLOCKS = "REQUESTED_BLOCKS" STATISTICS = "statistics" SERVICE_ACCOUNT = "service_account" diff --git a/pyjamaz/transport/jamnp_s/connection_acceptor.py b/pyjamaz/transport/jamnp_s/connection_acceptor.py index 884372d4..6476ca96 100644 --- a/pyjamaz/transport/jamnp_s/connection_acceptor.py +++ b/pyjamaz/transport/jamnp_s/connection_acceptor.py @@ -1,15 +1,55 @@ import logging -from aioquic.quic.events import QuicEvent, StreamDataReceived, ConnectionTerminated, HandshakeCompleted +from aioquic.quic.events import QuicEvent, StreamDataReceived, ConnectionTerminated, HandshakeCompleted, StreamReset from pyjamaz.transport.jamnp_s.connection_base import ConnectionBase - +from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP logger = logging.getLogger("pyjamaz.transport.jamnp_s") class ConnectionAcceptor(ConnectionBase): - def quic_event_received(self, event: QuicEvent): - pass + def quic_event_received(self, event: QuicEvent) -> None: + if event and hasattr(event, "stream_id"): + if event.stream_id != 0: + logger.debug(f'ConnectionAcceptor STREAM {event.stream_id} DATA {event}') + else: + logger.debug(f'ConnectionAcceptor received non stream data {event}') + + if isinstance(event, HandshakeCompleted): + # TODO: + # Both nodes are validators, and are neighbours in the grid structure. + # At least one of the nodes is not a validator. + if self.stream_up is not None: + raise Exception("There can be only one UP connection active at a time") + + self.protocol.up0_send_handshake(self) + + elif isinstance(event, StreamReset): + stream_id = event.stream_id + reset_code = event.error_code + + logger.info(f'ConnectionInitiator StreamReset {stream_id} code {reset_code}') + + if stream_id not in self.streams: + raise Exception(f"Stream {stream_id} not available") + + self.streams[stream_id].initiator_reset(reset_code) + + elif isinstance(event, StreamDataReceived): + + stream_id = event.stream_id + + if stream_id not in self.streams: + logging.warning(f"Stream {stream_id} not available") + return + + self.streams[stream_id].receive_data(bytes(event.data)) + + # TODO: handle gracefully + # elif isinstance(event, ConnectionTerminated): + # # Handle connection termination + + diff --git a/pyjamaz/transport/jamnp_s/connection_base.py b/pyjamaz/transport/jamnp_s/connection_base.py index 92037bde..67914344 100644 --- a/pyjamaz/transport/jamnp_s/connection_base.py +++ b/pyjamaz/transport/jamnp_s/connection_base.py @@ -18,15 +18,20 @@ def __init__(self, *args, **kwargs): self.stream_up = None self.streams = {} - self._keepalive_task = asyncio.create_task(self._keepalive()) + #self._keepalive_task = asyncio.create_task(self._keepalive()) - def create_jam_stream(self, StreamCls: Stream): + def open_jam_stream(self, StreamCls: Stream): stream_id = self._quic.get_next_available_stream_id() self.streams[stream_id] = StreamCls(stream_id, connection=self, direction=StreamDirection.initiator) return self.streams[stream_id] + def close_jam_stream(self, stream: Stream): + self._quic.reset_stream(stream.stream_id, error_code=0) + del self.streams[stream.stream_id] + + def send(self, stream_id: int, data, end_stream=False): self._quic.send_stream_data( stream_id, @@ -37,12 +42,11 @@ def send(self, stream_id: int, data, end_stream=False): async def _keepalive(self): - # try: - # while True: - # #TODO: what is a sane amount of time? - # await asyncio.sleep(4) - # self._quic.send_ping(id(self)) - # self.transmit() - # except asyncio.CancelledError: - # pass - pass \ No newline at end of file + try: + while True: + #TODO: what is a sane amount of time? + await asyncio.sleep(4) + self._quic.send_ping(id(self)) + self.transmit() + except asyncio.CancelledError: + pass diff --git a/pyjamaz/transport/jamnp_s/connection_initiator.py b/pyjamaz/transport/jamnp_s/connection_initiator.py index f48d6b2e..36b6b8bb 100644 --- a/pyjamaz/transport/jamnp_s/connection_initiator.py +++ b/pyjamaz/transport/jamnp_s/connection_initiator.py @@ -25,9 +25,7 @@ def quic_event_received(self, event: QuicEvent) -> None: if self.stream_up is not None: raise Exception("There can be only one UP connection active at a time") - stream_up = self.create_jam_stream(StreamUP) - self.stream_up = stream_up - stream_up.initiator_handshake() + self.protocol.up0_send_handshake(self) elif isinstance(event, StreamReset): stream_id = event.stream_id @@ -40,10 +38,6 @@ def quic_event_received(self, event: QuicEvent) -> None: self.streams[stream_id].initiator_reset(reset_code) - # if not self._quic.stream_is_closed(stream_id): - # self._quic.reset_stream(stream_id, error_code=0) - - elif isinstance(event, StreamDataReceived): stream_id = event.stream_id diff --git a/pyjamaz/transport/jamnp_s/message_types.py b/pyjamaz/transport/jamnp_s/message_types.py new file mode 100644 index 00000000..233f1fef --- /dev/null +++ b/pyjamaz/transport/jamnp_s/message_types.py @@ -0,0 +1,199 @@ +from dataclasses import dataclass, field +from enum import Enum +from typing import List, Optional, Dict + +from jamcodec.mixins import Serializable +from jamcodec.types import U32, H256, U8, VarInt64, Vec, Option, Array, Map, H512 + +from pyjamaz.models.block import Header, Guarantee +from pyjamaz.models.common import WorkPackage + + +@dataclass +class MsgUP0Leaf(Serializable): + header_hash: bytes = field(metadata={'codec': H256}) + timeslot: int = field(metadata={'codec': U32}) + + +@dataclass +class MsgUP0Handshake(Serializable): + header_hash: bytes = field(metadata={'codec': H256}) + timeslot: int = field(metadata={'codec': U32}) + leafs: Optional[List[MsgUP0Leaf]] = field(metadata={'codec': Option(Vec(MsgUP0Leaf.to_codec_def()))}) + + +@dataclass +class MsgUP0Announcement(Serializable): + header: Header = field(metadata={'codec': Header.to_codec_def()}) + header_hash: bytes = field(metadata={'codec': H256}) + timeslot: int = field(metadata={'codec': U32}) + + +class MsgCE128BlockRequestDirection(Enum): + ASC = 0 + DESC = 1 + + +@dataclass +class MsgCE128BlockRequest(Serializable): + header_hash: bytes = field(metadata={'codec': H256}) + direction: int = field(metadata={'codec': U8}) + max_blocks: int = field(metadata={'codec': U32}) + + +@dataclass +class MsgCE129KeyRangeRequest(Serializable): + header_hash: bytes = field(metadata={'codec': H256}) + key_start: bytes = field(metadata={'codec': Array(U8, 31)}) + key_end: bytes = field(metadata={'codec': Array(U8, 31)}) + max_size: int = field(metadata={'codec': U32}) + + +@dataclass +class MsgCE131SafroleTicket(Serializable): + attempt: int = field(metadata={'codec': U8}) # 0|1 + proof: bytes = field(metadata={'codec': Array(U8, 784)}) + + +@dataclass +class MsgCE131SafroleTicketDistribution(Serializable): + # Note: CE 131 & 132 + epoch_index: int = field(metadata={'codec': U32}) + ticket: MsgCE131SafroleTicket = field(metadata={'codec': MsgCE131SafroleTicket.to_codec_def()}) + + +@dataclass +class MsgCE132SafroleTicket(MsgCE131SafroleTicket): + pass + + +@dataclass +class MsgCE132SafroleTicketDistribution(MsgCE131SafroleTicketDistribution): + pass + + +@dataclass +class MsgCE133WorkPackageSubmission(Serializable): + core_index: int = field(metadata={'codec': U32}) + work_package: WorkPackage = field(metadata={'codec': WorkPackage.to_codec_def()}) + + +@dataclass +class MsgCE134WorkPackageSharing(Serializable): + core_index: int = field(metadata={'codec': U32}) + segment_root_map: Dict[bytes, bytes] = field(metadata={'codec': Map(H256, H256)}) + + +@dataclass +class MsgCE134WorkPackageBundle(Serializable): + work_package: WorkPackage = field(metadata={'codec': WorkPackage.to_codec_def()}) + + +@dataclass +class MsgCE134RefineResponse(Serializable): + report_hash: bytes = field(metadata={'codec': H256}) + signature: bytes = field(metadata={'codec': H512}) + + +@dataclass +class MsgCE135GuaranteedWorkReport(Guarantee): + pass + + +# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +# +# # ───────────────────────── CE-136 / 143 ──────────────────────── +# @dataclass +# class HashRequest(Serializable): # CE-136 (work-report) & CE-143 (preimage) +# hash: bytes = field(metadata={'codec': H256}) +# +# @dataclass +# class WorkReportEnvelope(Serializable): +# work_report: 'WorkReport' = field(metadata={'codec': WorkReport.to_codec_def()}) +# +# @dataclass +# class Preimage(Serializable): +# bytes_: bytes = field(metadata={'codec': Vec(U8)}) +# +# # ───────────────────────── CE-137 / 138 / 139 / 140 ─────────── +# @dataclass +# class ShardRequestHead(Serializable): # CE-137 & CE-138 share first message +# erasure_root: bytes = field(metadata={'codec': H256}) +# shard_index: int = field(metadata={'codec': U32}) # spec uses u32 +# +# @dataclass +# class SegmentShardRequestHead(Serializable): # CE-139 / 140 +# erasure_root: bytes = field(metadata={'codec': H256}) +# shard_index: int = field(metadata={'codec': U32}) +# segment_indices: List[int] = field( +# metadata={'codec': Vec(U16)} # u16 each +# ) +# +# @dataclass +# class JustificationNode(Serializable): # 0,1,2 tagged union – bytestream +# raw: bytes = field(metadata={'codec': Vec(U8)}) +# +# # reply payloads for 137-140 are raw blobs; dataclasses optional +# +# # ───────────────────────── CE-141 ────────────────────────────── +# @dataclass +# class Assurance(Serializable): +# header_hash: bytes = field(metadata={'codec': H256}) # anchor +# bitfield: bytes = field(metadata={'codec': Vec(U8)}) # len = ceil(C/8) +# signature: bytes = field(metadata={'codec': Array(U8, 64)}) +# +# # ───────────────────────── CE-142 ────────────────────────────── +# @dataclass +# class PreimageAnnouncement(Serializable): +# service_id: int = field(metadata={'codec': U32}) +# hash: bytes = field(metadata={'codec': H256}) +# preimage_length: int = field(metadata={'codec': U32}) +# +# # ───────────────────────── CE-144 ────────────────────────────── +# @dataclass +# class CoreWRPair(Serializable): +# core_index: int = field(metadata={'codec': U32}) +# wr_hash: bytes = field(metadata={'codec': H256}) +# +# @dataclass +# class NoShow(Serializable): +# validator_index: int = field(metadata={'codec': U32}) +# announcement: bytes = field(metadata={'codec': Vec(U8)}) # raw bytes +# +# @dataclass +# class TrancheEvidenceFirst(Serializable): +# signature: bytes = field(metadata={'codec': Array(U8, 96)}) # Bandersnatch +# +# @dataclass +# class TrancheEvidenceSubsequent(Serializable): +# signature: bytes = field(metadata={'codec': Array(U8, 96)}) +# no_shows: List[NoShow] = field(metadata={'codec': Vec(NoShow.to_codec_def())}) +# +# @dataclass +# class AuditAnnouncement(Serializable): +# header_hash: bytes = field(metadata={'codec': H256}) +# tranche: int = field(metadata={'codec': U8}) +# announcement: List[CoreWRPair] = field(metadata={'codec': Vec(CoreWRPair.to_codec_def())}) +# evidence: bytes = field( # discriminated union; raw bytes easiest +# metadata={'codec': Vec(U8)} +# ) +# +# # ───────────────────────── CE-145 ────────────────────────────── +# class JudgmentValidity(Enum): +# INVALID = 0 +# VALID = 1 +# +# @dataclass +# class JudgmentPublication(Serializable): +# epoch_index: int = field(metadata={'codec': U32}) +# validator_index: int = field(metadata={'codec': U32}) +# validity: int = field(metadata={'codec': U8}) # enum above +# wr_hash: bytes = field(metadata={'codec': H256}) +# signature: bytes = field(metadata={'codec': Array(U8, 64)}) +# +# # ----------------------------------------------------------------- +# # Helper type used above (validator sig in CE-135) +# @dataclass +# class ValidatorSig(Serializable): +# validator_index: int = field(metadata={'codec': U32}) +# signature: bytes = field(metadata={'codec': Array(U8, 64)}) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 6d3f03e0..ba45b4a6 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -1,3 +1,4 @@ +import asyncio import logging import ssl @@ -9,10 +10,17 @@ from aioquic.tls import SessionTicket from aioquic.asyncio.client import connect +from jamcodec.types import VarInt64 +from pyjamaz.models.block import Block +from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP + +from pyjamaz.transport.types import ProtocolType +from pyjamaz.transport.jamnp_s.message_types import MsgCE128BlockRequestDirection, MsgCE128BlockRequest, MsgUP0Handshake, MsgUP0Announcement +from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest +from pyjamaz.transport.jamnp_s.connection_base import ConnectionBase from pyjamaz.transport.jamnp_s.connection_initiator import ConnectionInitiator from pyjamaz.transport.jamnp_s.connection_acceptor import ConnectionAcceptor -from pyjamaz.transport.types import ProtocolType logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -45,16 +53,16 @@ class JAMNPS(ProtocolType): PROTOCOL_NAME = "jamnp-s/0/{}" - DIRECTION_ASC = int(0).to_bytes(length=1, byteorder='little') - DIRECTION_DESC = int(1).to_bytes(length=1, byteorder='little') #TODO: add role enum: validator, guarantors, light_client, builder, ... - def __init__(self, host, port, certificate, private_key, app): #, initial_slot_nr, initial_block_hash, role): + def __init__(self, host, port, certificate, private_key, app): self.host = host self.port = port self.pubsub = app.pubsub self.app = app + self.state_requesting_blocks = False + bl_hash = app.retrieve_block_hash(0).hex() if bl_hash.startswith('0x'): bl_hash = bl_hash[2:] bl_hash = bl_hash[:8] @@ -88,6 +96,7 @@ async def listen(self): retry=True, ) + async def connect(self, host, port): configuration = QuicConfiguration( alpn_protocols=[self.protocol_name], @@ -117,25 +126,95 @@ async def connect(self, host, port): logger.warning(f"💩 ClientProtocol Cannot connect to {host}:{port} {exc}") - #TODO: typings for data - async def request_blocks(self, data): - #TODO: temp hack, should extract peer from data - #conn_key = list(self.conn_initiated.keys())[0] - conn_key = (data[0], data[1]) - conn = self.conn_initiated[conn_key] - block_hash = data[2] - direction = data[3] - max_blocks = data[4] + def up0_send_handshake(self, conn: ConnectionBase): + # Create a presistent UP0 stream for the connection + # stream_up = conn.open_jam_stream(StreamUP) + # conn.stream_up = stream_up + # slot = self.app.state.timeslot.number + # header_hash = self.app.retrieve_block_hash(slot) + # leafs = [] #TODO + # handshake = MsgUP0Handshake( + # header_hash=header_hash, + # timeslot=slot, + # leafs=leafs + # ) + # logger.debug(f"Sending Handshake on stream {stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") + # conn.send( + # stream_up.stream_id, + # stream_up.create_message(handshake.to_jam_bytes().to_bytes()), + # ) + + #TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! fix zie hierboven!!!! + stream_up = conn.open_jam_stream(StreamUP) + conn.stream_up = stream_up + slot = self.app.state.timeslot.number + bl_ts = slot.to_bytes(length=4, byteorder='little') #self.conn.protocol.app.state.timeslot.number + bl_hash = self.app.retrieve_block_hash(slot) + final = bl_hash + bl_ts + leafs = bytes() # [bytes.fromhex(h) + s.to_bytes(length=4, byteorder='little') for h, s in []] + leaf_count = VarInt64.encode(len(leafs)).to_bytes() + handshake = final + leaf_count + leafs + logger.debug(f'Sending handshake {handshake} to {conn.host}:{conn.port}') + conn.send( + stream_up.stream_id, + stream_up.stream_type + (len(handshake)).to_bytes(length=4, byteorder='little') + handshake, + ) + + + def up0_received_handshake(self, conn: ConnectionBase, msg: MsgUP0Handshake): + # Note: For now we employ a very simple strategy, where we sync blocks from the first node that announced a finalized block greater than we have + if self.state_requesting_blocks: + return + + block = self.app.retrieve_block_by_hash(msg.header_hash) #TODO: missch een efficientere exists check toevoegen + #TODO: ook nog iets doen met slot en leafs ook mee requesten? + if not block: + logger.debug(f"Received newer block from handshake: {msg.header_hash} request blocks") + self.state_requesting_blocks = True + #self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(msg.header_hash, MsgCE128BlockRequestDirection.DESC.value, 1)) #TODO: max_blocks=1 for now, >1 results in error from node? + slot = self.app.state.timeslot.number + bl_hash = self.app.retrieve_block_hash(slot) + self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(bl_hash, MsgCE128BlockRequestDirection.ASC.value, 1)) #TODO: max_blocks=1 for now, >1 results in error from node? + + + def up0_received_announcement(self, conn: ConnectionBase, msg: MsgUP0Announcement): + # Note: For now we employ a very simple strategy, where we sync blocks from the first node that announced a finalized block greater than we have + if self.state_requesting_blocks: + return + + block = self.app.retrieve_block_by_hash(msg.header.hash) #TODO: missch een efficientere exists check toevoegen + #TODO: ook nog iets doen met bijhorende finalized header_hash & slot? + if not block: + logger.debug(f"Received new block announcement from up0: {msg.header.hash}") + self.state_requesting_blocks = True + self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(msg.header.hash, MsgCE128BlockRequestDirection.ASC.value, 1)) #TODO: max_blocks=1 for now, >1 results in error from node? + + + def ce128_initiate_block_request(self, conn: ConnectionBase, req: MsgCE128BlockRequest): + stream = conn.open_jam_stream(StreamBlockRequest) + print(f"PROTOCOL INITIATING BLOCK REQUEST ON STREAMID: {stream.stream_id} header hash: {req.header_hash} direction: {req.direction}, max_block: {req.max_blocks}") + conn.send( + stream.stream_id, + stream.create_message(req.to_jam_bytes().to_bytes()), + end_stream=True + ) + + + def ce128_received_block_request(self, conn: ConnectionBase, block: Block): + print(f"PROTOCOL RECEIVED BLOCK REQUEST") + asyncio.create_task(self.app.import_queue_add(block)) + self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(block.header.hash, MsgCE128BlockRequestDirection.ASC.value, 1)) # TODO: max_blocks=1 for now, >1 results in error from node? + - from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest - stream_req = conn.create_jam_stream(StreamBlockRequest) - print(f"CALLING BLOCK REQUEST ON STREAMID: {stream_req.stream_id} header hash: {block_hash} direction: {direction}, max_block: {max_blocks}") - stream_req.initiator_block_request(block_hash, direction, max_blocks) + def ce128_finished_block_request(self): + self.state_requesting_blocks = False + logger.debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!Finished block request") + asyncio.create_task(self.app.process_import_queue()) async def broadcast_block(self, block): block_bytes = block.to_jam_bytes().to_bytes() - logger.debug(f'ServerProtocol broadcasting block announcement to {len(self.conn_accepted)} clients') + logger.debug(f'JAMNP broadcasting block announcement to {len(self.conn_accepted)} clients') for client_id, client in self.conn_accepted.items(): - logger.debug(f"ServerProtocol send block to client {client}") + logger.debug(f"JAMNP send block to client {client}") await client.send_block_announcement(block_bytes) diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py index 3f6ed2ab..b635d524 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -2,7 +2,7 @@ from enum import Enum -logger = logging.getLogger("pyjamaz.transport.jamnp_s.streams") +logger = logging.getLogger("pyjamaz.transport.jamnp_s") class StreamType(Enum): @@ -38,6 +38,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_id = stream_id self.stream_type = None # Note: override in subclass self.conn = connection + self.protocol = connection.protocol self.direction = direction self._msg_buffer = b"" @@ -57,6 +58,10 @@ def acceptor_message(self, data: bytes): raise Exception("Override this method in Stream subclass") + def create_message(self, payload: bytes): + return self.stream_type + (len(payload)).to_bytes(length=4, byteorder='little') + payload + + def receive_data(self, data: bytes): # Note: Parse bytes until stream data is empty: https://github.com/microsoft/msquic/discussions/2037 @@ -95,8 +100,8 @@ def receive_data(self, data: bytes): def initiator_reset(self, reset_code: int): - del self.conn.streams[self.stream_id] + self.conn.close_jam_stream(self) def acceptor_reset(self, reset_code: int): - del self.conn.streams[self.stream_id] + self.conn.close_jam_stream(self) diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py index d96e41eb..6e31ac74 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -1,12 +1,21 @@ -import asyncio +import enum +import logging +from typing import List, Optional from jamcodec.base import JamBytes from jamcodec.types import U32, VarInt64 -from pyjamaz.constants import MESSAGE_TYPES - +from pyjamaz.models.block import Header +from pyjamaz.transport.jamnp_s.message_types import MsgUP0Handshake, MsgUP0Leaf, MsgUP0Announcement from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection -from pyjamaz.transport.pubsub import PubSubSignal + + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class UPState(enum.Enum): + IN_PROGRESS = 0 + CONNECTED = 1 class StreamUP(Stream): @@ -14,64 +23,54 @@ class StreamUP(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) self.stream_type = StreamType.UP0_BlockAnnouncement.value.to_bytes(length=1, byteorder='little') - self.handshake_complete = False - - - def initiator_handshake(self): - """ - TODO: - Both sides should begin by sending a handshake message containing all known leaves (descendants of the latest finalized block with no known children). - The header hash and slot of the latest finalized block should be included in the handshake message and also in every announcement message that is sent. - """ - bl_hash = self.conn.protocol.app.retrieve_block_hash(0) - bl_ts = int(0).to_bytes(length=4, byteorder='little') #self.conn.protocol.app.state.timeslot.number - final = bl_hash + bl_ts - print(f"HANDSHAKE FINAL: {bl_hash} {bl_ts} {final}") - # TODO: vraag app naar leafs vanaf bl_hash - leafs = bytes() # [bytes.fromhex(h) + s.to_bytes(length=4, byteorder='little') for h, s in []] - leaf_count = VarInt64.encode(len(leafs)).to_bytes() - handshake = final + leaf_count + leafs - - self.conn.send( - self.stream_id, - self.stream_type + (len(handshake)).to_bytes(length=4, byteorder='little') + handshake, - ) + self.state = UPState.IN_PROGRESS def initiator_message(self, data: bytes): - #print(f"RECEIVED UP0 MSG: {len(data)}") - - if not self.handshake_complete: - header_hash = data[:32].hex() - slot = int.from_bytes(data[32:36], byteorder='little') - - jam_bytes = JamBytes(data[36:]) - leaf_count = VarInt64.decode(jam_bytes) - - for leaf_nr in range(leaf_count): - leaf_hash = jam_bytes.get_next_bytes(32).hex() - leaf_slot = U32.decode(jam_bytes) - #print(f"leaf: {leaf_hash} {leaf_slot}") - - #print(f"HANDSHAKE: {header_hash} {slot} {leaf_count} remaining: {jam_bytes.get_remaining_length()}") - self.handshake_complete = True - - #self.conn._quic.reset_stream(self.stream_id, error_code=0x0) - latest_block_hash = self.conn.protocol.app.retrieve_block_hash(self.conn.protocol.app.state.timeslot.number) - #TODO: check if our latest hash is older than what we received with the handshake - print(f"REQUEST BLOCKS AFTER HANDSHAKE FOR {latest_block_hash.hex()} timeslot: {self.conn.protocol.app.state.timeslot.number}") - #TODO: nette dataclass encoders/decoders maken hiervoor!!! - from pyjamaz.transport.jamnp_s.protocol import JAMNPS - asyncio.create_task(self.conn.protocol.app.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.REQUEST_BLOCKS, data=[self.conn.host, self.conn.port, latest_block_hash, JAMNPS.DIRECTION_ASC, 1]))) - return - - # After handshake is completed, we receive Announcement messages - # jam_bytes = JamBytes(data) - # header = Header.from_jam_bytes(jam_bytes) - # header_hash = jam_bytes.get_next_bytes(32).hex() - # slot = U32.decode(jam_bytes) - # #print(f"UP0 Update: {header} {header_hash} {slot}") - # #MESSAGE_TYPES.RECEIVED_BLOCK -> moet ook weer checken of we dit block niet al hebben + + match self.state: + + case UPState.IN_PROGRESS: + self.state = UPState.CONNECTED + + #msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) + ############################ + #TODO:FIX!!!!!! zie hierboven + header_hash = data[:32] #.hex() + slot = int.from_bytes(data[32:36], byteorder='little') + + jam_bytes = JamBytes(data[36:]) + leaf_count = VarInt64.decode(jam_bytes) + + logger.debug(f"Received handshake response: header_hash={header_hash} slot={slot} leaf_count={leaf_count}") + + leafs = [] + for leaf_nr in range(leaf_count): + leaf_hash = jam_bytes.get_next_bytes(32) #.hex() + leaf_slot = U32.decode(jam_bytes) + leafs.append(MsgUP0Leaf(leaf_hash, leaf_slot)) + msg = MsgUP0Handshake( + header_hash=header_hash, + timeslot=slot, + leafs=leafs + ) + ############################################ + + self.protocol.up0_received_handshake(self.conn, msg) + + case UPState.CONNECTED: + # After handshake is completed, we receive Announcement messages + # jam_bytes = JamBytes(data) + # header = Header.from_jam_bytes(jam_bytes) + # header_hash = jam_bytes.get_next_bytes(32).hex() + # slot = U32.decode(jam_bytes) + # logger.debug(f"Received up0 announcement: header={header.hash} header_hash={header_hash} slot={slot}") + # self.protocol.up0_received_announcement(header, header_hash, slot) + msg = MsgUP0Announcement.from_jam_bytes(JamBytes(data)) + self.protocol.up0_received_announcement(self.conn, msg) + + case _: + raise RuntimeError(f"Unexpected state {self.state}") def acceptor_message(self, data: bytes): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py index b78ac9e6..d8940e48 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -1,13 +1,9 @@ -import asyncio import logging from jamcodec.base import JamBytes -from pyjamaz.constants import MESSAGE_TYPES from pyjamaz.models.block import Block -from pyjamaz.transport.jamnp_s.protocol import JAMNPS from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection -from pyjamaz.transport.pubsub import PubSubSignal logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -19,34 +15,25 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type = StreamType.CE128_BlockRequest.value.to_bytes(length=1, byteorder='little') - def initiator_block_request(self, header_hash: bytes, direction: bytes, max_blocks: int): - #TODO: nette data classes maken voor messages vd streams! - #direction_byte = direction.to_bytes(length=1, byteorder='little') - max_blocks_bytes = max_blocks.to_bytes(length=4, byteorder='little') - #direction = int(1).to_bytes(length=1, byteorder='little') - #max_blocks = int(0).to_bytes(length=4, byteorder='little') - payload = header_hash + direction + max_blocks_bytes - - print(f"SENDING BLOCK REQUEST streamid {self.stream_id} payload: {self.stream_type + (len(payload)).to_bytes(length=4, byteorder='little') + payload} end_stream=True") - - self.conn.send( - self.stream_id, - self.stream_type + (len(payload)).to_bytes(length=4, byteorder='little') + payload, - end_stream=True - ) + def initiator_reset(self, reset_code: int): + print(f"CE128 RECEIVED RESET: {reset_code}") + self.protocol.ce128_finished_block_request() + super().initiator_reset(reset_code) def initiator_message(self, data: bytes): print(f"RECEIVED INITIATED CE128 MSG: {len(data)}") if len(data) == 0: #int.from_bytes(b'\x00\x00\x00\x00') - logger.debug(f'!!!!!!!!!ConnectionInitiator StreamBlockRequest.initiator_message received empty data') + #TODO: send FIN? Let protocol know were finished? + print(f'!!!!!!!!!ConnectionInitiator StreamBlockRequest.initiator_message received empty data') + self.initiator_reset(reset_code=0) return block = Block.from_jam_bytes(JamBytes(data)) - latest_block_hash = block.header.hash - print(f"Parsed block: {block.header.hash.hex()} parent:{block.header.parent.hex()}") + block_hash = block.header.hash + print(f"Parsed block: {block_hash.hex()} parent:{block.header.parent.hex()}") - asyncio.create_task(self.conn.protocol.app.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.REQUEST_BLOCKS, data=[self.conn.host, self.conn.port, latest_block_hash, JAMNPS.DIRECTION_ASC, 1]))) + self.protocol.ce128_received_block_request(self.conn, block) def acceptor_message(self, data: bytes): diff --git a/pyjamaz/transport/protocol_fs.py b/pyjamaz/transport/protocol_fs.py index cb5a4029..f5d68829 100644 --- a/pyjamaz/transport/protocol_fs.py +++ b/pyjamaz/transport/protocol_fs.py @@ -1,81 +1,81 @@ -import json -import logging -import os -import anyio -from jamcodec.base import JamBytes - -from pyjamaz.constants import MESSAGE_TYPES -from pyjamaz.models.block import Block -from pyjamaz.transport.pubsub import PubSubSignal -from pyjamaz.transport.types import ProtocolType - - -logger = logging.getLogger("pyjamaz.transport") - - -class FSProtocol(ProtocolType): - - def __init__(self, block_dir, app): - self.block_dir = block_dir - self.lock = anyio.Lock() - self.pubsub = app.pubsub - self.app = app - - async def listen(self): - - seen_files = set() - - while True: - # Run the directory check in a separate thread (non-blocking) - new_files = await anyio.to_thread.run_sync( - lambda: {f for f in os.listdir(self.block_dir) if f.startswith('block-')} - seen_files - ) - - if new_files: - for filename in sorted(new_files): - filepath = os.path.join(self.block_dir, filename) - - try: - async with self.lock: - - with open(filepath, 'r') as file: - if filename.startswith("block-req-"): - await self.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.REQUESTED_BLOCKS, data=json.load(file))) - - else: - await self.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.RECEIVED_BLOCK,data=json.load(file))) - - except Exception as e: - logging.error(f"Failed to process {filepath}: {e}") - - # Update the seen_files set to include the newly processed files - seen_files.update(new_files) - - await anyio.sleep(.5) - - - async def request_blocks(self, direction, max_blocks, block_bytes): - block = Block.from_jam_bytes(JamBytes(block_bytes)) - blocks = [] - # TODO: take direction and max_blocks into account - # TODO: we decode and serialize blocks unnecessary here, improve! - while block.header.parent != bytes(32): - block = self.app.retrieve_block_by_hash(block.header.parent) - if not block: - break - blocks.append(block) - - serialized_blocks = [b.to_json() for b in blocks] - - filepath = os.path.join(self.block_dir, f'block-req-{block.header.timeslot:06}.json') - with open(filepath, 'w') as file: - json.dump(serialized_blocks, file, indent=2) - - - async def broadcast_block(self, block): - # write block to dir - async with self.lock: - filepath = os.path.join(self.block_dir, f'block-{block.header.timeslot:06}.json') - with open(filepath, 'w') as file: - logger.debug(f"FSProtocol dump block to disk: {filepath}") - json.dump(block.to_json(), file, indent=2) +# import json +# import logging +# import os +# import anyio +# from jamcodec.base import JamBytes +# +# from pyjamaz.constants import MESSAGE_TYPES +# from pyjamaz.models.block import Block +# from pyjamaz.transport.pubsub import PubSubSignal +# from pyjamaz.transport.types import ProtocolType +# +# +# logger = logging.getLogger("pyjamaz.transport") +# +# +# class FSProtocol(ProtocolType): +# +# def __init__(self, block_dir, app): +# self.block_dir = block_dir +# self.lock = anyio.Lock() +# self.pubsub = app.pubsub +# self.app = app +# +# async def listen(self): +# +# seen_files = set() +# +# while True: +# # Run the directory check in a separate thread (non-blocking) +# new_files = await anyio.to_thread.run_sync( +# lambda: {f for f in os.listdir(self.block_dir) if f.startswith('block-')} - seen_files +# ) +# +# if new_files: +# for filename in sorted(new_files): +# filepath = os.path.join(self.block_dir, filename) +# +# try: +# async with self.lock: +# +# with open(filepath, 'r') as file: +# if filename.startswith("block-req-"): +# await self.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.REQUESTED_BLOCKS, data=json.load(file))) +# +# else: +# await self.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.RECEIVED_BLOCK,data=json.load(file))) +# +# except Exception as e: +# logging.error(f"Failed to process {filepath}: {e}") +# +# # Update the seen_files set to include the newly processed files +# seen_files.update(new_files) +# +# await anyio.sleep(.5) +# +# +# async def request_blocks(self, direction, max_blocks, block_bytes): +# block = Block.from_jam_bytes(JamBytes(block_bytes)) +# blocks = [] +# # TODO: take direction and max_blocks into account +# # TODO: we decode and serialize blocks unnecessary here, improve! +# while block.header.parent != bytes(32): +# block = self.app.retrieve_block_by_hash(block.header.parent) +# if not block: +# break +# blocks.append(block) +# +# serialized_blocks = [b.to_json() for b in blocks] +# +# filepath = os.path.join(self.block_dir, f'block-req-{block.header.timeslot:06}.json') +# with open(filepath, 'w') as file: +# json.dump(serialized_blocks, file, indent=2) +# +# +# async def broadcast_block(self, block): +# # write block to dir +# async with self.lock: +# filepath = os.path.join(self.block_dir, f'block-{block.header.timeslot:06}.json') +# with open(filepath, 'w') as file: +# logger.debug(f"FSProtocol dump block to disk: {filepath}") +# json.dump(block.to_json(), file, indent=2) diff --git a/pyjamaz/rpc/blaas_playbook.py b/pyjamaz/transport/rpc/blaas_playbook.py similarity index 99% rename from pyjamaz/rpc/blaas_playbook.py rename to pyjamaz/transport/rpc/blaas_playbook.py index 6030446f..65102132 100644 --- a/pyjamaz/rpc/blaas_playbook.py +++ b/pyjamaz/transport/rpc/blaas_playbook.py @@ -5,7 +5,7 @@ from pyjamaz.models.builder import Instruction from pyjamaz.models.common import Authorizer, RefinementContext, WorkPackage, WorkItem, WorkItemExtrinsic from pyjamaz.pvm.types import PVMProgram -from pyjamaz.rpc.ws_client import WebsocketClient +from pyjamaz.transport.rpc.ws_client import WebsocketClient async def create_bootservice_workpackage(client, instruction: Instruction, extrinsic: List[bytes]): diff --git a/pyjamaz/rpc/interface.py b/pyjamaz/transport/rpc/interface.py similarity index 100% rename from pyjamaz/rpc/interface.py rename to pyjamaz/transport/rpc/interface.py diff --git a/pyjamaz/rpc/parity_playbook.py b/pyjamaz/transport/rpc/parity_playbook.py similarity index 99% rename from pyjamaz/rpc/parity_playbook.py rename to pyjamaz/transport/rpc/parity_playbook.py index 6030446f..65102132 100644 --- a/pyjamaz/rpc/parity_playbook.py +++ b/pyjamaz/transport/rpc/parity_playbook.py @@ -5,7 +5,7 @@ from pyjamaz.models.builder import Instruction from pyjamaz.models.common import Authorizer, RefinementContext, WorkPackage, WorkItem, WorkItemExtrinsic from pyjamaz.pvm.types import PVMProgram -from pyjamaz.rpc.ws_client import WebsocketClient +from pyjamaz.transport.rpc.ws_client import WebsocketClient async def create_bootservice_workpackage(client, instruction: Instruction, extrinsic: List[bytes]): diff --git a/pyjamaz/rpc/playbook.py b/pyjamaz/transport/rpc/playbook.py similarity index 99% rename from pyjamaz/rpc/playbook.py rename to pyjamaz/transport/rpc/playbook.py index 14071420..cebca8f0 100644 --- a/pyjamaz/rpc/playbook.py +++ b/pyjamaz/transport/rpc/playbook.py @@ -4,7 +4,7 @@ from pyjamaz.models.common import Authorizer, RefinementContext, WorkPackage, WorkItem, WorkItemExtrinsic from pyjamaz.pvm.types import PVMProgram from pyjamaz.pvm.utils import count_leading_zeroes, count_trailing_zeroes, reverse_bits_32 -from pyjamaz.rpc.ws_client import WebsocketClient +from pyjamaz.transport.rpc.ws_client import WebsocketClient async def create_workpackage(client): diff --git a/pyjamaz/rpc/rpc.py b/pyjamaz/transport/rpc/rpc.py similarity index 100% rename from pyjamaz/rpc/rpc.py rename to pyjamaz/transport/rpc/rpc.py diff --git a/pyjamaz/rpc/ws_client.py b/pyjamaz/transport/rpc/ws_client.py similarity index 97% rename from pyjamaz/rpc/ws_client.py rename to pyjamaz/transport/rpc/ws_client.py index 4d8dc34b..bef57436 100644 --- a/pyjamaz/rpc/ws_client.py +++ b/pyjamaz/transport/rpc/ws_client.py @@ -7,8 +7,8 @@ from pyjamaz.models.common import Authorizer, RefinementContext, WorkPackage, WorkItem from pyjamaz.models.state import ServiceAccount -from pyjamaz.rpc.interface import RPCMethods -from pyjamaz.rpc.rpc import generate_req_id, jsonapi_parse, RPCCallException, jsonapi_request +from pyjamaz.transport.rpc.interface import RPCMethods +from pyjamaz.transport.rpc.rpc import generate_req_id, jsonapi_parse, RPCCallException, jsonapi_request class WebsocketClient(RPCMethods): diff --git a/pyjamaz/rpc/ws_server.py b/pyjamaz/transport/rpc/ws_server.py similarity index 96% rename from pyjamaz/rpc/ws_server.py rename to pyjamaz/transport/rpc/ws_server.py index ac8d2eda..a4c539f6 100644 --- a/pyjamaz/rpc/ws_server.py +++ b/pyjamaz/transport/rpc/ws_server.py @@ -7,9 +7,9 @@ from typing import Set import websockets -from pyjamaz.rpc.rpc import RPC_REQUESTS, RPC_TYPE_SUBSCRIBE, RPC_TYPE_UNSUBSCRIBE, jsonapi_response, jsonapi_parse, RPCCallException, jsonapi_ws_subscribed, \ +from pyjamaz.transport.rpc.rpc import RPC_REQUESTS, RPC_TYPE_SUBSCRIBE, RPC_TYPE_UNSUBSCRIBE, jsonapi_response, jsonapi_parse, RPCCallException, jsonapi_ws_subscribed, \ jsonapi_ws_response, RPC_ERROR, jsonapi_error -from pyjamaz.rpc.ws_server_subscriptions import SubscriptionManager +from pyjamaz.transport.rpc.ws_server_subscriptions import SubscriptionManager if typing.TYPE_CHECKING: diff --git a/pyjamaz/rpc/ws_server_subscriptions.py b/pyjamaz/transport/rpc/ws_server_subscriptions.py similarity index 98% rename from pyjamaz/rpc/ws_server_subscriptions.py rename to pyjamaz/transport/rpc/ws_server_subscriptions.py index a689acb4..1432ca21 100644 --- a/pyjamaz/rpc/ws_server_subscriptions.py +++ b/pyjamaz/transport/rpc/ws_server_subscriptions.py @@ -9,10 +9,10 @@ from pyjamaz.constants import MESSAGE_TYPES from pyjamaz.hashing import blake2b_256_hash from pyjamaz.models.block import Block -from pyjamaz.rpc.rpc import generate_req_id, jsonapi_ws_response, RPCCallException, RPC_ERROR +from pyjamaz.transport.rpc.rpc import generate_req_id, jsonapi_ws_response, RPCCallException, RPC_ERROR if typing.TYPE_CHECKING: - from pyjamaz.rpc.ws_server import WebSocketServer + from pyjamaz.transport.rpc.ws_server import WebSocketServer class WSubscription(ABC): diff --git a/pyjamaz/transport/types.py b/pyjamaz/transport/types.py index 1366d130..5cf0c3cd 100644 --- a/pyjamaz/transport/types.py +++ b/pyjamaz/transport/types.py @@ -9,7 +9,7 @@ class ProtocolType(Protocol): async def listen(self): """ Starts this protocol, start listening for messages """ - #TODO: typings on data -> create dedicated Message dataclasses + #TODO: deprecated? -> sync Protocol with necesary features async def request_blocks(self, data): """ Starts a Blocks Request, to fetch a range of blocks from a given peer """ From fd88c4334f15ac29d4ab696124931bae1b1e0b05 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 3 Jul 2025 20:03:06 +0200 Subject: [PATCH 05/51] - JAMSNP WIP --- pyjamaz/cli.py | 2 +- pyjamaz/transport/jamnp_s/connection.py | 105 ++++++++++++++++++ .../transport/jamnp_s/connection_acceptor.py | 55 --------- pyjamaz/transport/jamnp_s/connection_base.py | 52 --------- .../transport/jamnp_s/connection_initiator.py | 55 --------- pyjamaz/transport/jamnp_s/message_types.py | 10 +- pyjamaz/transport/jamnp_s/protocol.py | 93 ++++++++++++---- pyjamaz/transport/jamnp_s/streams/stream_0.py | 11 +- .../transport/jamnp_s/streams/stream_128.py | 4 +- pyjamaz/transport/types.py | 7 -- 10 files changed, 189 insertions(+), 205 deletions(-) create mode 100644 pyjamaz/transport/jamnp_s/connection.py delete mode 100644 pyjamaz/transport/jamnp_s/connection_acceptor.py delete mode 100644 pyjamaz/transport/jamnp_s/connection_base.py delete mode 100644 pyjamaz/transport/jamnp_s/connection_initiator.py diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index aba97086..db583288 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -98,7 +98,7 @@ async def cli_import_block(self, block: Block, dry_run=False): def wrap_produced_block_jamnp(app: PyjamazApp, traces_dir, np_protocol: JAMNPS): async def produced_block_jamnp(block: Block): - await np_protocol.broadcast_block(block) + await np_protocol.up0_broadcast_block(block) return produced_block_jamnp diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py new file mode 100644 index 00000000..f066ef91 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -0,0 +1,105 @@ +import asyncio +import logging + +from aioquic.asyncio import QuicConnectionProtocol, serve +from aioquic.quic.events import QuicEvent, HandshakeCompleted, StreamReset, StreamDataReceived + +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamDirection + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class JAMConnection(QuicConnectionProtocol): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # Note: should be set in wrap_protocol + self.direction:StreamDirection = None + self.protocol = None + self.host = None + self.port = None + + self.stream_up = None + self.streams = {} + #self._keepalive_task = asyncio.create_task(self._keepalive()) + + + def open_jam_stream(self, StreamCls: Stream): + stream_id = self._quic.get_next_available_stream_id() + self.streams[stream_id] = StreamCls(stream_id, connection=self, direction=self.direction) + return self.streams[stream_id] + + + def close_jam_stream(self, stream: Stream, reason:int=0): + self._quic.reset_stream(stream.stream_id, error_code=reason) + del self.streams[stream.stream_id] + + + def send(self, stream_id: int, data, end_stream=False): + self._quic.send_stream_data( + stream_id, + data, + end_stream=end_stream + ) + self.transmit() + + + async def _keepalive(self): + try: + while True: + #TODO: what is a sane amount of time? + await asyncio.sleep(4) + self._quic.send_ping(id(self)) + self.transmit() + except asyncio.CancelledError: + pass + + + def quic_event_received(self, event: QuicEvent) -> None: + + if event and hasattr(event, "stream_id"): + if event.stream_id != 0: + logger.debug(f'Stream {event.stream_id} received data {event}') + else: + logger.debug(f'Received non stream data {event}') + + if isinstance(event, HandshakeCompleted): + #TODO: + # Both nodes are validators, and are neighbours in the grid structure. + # At least one of the nodes is not a validator. + if self.stream_up is not None: + raise Exception("There can be only one UP connection active at a time") + + self.protocol.up0_send_handshake(self) + + elif isinstance(event, StreamReset): + stream_id = event.stream_id + reset_code = event.error_code + + logger.info(f'StreamReset {stream_id} {self.direction} code {reset_code}') + + if stream_id not in self.streams: + raise Exception(f"Stream {stream_id} not available") + + if self.direction == StreamDirection.initiator: + self.streams[stream_id].initiator_reset(reset_code) + elif self.direction == StreamDirection.acceptor: + self.streams[stream_id].acceptor_reset(reset_code) + + elif isinstance(event, StreamDataReceived): + + stream_id = event.stream_id + + if stream_id not in self.streams: + #logging.warning(f"Stream {stream_id} not available") + #return + raise Exception(f"Stream {stream_id} not available") + + self.streams[stream_id].receive_data(bytes(event.data)) + + + # TODO: handle gracefully + # elif isinstance(event, ConnectionTerminated): + # # Handle connection termination + diff --git a/pyjamaz/transport/jamnp_s/connection_acceptor.py b/pyjamaz/transport/jamnp_s/connection_acceptor.py deleted file mode 100644 index 6476ca96..00000000 --- a/pyjamaz/transport/jamnp_s/connection_acceptor.py +++ /dev/null @@ -1,55 +0,0 @@ -import logging - -from aioquic.quic.events import QuicEvent, StreamDataReceived, ConnectionTerminated, HandshakeCompleted, StreamReset - -from pyjamaz.transport.jamnp_s.connection_base import ConnectionBase -from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class ConnectionAcceptor(ConnectionBase): - - def quic_event_received(self, event: QuicEvent) -> None: - if event and hasattr(event, "stream_id"): - if event.stream_id != 0: - logger.debug(f'ConnectionAcceptor STREAM {event.stream_id} DATA {event}') - else: - logger.debug(f'ConnectionAcceptor received non stream data {event}') - - if isinstance(event, HandshakeCompleted): - # TODO: - # Both nodes are validators, and are neighbours in the grid structure. - # At least one of the nodes is not a validator. - if self.stream_up is not None: - raise Exception("There can be only one UP connection active at a time") - - self.protocol.up0_send_handshake(self) - - elif isinstance(event, StreamReset): - stream_id = event.stream_id - reset_code = event.error_code - - logger.info(f'ConnectionInitiator StreamReset {stream_id} code {reset_code}') - - if stream_id not in self.streams: - raise Exception(f"Stream {stream_id} not available") - - self.streams[stream_id].initiator_reset(reset_code) - - elif isinstance(event, StreamDataReceived): - - stream_id = event.stream_id - - if stream_id not in self.streams: - logging.warning(f"Stream {stream_id} not available") - return - - self.streams[stream_id].receive_data(bytes(event.data)) - - # TODO: handle gracefully - # elif isinstance(event, ConnectionTerminated): - # # Handle connection termination - - - diff --git a/pyjamaz/transport/jamnp_s/connection_base.py b/pyjamaz/transport/jamnp_s/connection_base.py deleted file mode 100644 index 67914344..00000000 --- a/pyjamaz/transport/jamnp_s/connection_base.py +++ /dev/null @@ -1,52 +0,0 @@ -import asyncio -import logging -from aioquic.asyncio import QuicConnectionProtocol, serve - -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class ConnectionBase(QuicConnectionProtocol): - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - self.protocol = None # Note: should be set in wrap_protocol - self.host = None - self.port = None - - self.stream_up = None - self.streams = {} - #self._keepalive_task = asyncio.create_task(self._keepalive()) - - - def open_jam_stream(self, StreamCls: Stream): - stream_id = self._quic.get_next_available_stream_id() - self.streams[stream_id] = StreamCls(stream_id, connection=self, direction=StreamDirection.initiator) - return self.streams[stream_id] - - - def close_jam_stream(self, stream: Stream): - self._quic.reset_stream(stream.stream_id, error_code=0) - del self.streams[stream.stream_id] - - - def send(self, stream_id: int, data, end_stream=False): - self._quic.send_stream_data( - stream_id, - data, - end_stream=end_stream - ) - self.transmit() - - - async def _keepalive(self): - try: - while True: - #TODO: what is a sane amount of time? - await asyncio.sleep(4) - self._quic.send_ping(id(self)) - self.transmit() - except asyncio.CancelledError: - pass diff --git a/pyjamaz/transport/jamnp_s/connection_initiator.py b/pyjamaz/transport/jamnp_s/connection_initiator.py deleted file mode 100644 index 36b6b8bb..00000000 --- a/pyjamaz/transport/jamnp_s/connection_initiator.py +++ /dev/null @@ -1,55 +0,0 @@ -import logging - -from aioquic.quic.events import QuicEvent, StreamDataReceived, ConnectionTerminated, HandshakeCompleted, StreamReset - -from pyjamaz.transport.jamnp_s.connection_base import ConnectionBase -from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class ConnectionInitiator(ConnectionBase): - - - def quic_event_received(self, event: QuicEvent) -> None: - if event and hasattr(event, "stream_id"): - if event.stream_id != 0: - logger.debug(f'ConnectionInitiator STREAM {event.stream_id} DATA {event}') - else: - logger.debug(f'ConnectionInitiator received non stream data {event}') - - if isinstance(event, HandshakeCompleted): - #TODO: - # Both nodes are validators, and are neighbours in the grid structure. - # At least one of the nodes is not a validator. - if self.stream_up is not None: - raise Exception("There can be only one UP connection active at a time") - - self.protocol.up0_send_handshake(self) - - elif isinstance(event, StreamReset): - stream_id = event.stream_id - reset_code = event.error_code - - logger.info(f'ConnectionInitiator StreamReset {stream_id} code {reset_code}') - - if stream_id not in self.streams: - raise Exception(f"Stream {stream_id} not available") - - self.streams[stream_id].initiator_reset(reset_code) - - elif isinstance(event, StreamDataReceived): - - stream_id = event.stream_id - - if stream_id not in self.streams: - logging.warning(f"Stream {stream_id} not available") - return - - self.streams[stream_id].receive_data(bytes(event.data)) - - - # TODO: handle gracefully - # elif isinstance(event, ConnectionTerminated): - # # Handle connection termination - diff --git a/pyjamaz/transport/jamnp_s/message_types.py b/pyjamaz/transport/jamnp_s/message_types.py index 233f1fef..1a9e6c6e 100644 --- a/pyjamaz/transport/jamnp_s/message_types.py +++ b/pyjamaz/transport/jamnp_s/message_types.py @@ -5,7 +5,7 @@ from jamcodec.mixins import Serializable from jamcodec.types import U32, H256, U8, VarInt64, Vec, Option, Array, Map, H512 -from pyjamaz.models.block import Header, Guarantee +from pyjamaz.models.block import Header, Guarantee, Block from pyjamaz.models.common import WorkPackage @@ -19,7 +19,8 @@ class MsgUP0Leaf(Serializable): class MsgUP0Handshake(Serializable): header_hash: bytes = field(metadata={'codec': H256}) timeslot: int = field(metadata={'codec': U32}) - leafs: Optional[List[MsgUP0Leaf]] = field(metadata={'codec': Option(Vec(MsgUP0Leaf.to_codec_def()))}) + #leafs: Optional[List[MsgUP0Leaf]] = field(metadata={'codec': Option(Vec(MsgUP0Leaf.to_codec_def()))}) + leafs: List[MsgUP0Leaf] = field(metadata={'codec': Vec(MsgUP0Leaf.to_codec_def())}) @dataclass @@ -41,6 +42,11 @@ class MsgCE128BlockRequest(Serializable): max_blocks: int = field(metadata={'codec': U32}) +@dataclass +class MsgCE128BlockRequestResponse(Serializable): + blocks: List[Block] = field(metadata={'codec': Vec(Block.to_codec_def())}) + + @dataclass class MsgCE129KeyRangeRequest(Serializable): header_hash: bytes = field(metadata={'codec': H256}) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index ba45b4a6..0a4d2199 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -13,28 +13,30 @@ from jamcodec.types import VarInt64 from pyjamaz.models.block import Block +from pyjamaz.transport.jamnp_s.stream_base import StreamDirection from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP from pyjamaz.transport.types import ProtocolType -from pyjamaz.transport.jamnp_s.message_types import MsgCE128BlockRequestDirection, MsgCE128BlockRequest, MsgUP0Handshake, MsgUP0Announcement +from pyjamaz.transport.jamnp_s.message_types import MsgCE128BlockRequestDirection, MsgCE128BlockRequest, \ + MsgUP0Handshake, MsgUP0Announcement, MsgCE128BlockRequestResponse from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest -from pyjamaz.transport.jamnp_s.connection_base import ConnectionBase -from pyjamaz.transport.jamnp_s.connection_initiator import ConnectionInitiator -from pyjamaz.transport.jamnp_s.connection_acceptor import ConnectionAcceptor +from pyjamaz.transport.jamnp_s.connection import JAMConnection logger = logging.getLogger("pyjamaz.transport.jamnp_s") -def wrap_protocol(wrapper, protocol, host, port): - def create_protocol(*args, **kwargs): +#TODO: typings +def wrap_protocol(wrapper, protocol, direction, host, port): + def create_connection(*args, **kwargs): instance = protocol(*args, **kwargs) instance.protocol = wrapper + instance.direction = direction instance.host = host instance.port = port return instance - return create_protocol + return create_connection class SessionTicketStore: @@ -90,7 +92,7 @@ async def listen(self): self.host, self.port, configuration=self.configuration, - create_protocol=wrap_protocol(self, ConnectionAcceptor, self.host, self.port), + create_protocol=wrap_protocol(self, JAMConnection, StreamDirection.acceptor, self.host, self.port), session_ticket_fetcher=self.session_ticket_store.pop, session_ticket_handler=self.session_ticket_store.add, retry=True, @@ -107,16 +109,16 @@ async def connect(self, host, port): ) configuration.load_cert_chain(certfile=self.cert, keyfile=self.pk) - logger.debug(f"ClientProtocol Connecting to {host}:{port}") + logger.debug(f"Connecting to {host}:{port}") try: async with connect( host, port, configuration=configuration, # session_ticket_handler=save_session_ticket, - create_protocol=wrap_protocol(self, ConnectionInitiator, host, port), + create_protocol=wrap_protocol(self, JAMConnection, StreamDirection.initiator, host, port), ) as client: - client = cast(ConnectionInitiator, client) + client = cast(JAMConnection, client) self.conn_initiated[(host, port)] = client await client.wait_closed() del self.conn_initiated[(host, port)] @@ -126,8 +128,8 @@ async def connect(self, host, port): logger.warning(f"💩 ClientProtocol Cannot connect to {host}:{port} {exc}") - def up0_send_handshake(self, conn: ConnectionBase): - # Create a presistent UP0 stream for the connection + def up0_send_handshake(self, conn: JAMConnection): + # Create a presistent UP0 stream for this connection # stream_up = conn.open_jam_stream(StreamUP) # conn.stream_up = stream_up # slot = self.app.state.timeslot.number @@ -161,7 +163,7 @@ def up0_send_handshake(self, conn: ConnectionBase): ) - def up0_received_handshake(self, conn: ConnectionBase, msg: MsgUP0Handshake): + def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): # Note: For now we employ a very simple strategy, where we sync blocks from the first node that announced a finalized block greater than we have if self.state_requesting_blocks: return @@ -177,11 +179,19 @@ def up0_received_handshake(self, conn: ConnectionBase, msg: MsgUP0Handshake): self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(bl_hash, MsgCE128BlockRequestDirection.ASC.value, 1)) #TODO: max_blocks=1 for now, >1 results in error from node? - def up0_received_announcement(self, conn: ConnectionBase, msg: MsgUP0Announcement): + def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement): # Note: For now we employ a very simple strategy, where we sync blocks from the first node that announced a finalized block greater than we have if self.state_requesting_blocks: return + """ + TODO: send to other nodes (other than this connection) + Except when: + A descendant of the block is announced instead. + The block is not a descendant of the latest finalized block. + The block, or a descendant of the block, has been announced by the other side of the stream. + """ + block = self.app.retrieve_block_by_hash(msg.header.hash) #TODO: missch een efficientere exists check toevoegen #TODO: ook nog iets doen met bijhorende finalized header_hash & slot? if not block: @@ -190,7 +200,27 @@ def up0_received_announcement(self, conn: ConnectionBase, msg: MsgUP0Announcemen self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(msg.header.hash, MsgCE128BlockRequestDirection.ASC.value, 1)) #TODO: max_blocks=1 for now, >1 results in error from node? - def ce128_initiate_block_request(self, conn: ConnectionBase, req: MsgCE128BlockRequest): + async def up0_broadcast_block(self, block: Block): + logger.debug(f'JAMNP broadcasting block announcement to {len(self.conn_accepted)} clients') + + msg = MsgUP0Announcement( + header=block.header, + header_hash=block.header.hash, + timeslot=block.header.timeslot + ).to_jam_bytes().to_bytes() + + for client_id, client in self.conn_accepted.items(): + logger.debug(f"JAMNP send block to client {client}") + client.conn.send( + client.stream_up.stream_id, + client.stream_up.create_message(msg), + end_stream=False + ) + + #TODO: ook naar de self initiated connections? + + + def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRequest): stream = conn.open_jam_stream(StreamBlockRequest) print(f"PROTOCOL INITIATING BLOCK REQUEST ON STREAMID: {stream.stream_id} header hash: {req.header_hash} direction: {req.direction}, max_block: {req.max_blocks}") conn.send( @@ -200,9 +230,10 @@ def ce128_initiate_block_request(self, conn: ConnectionBase, req: MsgCE128BlockR ) - def ce128_received_block_request(self, conn: ConnectionBase, block: Block): + def ce128_received_block_request(self, conn: JAMConnection, block: Block): print(f"PROTOCOL RECEIVED BLOCK REQUEST") asyncio.create_task(self.app.import_queue_add(block)) + #TODO: check?: if block.header.parent == bytes(32) or self.retrieve_block_by_hash(block.header.parent): self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(block.header.hash, MsgCE128BlockRequestDirection.ASC.value, 1)) # TODO: max_blocks=1 for now, >1 results in error from node? @@ -212,9 +243,25 @@ def ce128_finished_block_request(self): asyncio.create_task(self.app.process_import_queue()) - async def broadcast_block(self, block): - block_bytes = block.to_jam_bytes().to_bytes() - logger.debug(f'JAMNP broadcasting block announcement to {len(self.conn_accepted)} clients') - for client_id, client in self.conn_accepted.items(): - logger.debug(f"JAMNP send block to client {client}") - await client.send_block_announcement(block_bytes) + def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE128BlockRequest): + blocks = [] + direction = block_req.direction * -1 + block = self.app.retrieve_block_by_hash(block_req.header_hash) + if block: + blocks.append(block) + + for x in range(block_req.max_blocks): + block = self.app.retrieve_block(block.header.timeslot+direction) + if block: + blocks.append(block) + else: + break + + print(f"SENDING {len(blocks)} BLOCKSZZZZZZZZZ") + stream.conn.send( + stream.stream_id, + stream.create_message(MsgCE128BlockRequestResponse(blocks).to_jam_bytes().to_bytes()), + end_stream=True + ) + + stream.acceptor_reset(1) diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py index 6e31ac74..f72ae7eb 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -1,11 +1,9 @@ import enum import logging -from typing import List, Optional from jamcodec.base import JamBytes from jamcodec.types import U32, VarInt64 -from pyjamaz.models.block import Header from pyjamaz.transport.jamnp_s.message_types import MsgUP0Handshake, MsgUP0Leaf, MsgUP0Announcement from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection @@ -60,12 +58,6 @@ def initiator_message(self, data: bytes): case UPState.CONNECTED: # After handshake is completed, we receive Announcement messages - # jam_bytes = JamBytes(data) - # header = Header.from_jam_bytes(jam_bytes) - # header_hash = jam_bytes.get_next_bytes(32).hex() - # slot = U32.decode(jam_bytes) - # logger.debug(f"Received up0 announcement: header={header.hash} header_hash={header_hash} slot={slot}") - # self.protocol.up0_received_announcement(header, header_hash, slot) msg = MsgUP0Announcement.from_jam_bytes(JamBytes(data)) self.protocol.up0_received_announcement(self.conn, msg) @@ -74,4 +66,5 @@ def initiator_message(self, data: bytes): def acceptor_message(self, data: bytes): - pass \ No newline at end of file + # Note: in case of UP0, initiator and acceptor have symmetrical message flow + self.initiator_message(data) diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py index d8940e48..4f4ae62e 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -3,6 +3,7 @@ from jamcodec.base import JamBytes from pyjamaz.models.block import Block +from pyjamaz.transport.jamnp_s.message_types import MsgCE128BlockRequest from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -37,4 +38,5 @@ def initiator_message(self, data: bytes): def acceptor_message(self, data: bytes): - raise Exception("Not implemented YET!!!!!!") + print("RECEIVED ACCEPTOR BLOCK REQUEST!!!!!!!!!!!!!!!!!!!!!!!!") + self.protocol.ce128_send_block_request(self.conn, MsgCE128BlockRequest.from_jam_bytes(JamBytes(data))) diff --git a/pyjamaz/transport/types.py b/pyjamaz/transport/types.py index 5cf0c3cd..5c68ea02 100644 --- a/pyjamaz/transport/types.py +++ b/pyjamaz/transport/types.py @@ -8,10 +8,3 @@ class ProtocolType(Protocol): async def listen(self): """ Starts this protocol, start listening for messages """ - - #TODO: deprecated? -> sync Protocol with necesary features - async def request_blocks(self, data): - """ Starts a Blocks Request, to fetch a range of blocks from a given peer """ - - async def broadcast_block(self, block:Block): - """ Broadcasts a new block to all connected peers """ \ No newline at end of file From d039b245cec24e2c80578f249e5e3f212cdb52c4 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Wed, 9 Jul 2025 13:21:59 +0200 Subject: [PATCH 06/51] - JAMSNP WIP --- pyjamaz/cli.py | 36 +++++---- pyjamaz/transport/jamnp_s/connection.py | 81 ++++++++++++------- pyjamaz/transport/jamnp_s/message_types.py | 1 - pyjamaz/transport/jamnp_s/protocol.py | 63 +++++++-------- pyjamaz/transport/jamnp_s/stream_base.py | 17 +++- pyjamaz/transport/jamnp_s/stream_map.py | 20 +++++ pyjamaz/transport/jamnp_s/streams/stream_0.py | 24 +----- .../transport/jamnp_s/streams/stream_128.py | 1 - 8 files changed, 134 insertions(+), 109 deletions(-) create mode 100644 pyjamaz/transport/jamnp_s/stream_map.py diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index db583288..469de8ff 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -273,26 +273,28 @@ async def main(ctx, seed, port, ts, culprit, block_dir, record_traces, custom_db try: #await nps_protocol.connect(conn["addr"], conn["port"]) #await nps_protocol.connect("54.39.18.64", 40000) - await nps_protocol.connect("localhost", conn["port"]) + #await nps_protocol.connect("localhost", conn["port"]) + await nps_protocol.connect("127.0.0.1", 40000) #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! except Exception as exc: traceback.print_exc() else: - for validator in app.state.safrole.validators: - # The validators' IP-layer endpoints are given as IPv6/port combinations, - # to be found in the first 18 bytes of validator metadata, with the first 16 bytes being the IPv6 address and - # the latter 2 being a little endian representation of the port. - - validator_port = validator.get_metadata_port() - validator_address = validator.get_metadata_ipaddress() - - if validator.ed25519 == app.config.keys.ed25519.public_key: - logging.debug( - f'Skipping own node ({validator_address}:{validator_port})' - ) - continue - - logging.debug(f'Connecting to node {validator_address}:{validator_port}') - tg.start_soon(nps_protocol.connect, validator_address, validator_port) + # for validator in app.state.safrole.validators: + # # The validators' IP-layer endpoints are given as IPv6/port combinations, + # # to be found in the first 18 bytes of validator metadata, with the first 16 bytes being the IPv6 address and + # # the latter 2 being a little endian representation of the port. + # + # validator_port = validator.get_metadata_port() + # validator_address = validator.get_metadata_ipaddress() + # + # if validator.ed25519 == app.config.keys.ed25519.public_key: + # logging.debug( + # f'Skipping own node ({validator_address}:{validator_port})' + # ) + # continue + # + # logging.debug(f'Connecting to node {validator_address}:{validator_port}') + # tg.start_soon(nps_protocol.connect, validator_address, validator_port) + pass await anyio.sleep(ts - time.time()) tg.start_soon(timeslot_ticker, app) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index f066ef91..b3c6c53d 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -2,9 +2,11 @@ import logging from aioquic.asyncio import QuicConnectionProtocol, serve -from aioquic.quic.events import QuicEvent, HandshakeCompleted, StreamReset, StreamDataReceived +from aioquic.quic.events import QuicEvent, HandshakeCompleted, StreamReset, StreamDataReceived, ConnectionTerminated from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamDirection +from pyjamaz.transport.jamnp_s.stream_map import StreamLookup +from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -15,7 +17,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Note: should be set in wrap_protocol - self.direction:StreamDirection = None + self.direction:StreamDirection = None #TODO: JAMConnectionDirection! self.protocol = None self.host = None self.port = None @@ -25,9 +27,10 @@ def __init__(self, *args, **kwargs): #self._keepalive_task = asyncio.create_task(self._keepalive()) - def open_jam_stream(self, StreamCls: Stream): - stream_id = self._quic.get_next_available_stream_id() - self.streams[stream_id] = StreamCls(stream_id, connection=self, direction=self.direction) + def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_id: int=None): + if stream_id is None: + stream_id = self._quic.get_next_available_stream_id() + self.streams[stream_id] = StreamCls(stream_id, connection=self, direction=direction) return self.streams[stream_id] @@ -36,7 +39,7 @@ def close_jam_stream(self, stream: Stream, reason:int=0): del self.streams[stream.stream_id] - def send(self, stream_id: int, data, end_stream=False): + def send(self, stream_id: int, data: bytes, end_stream=False): self._quic.send_stream_data( stream_id, data, @@ -45,15 +48,15 @@ def send(self, stream_id: int, data, end_stream=False): self.transmit() - async def _keepalive(self): - try: - while True: - #TODO: what is a sane amount of time? - await asyncio.sleep(4) - self._quic.send_ping(id(self)) - self.transmit() - except asyncio.CancelledError: - pass + # async def _keepalive(self): + # try: + # while True: + # #TODO: what is a sane amount of time? + # await asyncio.sleep(4) + # self._quic.send_ping(id(self)) + # self.transmit() + # except asyncio.CancelledError: + # pass def quic_event_received(self, event: QuicEvent) -> None: @@ -71,7 +74,11 @@ def quic_event_received(self, event: QuicEvent) -> None: if self.stream_up is not None: raise Exception("There can be only one UP connection active at a time") - self.protocol.up0_send_handshake(self) + if self.direction == StreamDirection.initiator: + # Initiating side will send a JAM handshake message and set the stream id + stream_up = self.open_jam_stream(StreamUP, direction=self.direction) + self.stream_up = stream_up + self.protocol.up0_send_handshake(self) elif isinstance(event, StreamReset): stream_id = event.stream_id @@ -82,24 +89,38 @@ def quic_event_received(self, event: QuicEvent) -> None: if stream_id not in self.streams: raise Exception(f"Stream {stream_id} not available") - if self.direction == StreamDirection.initiator: - self.streams[stream_id].initiator_reset(reset_code) - elif self.direction == StreamDirection.acceptor: - self.streams[stream_id].acceptor_reset(reset_code) + self.streams[stream_id].reset(reset_code) elif isinstance(event, StreamDataReceived): stream_id = event.stream_id + data = bytes(event.data) if stream_id not in self.streams: - #logging.warning(f"Stream {stream_id} not available") - #return - raise Exception(f"Stream {stream_id} not available") - - self.streams[stream_id].receive_data(bytes(event.data)) - - - # TODO: handle gracefully - # elif isinstance(event, ConnectionTerminated): - # # Handle connection termination + if self.direction == StreamDirection.acceptor: + stream_type = int(data[0]) + stream_cls = StreamLookup.get(stream_type) + if stream_cls is None: + raise Exception(f"Stream {stream_id} is not mapped") + + self.open_jam_stream(stream_cls, direction=self.direction) + + if stream_cls == StreamUP: + # If we're on the acceptor side of this stream, send a handshake message back + self.protocol.up0_send_handshake(self) + + # Only the first time an acceptor receives a message, we expect a stream id byte + data = data[1:] + else: + #TODO: of idem? kan een accepting connection bv ook een block request terug sturen? + raise Exception(f"Received data from unknown stream id: {stream_id}") + + try: + self.streams[stream_id].receive_data(data) + except Exception as e: + #TODO: reset stream? return error? + logger.warning(f"Received invalid message for stream {stream_id} ({self.streams[stream_id]}): {e}") + + elif isinstance(event, ConnectionTerminated): + logger.info(f"Connection terminated with code {event.error_code}") diff --git a/pyjamaz/transport/jamnp_s/message_types.py b/pyjamaz/transport/jamnp_s/message_types.py index 1a9e6c6e..c44cfb4b 100644 --- a/pyjamaz/transport/jamnp_s/message_types.py +++ b/pyjamaz/transport/jamnp_s/message_types.py @@ -19,7 +19,6 @@ class MsgUP0Leaf(Serializable): class MsgUP0Handshake(Serializable): header_hash: bytes = field(metadata={'codec': H256}) timeslot: int = field(metadata={'codec': U32}) - #leafs: Optional[List[MsgUP0Leaf]] = field(metadata={'codec': Option(Vec(MsgUP0Leaf.to_codec_def()))}) leafs: List[MsgUP0Leaf] = field(metadata={'codec': Vec(MsgUP0Leaf.to_codec_def())}) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 0a4d2199..7cdf9bd2 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -88,13 +88,21 @@ def __init__(self, host, port, certificate, private_key, app): async def listen(self): logger.debug(f'Listening on {self.host}:{self.port}') + + server_conf = QuicConfiguration( + alpn_protocols=[self.protocol_name], + is_client=False, + #verify_mode=ssl.CERT_NONE, + ) + server_conf.load_cert_chain(self.cert, self.pk) + await serve( self.host, self.port, - configuration=self.configuration, + configuration=server_conf, create_protocol=wrap_protocol(self, JAMConnection, StreamDirection.acceptor, self.host, self.port), - session_ticket_fetcher=self.session_ticket_store.pop, - session_ticket_handler=self.session_ticket_store.add, + #session_ticket_fetcher=self.session_ticket_store.pop, + #session_ticket_handler=self.session_ticket_store.add, retry=True, ) @@ -105,7 +113,7 @@ async def connect(self, host, port): is_client=True, #verify_mode=ssl.CERT_REQUIRED, verify_mode=ssl.CERT_NONE, - idle_timeout=300000 + #idle_timeout=300000 ) configuration.load_cert_chain(certfile=self.cert, keyfile=self.pk) @@ -130,36 +138,23 @@ async def connect(self, host, port): def up0_send_handshake(self, conn: JAMConnection): # Create a presistent UP0 stream for this connection - # stream_up = conn.open_jam_stream(StreamUP) - # conn.stream_up = stream_up - # slot = self.app.state.timeslot.number - # header_hash = self.app.retrieve_block_hash(slot) - # leafs = [] #TODO - # handshake = MsgUP0Handshake( - # header_hash=header_hash, - # timeslot=slot, - # leafs=leafs - # ) - # logger.debug(f"Sending Handshake on stream {stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") - # conn.send( - # stream_up.stream_id, - # stream_up.create_message(handshake.to_jam_bytes().to_bytes()), - # ) - - #TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! fix zie hierboven!!!! - stream_up = conn.open_jam_stream(StreamUP) - conn.stream_up = stream_up slot = self.app.state.timeslot.number - bl_ts = slot.to_bytes(length=4, byteorder='little') #self.conn.protocol.app.state.timeslot.number - bl_hash = self.app.retrieve_block_hash(slot) - final = bl_hash + bl_ts - leafs = bytes() # [bytes.fromhex(h) + s.to_bytes(length=4, byteorder='little') for h, s in []] - leaf_count = VarInt64.encode(len(leafs)).to_bytes() - handshake = final + leaf_count + leafs - logger.debug(f'Sending handshake {handshake} to {conn.host}:{conn.port}') + header_hash = self.app.retrieve_block_hash(slot) + leafs = [] #TODO + handshake = MsgUP0Handshake( + header_hash=header_hash, + timeslot=slot, + leafs=leafs + ) + logger.debug(f"Sending Handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") + + stream_type = None + if conn.direction == StreamDirection.initiator: + stream_type = StreamUP.stream_type + conn.send( - stream_up.stream_id, - stream_up.stream_type + (len(handshake)).to_bytes(length=4, byteorder='little') + handshake, + conn.stream_up.stream_id, + conn.stream_up.create_message(handshake.to_jam_bytes().to_bytes(), stream_type=stream_type), ) @@ -221,11 +216,11 @@ async def up0_broadcast_block(self, block: Block): def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRequest): - stream = conn.open_jam_stream(StreamBlockRequest) + stream = conn.open_jam_stream(StreamBlockRequest, direction=StreamDirection.initiator) print(f"PROTOCOL INITIATING BLOCK REQUEST ON STREAMID: {stream.stream_id} header hash: {req.header_hash} direction: {req.direction}, max_block: {req.max_blocks}") conn.send( stream.stream_id, - stream.create_message(req.to_jam_bytes().to_bytes()), + stream.create_message(req.to_jam_bytes().to_bytes(), stream_type=StreamBlockRequest.stream_type), end_stream=True ) diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py index b635d524..6abd520a 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -1,6 +1,6 @@ import logging from enum import Enum - +from typing import Optional logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -58,8 +58,12 @@ def acceptor_message(self, data: bytes): raise Exception("Override this method in Stream subclass") - def create_message(self, payload: bytes): - return self.stream_type + (len(payload)).to_bytes(length=4, byteorder='little') + payload + def create_message(self, payload: bytes, stream_type: Optional[bytes]): + if stream_type: + # Initial messages over a new stream should de prefixed with a stream type byte + return self.stream_type + (len(payload)).to_bytes(length=4, byteorder='little') + payload + else: + return (len(payload)).to_bytes(length=4, byteorder='little') + payload def receive_data(self, data: bytes): @@ -99,6 +103,13 @@ def receive_data(self, data: bytes): self._reset_msg() + def reset(self, reset_code: int): + if self.direction == StreamDirection.initiator: + self.initiator_reset(reset_code) + else: + self.acceptor_reset(reset_code) + + def initiator_reset(self, reset_code: int): self.conn.close_jam_stream(self) diff --git a/pyjamaz/transport/jamnp_s/stream_map.py b/pyjamaz/transport/jamnp_s/stream_map.py new file mode 100644 index 00000000..6a71b2c6 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/stream_map.py @@ -0,0 +1,20 @@ +from pyjamaz.transport.jamnp_s.stream_base import StreamType +from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP +from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest + +StreamLookup = { + StreamType.UP0_BlockAnnouncement.value: StreamUP, + StreamType.CE128_BlockRequest.value: StreamBlockRequest, + # StreamType.CE129_StateRequest.value: , + # StreamType.CE131_SafroleTicketDistributionStep1.value: , + # StreamType.CE132_SafroleTicketDistributionStep2.value: , + # StreamType.CE133_WorkPackageSubmission.value: , + # StreamType.CE134_WorkPackageSharing.value: , + # StreamType.CE135_WorkReportDistribution.value: , + # StreamType.CE136_WorkReportRequest.value: , + # StreamType.CE137_ShardDistribution.value: , + # StreamType.CE138_AuditShardRequest.value: , + # StreamType.CE139_SegmentShardRequest.value: , + # StreamType.CE140_SegmentShardRequestJustification.value: , + # StreamType.CE141_AssuranceDistribution.value: , +} \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py index f72ae7eb..f2f2c26c 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -31,29 +31,7 @@ def initiator_message(self, data: bytes): case UPState.IN_PROGRESS: self.state = UPState.CONNECTED - #msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) - ############################ - #TODO:FIX!!!!!! zie hierboven - header_hash = data[:32] #.hex() - slot = int.from_bytes(data[32:36], byteorder='little') - - jam_bytes = JamBytes(data[36:]) - leaf_count = VarInt64.decode(jam_bytes) - - logger.debug(f"Received handshake response: header_hash={header_hash} slot={slot} leaf_count={leaf_count}") - - leafs = [] - for leaf_nr in range(leaf_count): - leaf_hash = jam_bytes.get_next_bytes(32) #.hex() - leaf_slot = U32.decode(jam_bytes) - leafs.append(MsgUP0Leaf(leaf_hash, leaf_slot)) - msg = MsgUP0Handshake( - header_hash=header_hash, - timeslot=slot, - leafs=leafs - ) - ############################################ - + msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) self.protocol.up0_received_handshake(self.conn, msg) case UPState.CONNECTED: diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py index 4f4ae62e..258fcc8b 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -36,7 +36,6 @@ def initiator_message(self, data: bytes): self.protocol.ce128_received_block_request(self.conn, block) - def acceptor_message(self, data: bytes): print("RECEIVED ACCEPTOR BLOCK REQUEST!!!!!!!!!!!!!!!!!!!!!!!!") self.protocol.ce128_send_block_request(self.conn, MsgCE128BlockRequest.from_jam_bytes(JamBytes(data))) From 4c3ef36f02bd7e15fc8505ae54cc0d5746007107 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Wed, 9 Jul 2025 13:38:56 +0200 Subject: [PATCH 07/51] - JAMSNP WIP --- pyjamaz/transport/jamnp_s/protocol.py | 8 +++----- pyjamaz/transport/jamnp_s/stream_base.py | 9 +++++---- pyjamaz/transport/jamnp_s/streams/stream_0.py | 3 ++- pyjamaz/transport/jamnp_s/streams/stream_128.py | 3 ++- pyjamaz/transport/jamnp_s/streams/stream_129.py | 3 ++- pyjamaz/transport/jamnp_s/streams/stream_131.py | 3 ++- pyjamaz/transport/jamnp_s/streams/stream_132.py | 3 ++- pyjamaz/transport/jamnp_s/streams/stream_133.py | 3 ++- pyjamaz/transport/jamnp_s/streams/stream_134.py | 3 ++- pyjamaz/transport/jamnp_s/streams/stream_135.py | 4 ++-- pyjamaz/transport/jamnp_s/streams/stream_136.py | 3 ++- pyjamaz/transport/jamnp_s/streams/stream_137.py | 3 ++- pyjamaz/transport/jamnp_s/streams/stream_138.py | 3 ++- pyjamaz/transport/jamnp_s/streams/stream_139.py | 3 ++- pyjamaz/transport/jamnp_s/streams/stream_140.py | 3 ++- pyjamaz/transport/jamnp_s/streams/stream_141.py | 3 ++- 16 files changed, 36 insertions(+), 24 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 7cdf9bd2..9b9f0b46 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -148,13 +148,11 @@ def up0_send_handshake(self, conn: JAMConnection): ) logger.debug(f"Sending Handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") - stream_type = None - if conn.direction == StreamDirection.initiator: - stream_type = StreamUP.stream_type + add_stream_type = conn.direction == StreamDirection.initiator conn.send( conn.stream_up.stream_id, - conn.stream_up.create_message(handshake.to_jam_bytes().to_bytes(), stream_type=stream_type), + conn.stream_up.create_message(handshake.to_jam_bytes().to_bytes(), add_stream_type=add_stream_type), ) @@ -220,7 +218,7 @@ def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRe print(f"PROTOCOL INITIATING BLOCK REQUEST ON STREAMID: {stream.stream_id} header hash: {req.header_hash} direction: {req.direction}, max_block: {req.max_blocks}") conn.send( stream.stream_id, - stream.create_message(req.to_jam_bytes().to_bytes(), stream_type=StreamBlockRequest.stream_type), + stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True ) diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py index 6abd520a..c3bd2315 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -36,7 +36,8 @@ class Stream: #TODO: typings on connection def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_id = stream_id - self.stream_type = None # Note: override in subclass + self.stream_type = None # Note: override in subclass + self.stream_type_byte = None # Note: override in subclass self.conn = connection self.protocol = connection.protocol self.direction = direction @@ -58,10 +59,10 @@ def acceptor_message(self, data: bytes): raise Exception("Override this method in Stream subclass") - def create_message(self, payload: bytes, stream_type: Optional[bytes]): - if stream_type: + def create_message(self, payload: bytes, add_stream_type: Optional[bool]): + if add_stream_type: # Initial messages over a new stream should de prefixed with a stream type byte - return self.stream_type + (len(payload)).to_bytes(length=4, byteorder='little') + payload + return self.stream_type_byte + (len(payload)).to_bytes(length=4, byteorder='little') + payload else: return (len(payload)).to_bytes(length=4, byteorder='little') + payload diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py index f2f2c26c..fb03b94a 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -20,7 +20,8 @@ class StreamUP(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.UP0_BlockAnnouncement.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.UP0_BlockAnnouncement.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') self.state = UPState.IN_PROGRESS diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py index 258fcc8b..4411b19a 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -13,7 +13,8 @@ class StreamBlockRequest(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE128_BlockRequest.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.CE128_BlockRequest.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') def initiator_reset(self, reset_code: int): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_129.py b/pyjamaz/transport/jamnp_s/streams/stream_129.py index b399c324..b6c7c357 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_129.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_129.py @@ -8,7 +8,8 @@ class StreamStateRequest(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE129_StateRequest.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.CE129_StateRequest.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') self.state = self.BOUNDARY_NODE diff --git a/pyjamaz/transport/jamnp_s/streams/stream_131.py b/pyjamaz/transport/jamnp_s/streams/stream_131.py index 903e62bc..6ffe7ead 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_131.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_131.py @@ -5,7 +5,8 @@ class StreamSafroleTicketDistributionStep1(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE131_SafroleTicketDistributionStep1.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.CE131_SafroleTicketDistributionStep1.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') def initiate_state_request(self, header_hash, direction, max_blocks): pass diff --git a/pyjamaz/transport/jamnp_s/streams/stream_132.py b/pyjamaz/transport/jamnp_s/streams/stream_132.py index 3b589998..d87bd79c 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_132.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_132.py @@ -5,7 +5,8 @@ class StreamSafroleTicketDistributionStep2(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE132_SafroleTicketDistributionStep2.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.CE132_SafroleTicketDistributionStep2.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') def initiate_state_request(self, header_hash, direction, max_blocks): # Note: Sends message to all current validators diff --git a/pyjamaz/transport/jamnp_s/streams/stream_133.py b/pyjamaz/transport/jamnp_s/streams/stream_133.py index 1b9edbe4..05628295 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_133.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_133.py @@ -5,7 +5,8 @@ class StreamWorkPackageSubmission(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE133_WorkPackageSubmission.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.CE133_WorkPackageSubmission.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') def initiate_state_request(self, header_hash, direction, max_blocks): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_134.py b/pyjamaz/transport/jamnp_s/streams/stream_134.py index 915b255b..c2394d38 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_134.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_134.py @@ -5,7 +5,8 @@ class StreamWorkPackageSharing(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE134_WorkPackageSharing.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.CE134_WorkPackageSharing.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') def initiate_state_request(self, header_hash, direction, max_blocks): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_135.py b/pyjamaz/transport/jamnp_s/streams/stream_135.py index 6c9df6e2..54d9e387 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_135.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_135.py @@ -5,8 +5,8 @@ class StreamWorkReportDistribution(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE135_WorkReportDistribution.value.to_bytes(length=1, byteorder='little') - + self.stream_type = StreamType.CE135_WorkReportDistribution.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') def initiate_state_request(self, header_hash, direction, max_blocks): pass diff --git a/pyjamaz/transport/jamnp_s/streams/stream_136.py b/pyjamaz/transport/jamnp_s/streams/stream_136.py index f1db5cff..e5c3d0de 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_136.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_136.py @@ -5,7 +5,8 @@ class StreamWorkReportRequest(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE136_WorkReportRequest.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.CE136_WorkReportRequest.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') def initiate_state_request(self, header_hash, direction, max_blocks): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_137.py b/pyjamaz/transport/jamnp_s/streams/stream_137.py index ab3feb02..0ef4756c 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_137.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_137.py @@ -5,7 +5,8 @@ class StreamShardDistribution(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE137_ShardDistribution.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.CE137_ShardDistribution.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') def initiate_state_request(self, header_hash, direction, max_blocks): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_138.py b/pyjamaz/transport/jamnp_s/streams/stream_138.py index b27e8c22..3a7b4a66 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_138.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_138.py @@ -5,7 +5,8 @@ class StreamAuditShardRequest(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE138_AuditShardRequest.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.CE138_AuditShardRequest.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') def initiate_state_request(self, header_hash, direction, max_blocks): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_139.py b/pyjamaz/transport/jamnp_s/streams/stream_139.py index a4bf8ea3..722161dc 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_139.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_139.py @@ -5,7 +5,8 @@ class StreamSegmentShardRequest(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE139_SegmentShardRequest.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.CE139_SegmentShardRequest.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') def initiate_state_request(self, header_hash, direction, max_blocks): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_140.py b/pyjamaz/transport/jamnp_s/streams/stream_140.py index 1bb9a11d..886eb7ac 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_140.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_140.py @@ -5,7 +5,8 @@ class StreamSegmentShardRequestJustification(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE140_SegmentShardRequestJustification.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.CE140_SegmentShardRequestJustification.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') def initiate_state_request(self, header_hash, direction, max_blocks): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_141.py b/pyjamaz/transport/jamnp_s/streams/stream_141.py index d1bff418..268374df 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_141.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_141.py @@ -5,7 +5,8 @@ class StreamAssuranceDistribution(Stream): def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE141_AssuranceDistribution.value.to_bytes(length=1, byteorder='little') + self.stream_type = StreamType.CE141_AssuranceDistribution.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') def initiate_state_request(self, header_hash, direction, max_blocks): From 0a6ba01d866cb76ec0e051f36c3b75c9ef2d2436 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Wed, 9 Jul 2025 13:45:18 +0200 Subject: [PATCH 08/51] - JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index b3c6c53d..ebd8761a 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -103,7 +103,7 @@ def quic_event_received(self, event: QuicEvent) -> None: if stream_cls is None: raise Exception(f"Stream {stream_id} is not mapped") - self.open_jam_stream(stream_cls, direction=self.direction) + self.stream_up = self.open_jam_stream(stream_cls, direction=self.direction, stream_id=stream_id) if stream_cls == StreamUP: # If we're on the acceptor side of this stream, send a handshake message back From 0a30fabe58b75eddfc74d5d36ecec345472a4654 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Wed, 9 Jul 2025 15:04:15 +0200 Subject: [PATCH 09/51] - JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 6 +++++- pyjamaz/transport/jamnp_s/protocol.py | 2 +- pyjamaz/transport/jamnp_s/streams/stream_128.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index ebd8761a..acf12502 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -79,6 +79,10 @@ def quic_event_received(self, event: QuicEvent) -> None: stream_up = self.open_jam_stream(StreamUP, direction=self.direction) self.stream_up = stream_up self.protocol.up0_send_handshake(self) + else: + # Note: it seems only now we have + self.host = self._quic._network_paths[0][0] + self.port = self._quic._network_paths[0][1] elif isinstance(event, StreamReset): stream_id = event.stream_id @@ -119,7 +123,7 @@ def quic_event_received(self, event: QuicEvent) -> None: self.streams[stream_id].receive_data(data) except Exception as e: #TODO: reset stream? return error? - logger.warning(f"Received invalid message for stream {stream_id} ({self.streams[stream_id]}): {e}") + logger.error(f"Received invalid message for stream {stream_id} ({self.streams[stream_id]}): {e}") elif isinstance(event, ConnectionTerminated): logger.info(f"Connection terminated with code {event.error_code}") diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 9b9f0b46..701b0e0c 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -146,7 +146,7 @@ def up0_send_handshake(self, conn: JAMConnection): timeslot=slot, leafs=leafs ) - logger.debug(f"Sending Handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") + logger.info(f"Sending Handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") add_stream_type = conn.direction == StreamDirection.initiator diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py index 4411b19a..1bf14b9c 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -39,4 +39,4 @@ def initiator_message(self, data: bytes): def acceptor_message(self, data: bytes): print("RECEIVED ACCEPTOR BLOCK REQUEST!!!!!!!!!!!!!!!!!!!!!!!!") - self.protocol.ce128_send_block_request(self.conn, MsgCE128BlockRequest.from_jam_bytes(JamBytes(data))) + self.protocol.ce128_send_block_request(self, MsgCE128BlockRequest.from_jam_bytes(JamBytes(data))) From 0c37343040757faaf519e9044ce42439ad9666e6 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Wed, 9 Jul 2025 15:07:26 +0200 Subject: [PATCH 10/51] - JAMSNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 29 ++++++++++------- pyjamaz/transport/jamnp_s/protocol.py | 41 +++++++++++++++++-------- requirements.txt | 1 + 3 files changed, 48 insertions(+), 23 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index acf12502..63071996 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -19,12 +19,13 @@ def __init__(self, *args, **kwargs): # Note: should be set in wrap_protocol self.direction:StreamDirection = None #TODO: JAMConnectionDirection! self.protocol = None + self.jam_connection_ulid = None self.host = None self.port = None self.stream_up = None self.streams = {} - #self._keepalive_task = asyncio.create_task(self._keepalive()) + self._keepalive_task = asyncio.create_task(self._keepalive()) def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_id: int=None): @@ -48,15 +49,21 @@ def send(self, stream_id: int, data: bytes, end_stream=False): self.transmit() - # async def _keepalive(self): - # try: - # while True: - # #TODO: what is a sane amount of time? - # await asyncio.sleep(4) - # self._quic.send_ping(id(self)) - # self.transmit() - # except asyncio.CancelledError: - # pass + def connection_lost(self, exc): + logger.info("UDP transport closed:", exc) + self.protocol.disconnect(self) + super().connection_lost(exc) # keeps aioquic tidy + + + async def _keepalive(self): + try: + while True: + #TODO: what is a sane amount of time? Usefull for detecting disconnect (early)? + await asyncio.sleep(3) + self._quic.send_ping(id(self)) + self.transmit() + except asyncio.CancelledError: + pass def quic_event_received(self, event: QuicEvent) -> None: @@ -127,4 +134,4 @@ def quic_event_received(self, event: QuicEvent) -> None: elif isinstance(event, ConnectionTerminated): logger.info(f"Connection terminated with code {event.error_code}") - + self.protocol.disconnect(self) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 701b0e0c..4c4844e3 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -10,11 +10,10 @@ from aioquic.tls import SessionTicket from aioquic.asyncio.client import connect -from jamcodec.types import VarInt64 +from ulid import ULID from pyjamaz.models.block import Block from pyjamaz.transport.jamnp_s.stream_base import StreamDirection -from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP from pyjamaz.transport.types import ProtocolType from pyjamaz.transport.jamnp_s.message_types import MsgCE128BlockRequestDirection, MsgCE128BlockRequest, \ @@ -32,8 +31,18 @@ def create_connection(*args, **kwargs): instance = protocol(*args, **kwargs) instance.protocol = wrapper instance.direction = direction - instance.host = host - instance.port = port + + instance.jam_conn_id = ULID() + protocol.connections[instance.jam_conn_id] = instance + + # Note: for accepting connections addr & port are known after the QUIC handshake, see JAMConnection::HandshakeComplete + if direction == StreamDirection.initiator: + instance.host = host + instance.port = port + protocol.conn_initiated.add(instance.jam_conn_id) + else: + protocol.conn_accepted.add(instance.jam_conn_id) + return instance return create_connection @@ -82,8 +91,10 @@ def __init__(self, host, port, certificate, private_key, app): self.cert = certificate self.pk = private_key self.configuration.load_cert_chain(certificate, private_key) - self.conn_accepted = {} # All incomming connections - self.conn_initiated = {} # All outgoing connections (who we connect to) + + self.connections = {} # All connections + self.conn_accepted = set() # All incomming connections + self.conn_initiated = set() # All outgoing connections (who we connect to) async def listen(self): @@ -127,15 +138,21 @@ async def connect(self, host, port): create_protocol=wrap_protocol(self, JAMConnection, StreamDirection.initiator, host, port), ) as client: client = cast(JAMConnection, client) - self.conn_initiated[(host, port)] = client await client.wait_closed() - del self.conn_initiated[(host, port)] + self.disconnect(client) except ConnectionError as exc: - if (host, port) in self.conn_initiated: - del self.conn_initiated[(host, port)] logger.warning(f"💩 ClientProtocol Cannot connect to {host}:{port} {exc}") + def disconnect(self, connection: JAMConnection): + if connection.direction == StreamDirection.initiator: + self.conn_initiated.remove(connection.jam_connection_ulid) + else: + self.conn_accepted.remove(connection.jam_connection_ulid) + + del self.connections[connection.jam_connection_ulid] + + def up0_send_handshake(self, conn: JAMConnection): # Create a presistent UP0 stream for this connection slot = self.app.state.timeslot.number @@ -194,7 +211,7 @@ def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement async def up0_broadcast_block(self, block: Block): - logger.debug(f'JAMNP broadcasting block announcement to {len(self.conn_accepted)} clients') + logger.debug(f'JAMNP broadcasting block announcement to {len(self.connections)} connections') msg = MsgUP0Announcement( header=block.header, @@ -202,7 +219,7 @@ async def up0_broadcast_block(self, block: Block): timeslot=block.header.timeslot ).to_jam_bytes().to_bytes() - for client_id, client in self.conn_accepted.items(): + for client_id, client in self.connections.items(): logger.debug(f"JAMNP send block to client {client}") client.conn.send( client.stream_up.stream_id, diff --git a/requirements.txt b/requirements.txt index 4a18542d..5242f700 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,3 +17,4 @@ jamcodec~=0.1.1 numpy~=2.1 aioquic==1.2.0 +python-ulid==3.0.0 \ No newline at end of file From 64ecb3d098dfa5a4012b0db5290e12b26bca1cc4 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Wed, 9 Jul 2025 15:14:25 +0200 Subject: [PATCH 11/51] - JAMSNP WIP --- pyjamaz/transport/jamnp_s/protocol.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 4c4844e3..1efc653e 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -26,24 +26,24 @@ #TODO: typings -def wrap_protocol(wrapper, protocol, direction, host, port): +def wrap_protocol(protocol, quic_connection, direction, host, port): def create_connection(*args, **kwargs): - instance = protocol(*args, **kwargs) - instance.protocol = wrapper - instance.direction = direction + conn = quic_connection(*args, **kwargs) + conn.protocol = protocol + conn.direction = direction - instance.jam_conn_id = ULID() - protocol.connections[instance.jam_conn_id] = instance + conn.jam_connection_ulid = ULID() + protocol.connections[conn.jam_connection_ulid] = conn # Note: for accepting connections addr & port are known after the QUIC handshake, see JAMConnection::HandshakeComplete if direction == StreamDirection.initiator: - instance.host = host - instance.port = port - protocol.conn_initiated.add(instance.jam_conn_id) + conn.host = host + conn.port = port + protocol.conn_initiated.add(conn.jam_connection_ulid) else: - protocol.conn_accepted.add(instance.jam_conn_id) + protocol.conn_accepted.add(conn.jam_connection_ulid) - return instance + return conn return create_connection From 1be016cc9225cdc09ca72989e1a46481f1aa3c39 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 10 Jul 2025 12:52:43 +0200 Subject: [PATCH 12/51] - JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 31 ++++++++++++------- pyjamaz/transport/jamnp_s/protocol.py | 20 ++++++------ pyjamaz/transport/jamnp_s/stream_base.py | 2 +- .../transport/jamnp_s/streams/stream_128.py | 8 ++--- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 63071996..6eb37e83 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -31,22 +31,30 @@ def __init__(self, *args, **kwargs): def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_id: int=None): if stream_id is None: stream_id = self._quic.get_next_available_stream_id() + logger.info(f"Opening NEW JAM stream {stream_id} for {direction}") + else: + logger.info(f"Opening EXISTING JAM stream {stream_id} for {direction}") self.streams[stream_id] = StreamCls(stream_id, connection=self, direction=direction) return self.streams[stream_id] def close_jam_stream(self, stream: Stream, reason:int=0): + logger.info(f"Closing JAM stream {stream} {stream.stream_id} for {stream.direction}") self._quic.reset_stream(stream.stream_id, error_code=reason) del self.streams[stream.stream_id] def send(self, stream_id: int, data: bytes, end_stream=False): - self._quic.send_stream_data( - stream_id, - data, - end_stream=end_stream - ) - self.transmit() + try: + self._quic.send_stream_data( + stream_id, + data, + end_stream=end_stream + ) + self.transmit() + except Exception as e: + #TODO!!!!!!!!!!!!!!!!!! + logger.error(e) def connection_lost(self, exc): @@ -87,9 +95,8 @@ def quic_event_received(self, event: QuicEvent) -> None: self.stream_up = stream_up self.protocol.up0_send_handshake(self) else: - # Note: it seems only now we have - self.host = self._quic._network_paths[0][0] - self.port = self._quic._network_paths[0][1] + # Note: it seems only now we have this info available from the accepting side + self.host, self.port = self._quic._network_paths[0].addr elif isinstance(event, StreamReset): stream_id = event.stream_id @@ -114,11 +121,13 @@ def quic_event_received(self, event: QuicEvent) -> None: if stream_cls is None: raise Exception(f"Stream {stream_id} is not mapped") - self.stream_up = self.open_jam_stream(stream_cls, direction=self.direction, stream_id=stream_id) + stream_obj = self.open_jam_stream(stream_cls, direction=self.direction, stream_id=stream_id) if stream_cls == StreamUP: # If we're on the acceptor side of this stream, send a handshake message back - self.protocol.up0_send_handshake(self) + if self.stream_up is None or self.stream_up.stream_id < stream_id: + self.stream_up = stream_obj + self.protocol.up0_send_handshake(self) # Only the first time an acceptor receives a message, we expect a stream id byte data = data[1:] diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 1efc653e..a4667bb1 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -163,7 +163,7 @@ def up0_send_handshake(self, conn: JAMConnection): timeslot=slot, leafs=leafs ) - logger.info(f"Sending Handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") + logger.debug(f"Sending Handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") add_stream_type = conn.direction == StreamDirection.initiator @@ -176,6 +176,7 @@ def up0_send_handshake(self, conn: JAMConnection): def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): # Note: For now we employ a very simple strategy, where we sync blocks from the first node that announced a finalized block greater than we have if self.state_requesting_blocks: + logger.debug(f"Skipping handshake block header check, already importing blocks") return block = self.app.retrieve_block_by_hash(msg.header_hash) #TODO: missch een efficientere exists check toevoegen @@ -192,6 +193,7 @@ def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement): # Note: For now we employ a very simple strategy, where we sync blocks from the first node that announced a finalized block greater than we have if self.state_requesting_blocks: + logger.debug(f"Skipping block header announcement check, already importing blocks") return """ @@ -220,19 +222,17 @@ async def up0_broadcast_block(self, block: Block): ).to_jam_bytes().to_bytes() for client_id, client in self.connections.items(): - logger.debug(f"JAMNP send block to client {client}") - client.conn.send( + logger.debug(f"JAMNP send block to client {client.host}:{client.port} with hash {block.header.hash}") + client.send( client.stream_up.stream_id, client.stream_up.create_message(msg), end_stream=False ) - #TODO: ook naar de self initiated connections? - def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRequest): stream = conn.open_jam_stream(StreamBlockRequest, direction=StreamDirection.initiator) - print(f"PROTOCOL INITIATING BLOCK REQUEST ON STREAMID: {stream.stream_id} header hash: {req.header_hash} direction: {req.direction}, max_block: {req.max_blocks}") + logger.debug(f"CE128 initiating block request on streamid: {stream.stream_id} header hash: {req.header_hash} direction: {req.direction}, max_block: {req.max_blocks}") conn.send( stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), @@ -241,7 +241,7 @@ def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRe def ce128_received_block_request(self, conn: JAMConnection, block: Block): - print(f"PROTOCOL RECEIVED BLOCK REQUEST") + logger.debug(f"CE128 received block request block") asyncio.create_task(self.app.import_queue_add(block)) #TODO: check?: if block.header.parent == bytes(32) or self.retrieve_block_by_hash(block.header.parent): self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(block.header.hash, MsgCE128BlockRequestDirection.ASC.value, 1)) # TODO: max_blocks=1 for now, >1 results in error from node? @@ -249,7 +249,7 @@ def ce128_received_block_request(self, conn: JAMConnection, block: Block): def ce128_finished_block_request(self): self.state_requesting_blocks = False - logger.debug("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!Finished block request") + logger.debug("Finished block request, start parsing import queue") asyncio.create_task(self.app.process_import_queue()) @@ -267,10 +267,10 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 else: break - print(f"SENDING {len(blocks)} BLOCKSZZZZZZZZZ") + logger.debug(f"CE128 sending {len(blocks)} blocks") stream.conn.send( stream.stream_id, - stream.create_message(MsgCE128BlockRequestResponse(blocks).to_jam_bytes().to_bytes()), + stream.create_message(MsgCE128BlockRequestResponse(blocks).to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True ) diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py index c3bd2315..f6e5fe41 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -59,7 +59,7 @@ def acceptor_message(self, data: bytes): raise Exception("Override this method in Stream subclass") - def create_message(self, payload: bytes, add_stream_type: Optional[bool]): + def create_message(self, payload: bytes, add_stream_type:bool=False): if add_stream_type: # Initial messages over a new stream should de prefixed with a stream type byte return self.stream_type_byte + (len(payload)).to_bytes(length=4, byteorder='little') + payload diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py index 1bf14b9c..37da4959 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -18,13 +18,12 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - print(f"CE128 RECEIVED RESET: {reset_code}") + logger.debug(f"CE128 received reset code: {reset_code}") self.protocol.ce128_finished_block_request() super().initiator_reset(reset_code) def initiator_message(self, data: bytes): - print(f"RECEIVED INITIATED CE128 MSG: {len(data)}") if len(data) == 0: #int.from_bytes(b'\x00\x00\x00\x00') #TODO: send FIN? Let protocol know were finished? print(f'!!!!!!!!!ConnectionInitiator StreamBlockRequest.initiator_message received empty data') @@ -33,10 +32,9 @@ def initiator_message(self, data: bytes): block = Block.from_jam_bytes(JamBytes(data)) block_hash = block.header.hash - print(f"Parsed block: {block_hash.hex()} parent:{block.header.parent.hex()}") - + logger.debug(f"CE128 initiated stream {self.stream_id} parsed block: {block_hash.hex()} parent:{block.header.parent.hex()}") self.protocol.ce128_received_block_request(self.conn, block) def acceptor_message(self, data: bytes): - print("RECEIVED ACCEPTOR BLOCK REQUEST!!!!!!!!!!!!!!!!!!!!!!!!") + logger.debug(f"CE128 acceptor stream {self.stream_id} received block request") self.protocol.ce128_send_block_request(self, MsgCE128BlockRequest.from_jam_bytes(JamBytes(data))) From dce35bcb1ec9d580dbbc9f6e21f32c009c30e2bd Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 10 Jul 2025 12:54:09 +0200 Subject: [PATCH 13/51] - JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 6eb37e83..d22e1429 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -25,7 +25,7 @@ def __init__(self, *args, **kwargs): self.stream_up = None self.streams = {} - self._keepalive_task = asyncio.create_task(self._keepalive()) + #self._keepalive_task = asyncio.create_task(self._keepalive()) def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_id: int=None): From 2feeebc2ee0973d0a853ca17f523c9ed3e776c6e Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 10 Jul 2025 13:01:03 +0200 Subject: [PATCH 14/51] - JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 5 +++-- pyjamaz/transport/jamnp_s/protocol.py | 2 +- pyjamaz/transport/jamnp_s/stream_base.py | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index d22e1429..4fd17ae0 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -31,9 +31,10 @@ def __init__(self, *args, **kwargs): def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_id: int=None): if stream_id is None: stream_id = self._quic.get_next_available_stream_id() - logger.info(f"Opening NEW JAM stream {stream_id} for {direction}") + logger.debug(f"Initiating stream {stream_id} for {direction}") else: - logger.info(f"Opening EXISTING JAM stream {stream_id} for {direction}") + logger.debug(f"Accepting stream {stream_id} for {direction}") + self.streams[stream_id] = StreamCls(stream_id, connection=self, direction=direction) return self.streams[stream_id] diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index a4667bb1..2fd6cedc 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -182,7 +182,7 @@ def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): block = self.app.retrieve_block_by_hash(msg.header_hash) #TODO: missch een efficientere exists check toevoegen #TODO: ook nog iets doen met slot en leafs ook mee requesten? if not block: - logger.debug(f"Received newer block from handshake: {msg.header_hash} request blocks") + logger.debug(f"Received newer block from handshake: {msg.header_hash} -> initiate CE128RequestBlocks") self.state_requesting_blocks = True #self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(msg.header_hash, MsgCE128BlockRequestDirection.DESC.value, 1)) #TODO: max_blocks=1 for now, >1 results in error from node? slot = self.app.state.timeslot.number diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py index f6e5fe41..8788fe02 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -76,7 +76,7 @@ def receive_data(self, data: bytes): # byte_data = bytes(event.data) self._msg_len = int.from_bytes(data[0:4], byteorder='little') data = data[4:] - #print(f"NEW MESSAGE: {self.stream_id} msg length: {len(data)} data length: {self._msg_len}") + logger.debug(f"Received new message for stream {self.stream_id} with a msg length: {self._msg_len} ({len(data)} received now)") # TODO: check message length > 0???!!!! msg_complete = False @@ -91,10 +91,11 @@ def receive_data(self, data: bytes): # Otherwise append only, we expect more data to finish this message self._msg_buffer += data data = [] - print(f"APPENDING TO EXISTING MESSAGE: {self.stream_id} msg length: {len(data)} data length: {self._msg_len}") + logger.debug(f"Appending to message for stream {self.stream_id} ({len(data)} bytes of {self._msg_len})") # If we assembled a new message, parse it if msg_complete: + logger.debug(f"Message complete for stream {self.stream_id}") try: if self.direction == StreamDirection.initiator: self.initiator_message(self._msg_buffer) From 772adbd09d6e70f59649ea2020686aac6a76be72 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 10 Jul 2025 13:05:55 +0200 Subject: [PATCH 15/51] - JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 2 +- pyjamaz/transport/jamnp_s/stream_base.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 4fd17ae0..f2ad8ec8 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -40,7 +40,7 @@ def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_i def close_jam_stream(self, stream: Stream, reason:int=0): - logger.info(f"Closing JAM stream {stream} {stream.stream_id} for {stream.direction}") + logger.info(f"Closing JAM stream {stream.stream_type} {stream.stream_id} for {stream.direction}") self._quic.reset_stream(stream.stream_id, error_code=reason) del self.streams[stream.stream_id] diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py index 8788fe02..46e03d24 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -76,7 +76,7 @@ def receive_data(self, data: bytes): # byte_data = bytes(event.data) self._msg_len = int.from_bytes(data[0:4], byteorder='little') data = data[4:] - logger.debug(f"Received new message for stream {self.stream_id} with a msg length: {self._msg_len} ({len(data)} received now)") + logger.debug(f"Received new message for stream {self.stream_id} with a msg length: {self._msg_len} ({len(data)} received)") # TODO: check message length > 0???!!!! msg_complete = False @@ -84,14 +84,13 @@ def receive_data(self, data: bytes): # If we received a full message (or more than 1 message) end_offset = self._msg_len - len(self._msg_buffer) self._msg_buffer += data[:end_offset] - data = data[end_offset:] + data = data[end_offset:] # Note: this packet could contain start of a new message msg_complete = True - #print(f"FULL MESSAGE RECEIVED {self.stream_type} stream type = {int(self.stream_id)}") else: # Otherwise append only, we expect more data to finish this message self._msg_buffer += data - data = [] - logger.debug(f"Appending to message for stream {self.stream_id} ({len(data)} bytes of {self._msg_len})") + logger.debug(f"Appending to message for stream {self.stream_id} ({self._msg_buffer} bytes of {self._msg_len})") + data = [] # Note: no new packet can be present in this data # If we assembled a new message, parse it if msg_complete: From cfa09f308e98b394de5ae897cb12422c785ad5ea Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 10 Jul 2025 13:11:43 +0200 Subject: [PATCH 16/51] - JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 22 +++++++++++----------- pyjamaz/transport/jamnp_s/protocol.py | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index f2ad8ec8..6f4bca99 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -31,16 +31,16 @@ def __init__(self, *args, **kwargs): def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_id: int=None): if stream_id is None: stream_id = self._quic.get_next_available_stream_id() - logger.debug(f"Initiating stream {stream_id} for {direction}") + logger.debug(f"QUIC initiating stream {stream_id} for {direction}") else: - logger.debug(f"Accepting stream {stream_id} for {direction}") + logger.debug(f"QUIC accepting stream {stream_id} for {direction}") self.streams[stream_id] = StreamCls(stream_id, connection=self, direction=direction) return self.streams[stream_id] def close_jam_stream(self, stream: Stream, reason:int=0): - logger.info(f"Closing JAM stream {stream.stream_type} {stream.stream_id} for {stream.direction}") + logger.info(f"QUIC closing JAM stream {stream.stream_type} {stream.stream_id} for {stream.direction}") self._quic.reset_stream(stream.stream_id, error_code=reason) del self.streams[stream.stream_id] @@ -59,7 +59,7 @@ def send(self, stream_id: int, data: bytes, end_stream=False): def connection_lost(self, exc): - logger.info("UDP transport closed:", exc) + logger.info("QUIC UDP transport closed:", exc) self.protocol.disconnect(self) super().connection_lost(exc) # keeps aioquic tidy @@ -79,9 +79,9 @@ def quic_event_received(self, event: QuicEvent) -> None: if event and hasattr(event, "stream_id"): if event.stream_id != 0: - logger.debug(f'Stream {event.stream_id} received data {event}') + logger.debug(f'QUIC stream {event.stream_id} received data {event}') else: - logger.debug(f'Received non stream data {event}') + logger.debug(f'QUIC received non stream data {event}') if isinstance(event, HandshakeCompleted): #TODO: @@ -103,10 +103,10 @@ def quic_event_received(self, event: QuicEvent) -> None: stream_id = event.stream_id reset_code = event.error_code - logger.info(f'StreamReset {stream_id} {self.direction} code {reset_code}') + logger.info(f'QUIC StreamReset {stream_id} {self.direction} code {reset_code}') if stream_id not in self.streams: - raise Exception(f"Stream {stream_id} not available") + raise Exception(f"QUIC stream {stream_id} not available") self.streams[stream_id].reset(reset_code) @@ -120,7 +120,7 @@ def quic_event_received(self, event: QuicEvent) -> None: stream_type = int(data[0]) stream_cls = StreamLookup.get(stream_type) if stream_cls is None: - raise Exception(f"Stream {stream_id} is not mapped") + raise Exception(f"QUIC stream {stream_id} is not mapped") stream_obj = self.open_jam_stream(stream_cls, direction=self.direction, stream_id=stream_id) @@ -140,8 +140,8 @@ def quic_event_received(self, event: QuicEvent) -> None: self.streams[stream_id].receive_data(data) except Exception as e: #TODO: reset stream? return error? - logger.error(f"Received invalid message for stream {stream_id} ({self.streams[stream_id]}): {e}") + logger.error(f"JAMStream received invalid message for stream {stream_id} ({self.streams[stream_id]}): {e}") elif isinstance(event, ConnectionTerminated): - logger.info(f"Connection terminated with code {event.error_code}") + logger.info(f"QUIC connection terminated with code {event.error_code}") self.protocol.disconnect(self) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 2fd6cedc..57143fac 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -213,7 +213,7 @@ def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement async def up0_broadcast_block(self, block: Block): - logger.debug(f'JAMNP broadcasting block announcement to {len(self.connections)} connections') + logger.debug(f'UP0 broadcasting block announcement to {len(self.connections)} connections') msg = MsgUP0Announcement( header=block.header, @@ -222,7 +222,7 @@ async def up0_broadcast_block(self, block: Block): ).to_jam_bytes().to_bytes() for client_id, client in self.connections.items(): - logger.debug(f"JAMNP send block to client {client.host}:{client.port} with hash {block.header.hash}") + logger.debug(f"UP0 send block header to client {client.host}:{client.port} with hash {block.header.hash}") client.send( client.stream_up.stream_id, client.stream_up.create_message(msg), From fbeca1840346ea519f026de179b430ef13eba550 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 10 Jul 2025 13:16:49 +0200 Subject: [PATCH 17/51] - JAMNP WIP --- pyjamaz/transport/jamnp_s/protocol.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 57143fac..4e7d7dfb 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -270,7 +270,7 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 logger.debug(f"CE128 sending {len(blocks)} blocks") stream.conn.send( stream.stream_id, - stream.create_message(MsgCE128BlockRequestResponse(blocks).to_jam_bytes().to_bytes(), add_stream_type=True), + stream.create_message(MsgCE128BlockRequestResponse(blocks=blocks).to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True ) From b7f9ebd36f72c56a6837b5e8a9c86d0159a965c3 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 10 Jul 2025 13:28:31 +0200 Subject: [PATCH 18/51] - JAMNP WIP --- pyjamaz/transport/jamnp_s/protocol.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 4e7d7dfb..2bbf7423 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -236,7 +236,7 @@ def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRe conn.send( stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), - end_stream=True + #end_stream=True ) @@ -270,7 +270,8 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 logger.debug(f"CE128 sending {len(blocks)} blocks") stream.conn.send( stream.stream_id, - stream.create_message(MsgCE128BlockRequestResponse(blocks=blocks).to_jam_bytes().to_bytes(), add_stream_type=True), + #stream.create_message(MsgCE128BlockRequestResponse(blocks=blocks).to_jam_bytes().to_bytes(), add_stream_type=True), + stream.create_message(MsgCE128BlockRequestResponse(blocks=blocks).to_jam_bytes().to_bytes()), end_stream=True ) From 4cc60c21cc8caf08c5ae691a5e36b8eba56687d8 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 10 Jul 2025 20:39:15 +0200 Subject: [PATCH 19/51] - JAMNP WIP --- pyjamaz/app.py | 36 ++++-- pyjamaz/constants.py | 4 + pyjamaz/transport/jamnp_s/connection.py | 15 ++- pyjamaz/transport/jamnp_s/protocol.py | 118 +++++++++++++----- .../transport/jamnp_s/streams/stream_128.py | 13 +- 5 files changed, 136 insertions(+), 50 deletions(-) diff --git a/pyjamaz/app.py b/pyjamaz/app.py index 7373e5b3..37ce76d1 100644 --- a/pyjamaz/app.py +++ b/pyjamaz/app.py @@ -123,22 +123,34 @@ async def requested_blocks_from_json(self, data): await self.process_import_queue() - async def import_queue_add(self, block): + async def import_queue_add_blocks(self, blocks: List[Block], process: bool=False, on_success: MESSAGE_TYPES=None, on_failure: MESSAGE_TYPES=None): async with self._import_queue_lock: - self._import_queue.append(block) + for block in blocks: + self._import_queue.append(block) + if process: + await self.process_import_queue(on_success=on_success, on_failure=on_failure) - async def process_import_queue(self): + + async def process_import_queue(self, on_success:MESSAGE_TYPES=None, on_failure:MESSAGE_TYPES=None): async with self._import_queue_lock: sorted_blocks = sorted(self._import_queue, key=lambda x: x.header.timeslot) self._import_queue.clear() - for block in sorted_blocks: - if self.state.timeslot.number >= block.header.timeslot: - raise Exception(f" Import queue timeslot mismatch: {self.state.timeslot.number} >= {block.header.timeslot}") + try: + for block in sorted_blocks: + if self.state.timeslot.number >= block.header.timeslot: + raise Exception(f" Import queue timeslot mismatch: {self.state.timeslot.number} >= {block.header.timeslot}") + + await self.import_block(block) + logging.debug(f'✅ Block {block.header.timeslot} successfully imported from process_import_queue.') + + if on_success: + await self.pubsub.publish(PubSubSignal(topic=on_success, data=None)) - await self.import_block(block) - logging.debug(f'✅ Block {block.header.timeslot} successfully imported from process_import_queue.') + except Exception as e: + await self.pubsub.publish(PubSubSignal(topic=on_failure, data=None)) + logging.error(f"Error processing import queue: {e}") async def initialize(self): @@ -620,6 +632,7 @@ async def _import_block(self, block: Block, dry_run=False) -> STFOutput: async def store_block(self, block: Block): # Store block in DB + # TODO: should include hash in case of forks self.block_db.put( b'block:' + block.header.timeslot.to_bytes(length=4, byteorder='little'), block.to_jam_bytes().to_bytes() ) @@ -634,6 +647,10 @@ async def store_block(self, block: Block): self.block_db.put( b'block_number:' + block.header.hash, block.header.timeslot.to_bytes(length=4, byteorder='little') ) + self.block_db.put( + b'block_child:' + block.header.parent, block.header.hash + ) + def retrieve_block(self, timeslot: int) -> Optional[Block]: block_data = self.block_db.get(b'block:' + timeslot.to_bytes(length=4, byteorder='little')) @@ -655,6 +672,9 @@ def retrieve_block_header(self, block_hash: bytes) -> Optional[Header]: def retrieve_block_hash(self, timeslot: int) -> Optional[bytes]: return self.block_db.get(b'block_hash:' + timeslot.to_bytes(length=4, byteorder='little')) + def retrieve_block_child_hash(self, block_hash: bytes) -> Optional[bytes]: + return self.block_db.get(b'block_child:' + block_hash) + def should_produce_block(self, timeslot: int, safrole_state: SafroleState) -> bool: slot_phase_index = timeslot % EPOCH_TIMESLOTS diff --git a/pyjamaz/constants.py b/pyjamaz/constants.py index 2a041bdf..e132074e 100644 --- a/pyjamaz/constants.py +++ b/pyjamaz/constants.py @@ -45,6 +45,10 @@ class MESSAGE_TYPES(Enum): BEST_BLOCK = "best_block" FINALIZED_BLOCK = "finalized_block" + CE128_SUCCESS = "CE128_SUCCESS" + CE128_FAILURE = "CE128_FAILURE" + + PVM_MARSHALLING_OFFSET_ACCUMULATE = 5 PVM_MARSHALLING_OFFSET_TRANSFER = 10 PVM_MARSHALLING_OFFSET_REFINE = 0 diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 6f4bca99..03226401 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -106,7 +106,9 @@ def quic_event_received(self, event: QuicEvent) -> None: logger.info(f'QUIC StreamReset {stream_id} {self.direction} code {reset_code}') if stream_id not in self.streams: - raise Exception(f"QUIC stream {stream_id} not available") + #raise Exception(f"QUIC stream {stream_id} not available") + logger.debug(f"QUIC stream {stream_id} already closed") + return self.streams[stream_id].reset(reset_code) @@ -136,11 +138,12 @@ def quic_event_received(self, event: QuicEvent) -> None: #TODO: of idem? kan een accepting connection bv ook een block request terug sturen? raise Exception(f"Received data from unknown stream id: {stream_id}") - try: - self.streams[stream_id].receive_data(data) - except Exception as e: - #TODO: reset stream? return error? - logger.error(f"JAMStream received invalid message for stream {stream_id} ({self.streams[stream_id]}): {e}") + self.streams[stream_id].receive_data(data) + # try: + # self.streams[stream_id].receive_data(data) + # except Exception as e: + # #TODO: reset stream? return error? + # logger.error(f"JAMStream received invalid message for stream {stream_id} ({self.streams[stream_id]}): {e}") elif isinstance(event, ConnectionTerminated): logger.info(f"QUIC connection terminated with code {event.error_code}") diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 2bbf7423..ef3e605d 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -2,7 +2,7 @@ import logging import ssl -from typing import Dict +from typing import Dict, List from typing import Optional, cast from aioquic.asyncio import serve @@ -12,7 +12,8 @@ from aioquic.asyncio.client import connect from ulid import ULID -from pyjamaz.models.block import Block +from pyjamaz.constants import MESSAGE_TYPES +from pyjamaz.models.block import Block, Header from pyjamaz.transport.jamnp_s.stream_base import StreamDirection from pyjamaz.transport.types import ProtocolType @@ -78,6 +79,9 @@ def __init__(self, host, port, certificate, private_key, app): if bl_hash.startswith('0x'): bl_hash = bl_hash[2:] bl_hash = bl_hash[:8] + self.pubsub.subscribe(MESSAGE_TYPES.CE128_SUCCESS, self.ce128_processed_block_request) + self.pubsub.subscribe(MESSAGE_TYPES.CE128_FAILURE, self.ce128_processed_block_request) + self.protocol_name = JAMNPS.PROTOCOL_NAME.format(bl_hash) self.session_ticket_store = SessionTicketStore() self.configuration = QuicConfiguration( @@ -185,9 +189,17 @@ def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): logger.debug(f"Received newer block from handshake: {msg.header_hash} -> initiate CE128RequestBlocks") self.state_requesting_blocks = True #self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(msg.header_hash, MsgCE128BlockRequestDirection.DESC.value, 1)) #TODO: max_blocks=1 for now, >1 results in error from node? - slot = self.app.state.timeslot.number - bl_hash = self.app.retrieve_block_hash(slot) - self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(bl_hash, MsgCE128BlockRequestDirection.ASC.value, 1)) #TODO: max_blocks=1 for now, >1 results in error from node? + #slot = self.app.state.timeslot.number + #bl_hash = self.app.retrieve_block_hash(slot) + # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? + self.ce128_initiate_block_request( + conn, + MsgCE128BlockRequest( + msg.header_hash, + MsgCE128BlockRequestDirection.DESC.value, + 10 + ) + ) def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement): @@ -209,7 +221,15 @@ def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement if not block: logger.debug(f"Received new block announcement from up0: {msg.header.hash}") self.state_requesting_blocks = True - self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(msg.header.hash, MsgCE128BlockRequestDirection.ASC.value, 1)) #TODO: max_blocks=1 for now, >1 results in error from node? + # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? + self.ce128_initiate_block_request( + conn, + MsgCE128BlockRequest( + msg.header.hash, + MsgCE128BlockRequestDirection.DESC.value, + 10 + ) + ) async def up0_broadcast_block(self, block: Block): @@ -231,48 +251,88 @@ async def up0_broadcast_block(self, block: Block): def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRequest): + #TODO: missch een batch param meegeven, zodat we een grotere reeks kunnen binnenhalen maar batchen... stream = conn.open_jam_stream(StreamBlockRequest, direction=StreamDirection.initiator) - logger.debug(f"CE128 initiating block request on streamid: {stream.stream_id} header hash: {req.header_hash} direction: {req.direction}, max_block: {req.max_blocks}") + logger.debug(f"CE128 initiating block request on stream id: {stream.stream_id} direction: {req.direction}, max_block: {req.max_blocks} header hash: {req.header_hash}") conn.send( stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), - #end_stream=True + end_stream=True ) - def ce128_received_block_request(self, conn: JAMConnection, block: Block): - logger.debug(f"CE128 received block request block") - asyncio.create_task(self.app.import_queue_add(block)) - #TODO: check?: if block.header.parent == bytes(32) or self.retrieve_block_by_hash(block.header.parent): - self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(block.header.hash, MsgCE128BlockRequestDirection.ASC.value, 1)) # TODO: max_blocks=1 for now, >1 results in error from node? - - - def ce128_finished_block_request(self): - self.state_requesting_blocks = False - logger.debug("Finished block request, start parsing import queue") - asyncio.create_task(self.app.process_import_queue()) + def ce128_received_block_request(self, stream: StreamBlockRequest, req: MsgCE128BlockRequestResponse): + blocks = req.blocks + logger.debug(f"CE128 received block request (parsing {len(blocks)} blocks)") + asyncio.create_task( + self.app.import_queue_add_blocks( + blocks, + process=True, + on_success=MESSAGE_TYPES.CE128_SUCCESS, + on_failure=MESSAGE_TYPES.CE128_FAILURE + ) + ) def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE128BlockRequest): - blocks = [] - direction = block_req.direction * -1 - block = self.app.retrieve_block_by_hash(block_req.header_hash) - if block: - blocks.append(block) + block:Block = None + blocks:List[Block] = [] + first_block_hash = bytes(32) + last_block_hash = self.app.retrieve_block_hash(self.app.state.timeslot.number) + #TODO: check <=0 -> bad request + block_header:Header = self.app.retrieve_block_header(block_req.header_hash) + next_hash = block_req.header_hash + + if block_header and block_req.max_blocks > 0: + #direction = 1 + block_req.direction * -2 for x in range(block_req.max_blocks): - block = self.app.retrieve_block(block.header.timeslot+direction) - if block: + + if block_req.direction == MsgCE128BlockRequestDirection.ASC.value: + # Note: exclusive request block hash + block_child_hash: bytes = self.app.retrieve_block_child_hash(next_hash) + block = self.app.retrieve_block_by_hash(block_child_hash) + if block: next_hash = block.header.hash + elif block_req.direction == MsgCE128BlockRequestDirection.DESC.value: + # Note: inclusive request block hash + block = self.app.retrieve_block_by_hash(next_hash) + if block: next_hash = block.header.parent + else: + # TODO: error + raise Exception("HMmmmmmz?????") + + if block and block.header.timeslot > 0: blocks.append(block) else: break - logger.debug(f"CE128 sending {len(blocks)} blocks") + logger.debug(f"CE128 sending {len(blocks)} blocks (direction={block_req.direction} max blocks={block_req.max_blocks})") + + if blocks: stream.conn.send( stream.stream_id, - #stream.create_message(MsgCE128BlockRequestResponse(blocks=blocks).to_jam_bytes().to_bytes(), add_stream_type=True), stream.create_message(MsgCE128BlockRequestResponse(blocks=blocks).to_jam_bytes().to_bytes()), end_stream=True ) - stream.acceptor_reset(1) + #TODO: check ook direction!!! + if not block or block_req.header_hash == last_block_hash or block.header.timeslot == 0: + #TODO: PolkaJAM lijkt in dit geval een reset te sturen???? + #TODO: ook op max_blocks checken -> bijhouden op de stream? + logger.debug(f"CE128 block request finished") + stream.acceptor_reset(1) #TODO: REVERSE ENGINEER DEZE CODES + + + def ce128_abort_block_request(self): + logger.debug(f"Finished block request, start parsing import queue {len(self.app._import_queue)}") + # Note: could happen during a block_request "session" + asyncio.create_task( + self.app.process_import_queue( + on_success=MESSAGE_TYPES.CE128_SUCCESS, + on_failure=MESSAGE_TYPES.CE128_FAILURE + ) + ) + + + async def ce128_processed_block_request(self, *args, **kwargs): + self.state_requesting_blocks = False \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py index 37da4959..8ce964d4 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -2,8 +2,7 @@ from jamcodec.base import JamBytes -from pyjamaz.models.block import Block -from pyjamaz.transport.jamnp_s.message_types import MsgCE128BlockRequest +from pyjamaz.transport.jamnp_s.message_types import MsgCE128BlockRequest, MsgCE128BlockRequestResponse from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -19,7 +18,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE128 received reset code: {reset_code}") - self.protocol.ce128_finished_block_request() + self.protocol.ce128_abort_block_request() super().initiator_reset(reset_code) @@ -30,10 +29,10 @@ def initiator_message(self, data: bytes): self.initiator_reset(reset_code=0) return - block = Block.from_jam_bytes(JamBytes(data)) - block_hash = block.header.hash - logger.debug(f"CE128 initiated stream {self.stream_id} parsed block: {block_hash.hex()} parent:{block.header.parent.hex()}") - self.protocol.ce128_received_block_request(self.conn, block) + logger.debug(f"CE128 initiated stream {self.stream_id} received block request response: {len(data)} bytes") + req = MsgCE128BlockRequestResponse.from_jam_bytes(JamBytes(data)) + self.protocol.ce128_received_block_request(self, req) + def acceptor_message(self, data: bytes): logger.debug(f"CE128 acceptor stream {self.stream_id} received block request") From 5385ac3798f80cec0f80c18f84da65eba31a422b Mon Sep 17 00:00:00 2001 From: matthijsb Date: Fri, 11 Jul 2025 11:39:35 +0200 Subject: [PATCH 20/51] - JAMNP UP0 and CE128 working! --- pyjamaz/app.py | 3 +- pyjamaz/cli.py | 4 +- pyjamaz/data/chainspecs/dev-spec.json | 35 ++++++++------- pyjamaz/transport/jamnp_s/message_types.py | 52 +++++++++++++++++----- pyjamaz/transport/jamnp_s/protocol.py | 41 ++++++++++------- 5 files changed, 88 insertions(+), 47 deletions(-) diff --git a/pyjamaz/app.py b/pyjamaz/app.py index 37ce76d1..4e9fa911 100644 --- a/pyjamaz/app.py +++ b/pyjamaz/app.py @@ -149,7 +149,8 @@ async def process_import_queue(self, on_success:MESSAGE_TYPES=None, on_failure:M await self.pubsub.publish(PubSubSignal(topic=on_success, data=None)) except Exception as e: - await self.pubsub.publish(PubSubSignal(topic=on_failure, data=None)) + if on_failure: + await self.pubsub.publish(PubSubSignal(topic=on_failure, data=None)) logging.error(f"Error processing import queue: {e}") diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 469de8ff..84f4706c 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -273,8 +273,8 @@ async def main(ctx, seed, port, ts, culprit, block_dir, record_traces, custom_db try: #await nps_protocol.connect(conn["addr"], conn["port"]) #await nps_protocol.connect("54.39.18.64", 40000) - #await nps_protocol.connect("localhost", conn["port"]) - await nps_protocol.connect("127.0.0.1", 40000) #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + await nps_protocol.connect("localhost", conn["port"]) + #await nps_protocol.connect("127.0.0.1", 40000) #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! except Exception as exc: traceback.print_exc() else: diff --git a/pyjamaz/data/chainspecs/dev-spec.json b/pyjamaz/data/chainspecs/dev-spec.json index a5cd2adc..90b728df 100644 --- a/pyjamaz/data/chainspecs/dev-spec.json +++ b/pyjamaz/data/chainspecs/dev-spec.json @@ -1,27 +1,28 @@ { "bootnodes": [], - "genesis_header": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25bb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aaff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499acedee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d9339326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e3410746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a2105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c060000ffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "id": "dev", "genesis_state": { - "07000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0f000000000000000000000000000000000000000000000000000000000000": "000000000000000000000000", "06000000000000000000000000000000000000000000000000000000000000": "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25bb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aae88bd757ad5b9bedf372d8d3f0cf6c962a469db61a265f6418e1ffed86da29ec", - "0c000000000000000000000000000000000000000000000000000000000000": "00000000000000000000000000", - "01000000000000000000000000000000000000000000000000000000000000": "08675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be008675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0", - "0e000000000000000000000000000000000000000000000000000000000000": "000000000000000000000000", - "0b000000000000000000000000000000000000000000000000000000000000": "00000000", + "00fe00ff00ff00ff5b06efb68e00d58f668d6c2f5e34bdfe52c18d1176b230": "4e00136a616d2d6e756c6c2d617574686f72697a657206302e312e32330a4170616368652d322e30012550617269747920546563686e6f6c6f67696573203c61646d696e407061726974792e696f3e801e006827000000002000e200ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffec00ffffe600ffffe400ffffe400ffffea00ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffee00ffffe400ffffe400ffffe400ffffe400ffffe800ffff6e00ffff6e00ffff6e00ffff6c00ffff6a00ffff6c00ffff6a00ffff6e00ffff6e00ffff6e00ffff6e00ffff6e00ffff6a00ffff6a00ffff6e00ffff6a00ffff6e00ffff6a00ffff3400ffff3400ffff3400ffff3200ffff2e00ffff3200ffff2e00ffff3400ffff3400ffff3400ffff3400ffff3400ffff2e00ffff2e00ffff3400ffff2e00ffff3400ffff2e00fffff0fffefff0fffefff0fffeffeefffeffe8fffeffeefffeffe8fffefff0fffefff0fffefff0fffefff0fffefff0fffeffe8fffeffe8fffefff0fffeffe8fffefff0fffeffe8fffeff63616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c75652f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f6d656d2e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f736c6963652e72734e756c6c20417574686f72697a65722c205b5d2c20206761732c2020706172616d2c20202862797465732020746f6b656e417574683d3c3e417574686f72697a6174696f6e206661696c656473657276696365732f6e756c6c2d617574686f72697a65722f7372632f6c69622e72736361706163697479206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72736d656d6f727920616c6c6f636174696f6e206f6620206279746573206661696c65642f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f737472696e672e72736120666f726d617474696e6720747261697420696d706c656d656e746174696f6e2072657475726e656420616e206572726f72207768656e2074686520756e6465726c79696e672073747265616d20646964206e6f742f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f666d742e72732e2e00000029207768656e20736c6963696e67206072616e676520656e6420696e6465782001000000000000000000000000000000303132333435363738396162636465663a63616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c756570616e69636b6564206174203a0a696e646578206f7574206f6620626f756e64733a20746865206c656e20697320206275742074686520696e646578206973203a202f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6e756d2e727330303031303230333034303530363037303830393130313131323133313431353136313731383139323032313232323332343235323632373238323933303331333233333334333533363337333833393430343134323433343434353436343734383439353035313532353335343535353635373538353936303631363236333634363536363637363836393730373137323733373437353736373737383739383038313832383338343835383638373838383939303931393239333934393539363937393839392f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6d6f642e727372616e676520737461727420696e64657820206f7574206f662072616e676520666f7220736c696365206f66206c656e67746820736c69636520696e64657820737461727473206174202062757420656e6473206174202f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f6d6f642e727301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030304040404040000000000000000000000005b2e2e2e5d626567696e203c3d20656e642028203c3d20606279746520696e64657820206973206e6f742061206368617220626f756e646172793b20697420697320696e736964652029206f662060206973206f7574206f6620626f756e6473206f6620602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f756e69636f64652f7072696e7461626c652e7273000601010301040205070702080809020a050b020e04100111021205131c140115021702190d1c051d081f0124016a046b02af03b102bc02cf02d102d40cd509d602d702da01e005e102e704e802ee20f004f802fa04fb010c273b3e4e4f8f9e9e9f7b8b9396a2b2ba86b1060709363d3e56f3d0d1041418363756577faaaeafbd35e01287898e9e040d0e11122931343a4546494a4e4f64658a8c8d8fb6c1c3c4c6cbd65cb6b71b1c07080a0b141736393aa8a9d8d909379091a8070a3b3e66698f92116f5fbfeeef5a62f4fcff53549a9b2e2f2728559da0a1a3a4a7a8adbabcc4060b0c151d3a3f4551a6a7cccda007191a22253e3fe7ecefffc5c604202325262833383a484a4c50535556585a5c5e606365666b73787d7f8aa4aaafb0c0d0aeaf6e6fddde935e227b0503042d036603012f2e80821d03310f1c0424091e052b0544040e2a80aa06240424042808340b4e03340c813709160a08183b45390363080930160521031b05014038044b052f040a070907402027040c0936033a051a07040c07504937330d33072e080a0626031d080280d0521003372c082a161a261c1417094e042409440d19070a0648082709750b423e2a063b050a06510601051003050b5908021d621e48080a80a65e22450b0a060d133a060a06141c2c041780b93c64530c48090a46451b4808530d49070a80b6220e0a06460a1d03474937030e080a0639070a813619073b031d55010f320d839b66750b80c48a4c630d843010160a8f9b0582479ab93a86c68239072a045c06260a460a28051381b03a80c65b654b0439071140050b020e97f80884d6290aa2e781330f011d060e0408818c89046b050d030907108f6080fa0681b44c4709743c80f60a73087015467a140c140c570919808781470385420f1584501f060680d52b053e2101702d031a040281401f113a050181d02a80d62b040181e080f7294c040a04028311444c3d80c23c06010455051b3402810e2c04640c560a80ae381d0d2c040907020e06809a83d80411030d0377045f060c04010f0c0438080a0628082c04023e81540c1d030a0538071c06090780fa840600010305050606020706080709110a1c0b190c1a0d100e0c0f0410031212130916011704180119031a071b011c021f1620032b032d0b2e01300431023201a704a902aa04ab08fa02fb05fd02fe03ff09ad78798b8da23057588b8c901cdd0e0f4b4cfbfc2e2f3f5c5d5fe2848d8e9192a9b1babbc5c6c9cadee4e5ff00041112293134373a3b3d494a5d848e92a9b1b4babbc6cacecfe4e500040d0e11122931343a3b4546494a5e646584919b9dc9cecf0d11293a3b4549575b5c5e5f64658d91a9b4babbc5c9dfe4e5f00d11454964658084b2bcbebfd5d7f0f183858ba4a6bebfc5c7cfdadb4898bdcdc6cecf494e4f57595e5f898e8fb1b6b7bfc1c6c7d71116175b5cf6f7feff806d71dedf0e1f6e6f1c1d5f7d7eaeaf4dbbbc16171e1f46474e4f585a5c5e7e7fb5c5d4d5dcf0f1f572738f747596262e2fa7afb7bfc7cfd7df9a00409798308f1fcecfd2d4ceff4e4f5a5b07080f10272feeef6e6f373d3f42459091536775c8c9d0d1d8d9e7feff00205f2282df048244081b04061181ac0e80ab051f08811c03190801042f043404070301070607110a500f1207550703041c0a090308030703020303030c0405030b06010e15054e071b0757070206170c500443032d03010411060f0c3a041d255f206d046a2580c80582b0031a0682fd03590716091809140c140c6a060a061a0659072b05460a2c040c040103310b2c041a060b0380ac060a062f3180f4083c030f033e0538082b0582ff1118082f112d03210f210f808c04829a160b158894052f053b07020e180980be22740c80d61a81100580e109f29e033709815c1480b80880dd153b030a06380846080c06740b1e035a0459098083181c0a16094c04808a06aba40c170431a10481da26070c050580a61081f50701202a064c04808d0480be031b030f0d2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f756e69636f64652f756e69636f64655f646174612e7273617474656d707420746f2063616c63756c617465207468652072656d61696e646572207769746820612064697669736f72206f66207a65726f0003000083042000910560005d13a0001217201f0c20601fef2c202b2a30a02b6fa6602c02a8e02c1efbe02d00fe20369eff6036fd01e136010a2137240de137ab0e61392f18e139301ce14af31ee14e4034a1521e61e153f06a61544f6fe1549dbc615500cf615665d1a15600da215700e0a158aee2215aece4e15bd0e8615c2000ee5cf0017f5d00700007002d0101010201020101480b30151001650702060202010423011e1b5b0b3a09090118040109010301052b033b092a180120370101010408040103070a021d013a0101010204080109010a021a010202390104020402020303011e0203010b0239010405010204011402160601013a0101020104080107030a021e013b0101010c01090128010301370101030503010407020b021d013a01020201010303010407020b021c02390201010204080109010a021d0148010401020301010801510102070c08620102090b0749021b0101010101370e01050102050b0124090166040106010202021902040310040d01020206010f01000300041c031d021e02400201070801020b09012d030101750222017603040209010603db0202013a010107010101010208060a0201301f3104300a040326090c02200402063801010203010105380802029803010d0107040106010302c6400001c32100038d016020000669020004010a200250020001030104011902050197021a120d012608190b01012c03300102040202020124014306020202020c0108012f01330101030202050201012a020801ee010201040100010010101000020001e201950500030102050428030401a5020004410500024f04460b31047b01360f290102020a033104020207013d03240501083e010c0234090101080402015f030204060102019d01030815023902010101010c0109010e07030543010206010102010103040301010e025508020301011701510102060101020101020102eb010204060201021b025508020101026a0101010208650101010204010500090102f5010a040401900402020401200a280602040801090602032e0d010200070106010152160207010201027a06030101020107010148020301010100020b023405050317010001060f000c030300053b0700013f0451010b020002002e021700050306080802071e0494030037043208010e011605010f000701110207010201056401a00700013d040004fe0200076d07006080f0002f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e72734572726f722f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6d706163742e72736372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f686f73745f63616c6c732e7273686f73742063616c6c2072657475726e7320636f727265637420747970653b207165646974656d206d75737420626520617661696c61626c653b2071656450616e69632068616e646c65722063616c6c65642150616e6963206d6573736167653a2050726f746f636f6c20706172616d65746572732061726520696e76616c69646372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f737461727475702e72736372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f726573756c742e72734f7572206f776e2041504920696d706c2068617320726573756c74656420696e20737563636573732076616c7565207768696368206973206f7574206f662072616e67652e6372617465732f6a616d2d74797065732f7372632f73696d706c652e727360726563656e745f626c6f636b5f636f756e7460206d6179206265206e6f206c6172676572207468616e2060426c6f636b496e6465783a3a4d415860202832353529607472616e736665725f6d656d6f5f73697a6560206d75737420626520657175616c20746f7220604d454d4f5f4c454e602028313238296076616c5f636f756e7420213d20636f72655f636f756e74202a2056414c535f5045525f434f524560202833296076616c5f636f756e746020646f6573206e6f7420646976696465206279206056414c535f5045525f434f524560202833296065706f63685f7461696c5f737461727460206d757374206265206c657373207468616e206065706f63685f706572696f646060726f746174696f6e5f706572696f646020646f6573206e6f742064697669646520696e746f206065706f63685f706572696f64606062617369635f70696563655f6c656e6020646f6573206e6f742064697669646520696e746f20605345474d454e545f4c454e602028342c313034296062617369635f70696563655f6c656e60206973206e6f74206576656e2e2e2e2230782b2a300000000000000000000000000000000000000000000001000000000000009200000000000000a30101000000000054000000000000002000000009000000f70101000000000077000000000000009f000000190000006e020100000000001200000000000000800201000000000003000000000000008302010000000000060000000000000089020100000000000800000000000000990201000000000006000000000000009f020100000000000600000000000000a5020100000000000100000000000000a6020100000000001400000000000000ba020100000000002300000000000000160000000d00000094000000000000001800000000000000080000000000000096000000000000009c00000000000000a400000000000000dd020100000000001100000000000000ee0201000000000079000000000000002b02000011000000de030100000000001500000000000000f3030100000000000d0000000000000067030100000000007700000000000000b60100000d00000000040100000000007800000000000000ea010000170000000000000000000000000000000000000001000000000000009200000000000000ce0401000000000075000000000000007e0200000e000000000401000000000078000000000000008d0500001b000000010000000000000000000000000000008805010000000000010000000000000088050100000000000100000000000000c2050100000000002000000000000000e205010000000000120000000000000001000000000000000000000000000000f4050100000000000200000000000000f60501000000000078000000000000005b0000001700000036070100000000007800000000000000a70900002600000036070100000000007800000000000000b00900001a000000ae070100000000001200000000000000c007010000000000220000000000000058050100000000001000000000000000c0070100000000002200000000000000e2070100000000001600000000000000f8070100000000000d0000000000000083090100000000000e0000000000000091090100000000000400000000000000480501000000000010000000000000009509010000000000010000000000000096090100000000000b00000000000000a109010000000000260000000000000091020100000000000800000000000000c70901000000000006000000000000009509010000000000010000000000000096090100000000000b00000000000000cd0901000000000016000000000000009509010000000000010000000000000005080100000000007800000000000000f20000002c000000e30901000000000082000000000000001a00000036000000e30901000000000082000000000000000a0000002b0000000e1001000000000085000000000000004e000000280000000e1001000000000085000000000000005a00000016000000931001000000000039000000000000000000000000000000080100000000000008000000000000003c010000000000004e010000000000005001000000000000a61401000000000060000000000000002d0100003d00000043140100000000005e00000000000000920400001e00000043140100000000005e000000000000006c0400000f00000043140100000000005e000000000000009e0400001000000006150100000000002700000000000000c90000001c000000000000000000000000000000000000000100000000000000920000000000000006150100000000002700000000000000d10000002e00000006150100000000002700000000000000db0000002e00000006150100000000002700000000000000ee0000002700000006150100000000002700000000000000f3000000260000000000000000000000100000000000000008000000000000005201000000000000ae1501000000000024000000000000000400000014000000f5150100000000004500000000000000d2150100000000002300000000000000560000000d0000003a160100000000001e000000000000009a0100000c0000003a160100000000001e000000000000009d0100000c0000003a160100000000001e000000000000000002000001000000f4170100000000000100000000000000f4170100000000000100000000000000f5170100000000000200000000000000f1170100000000000300000000000000f5170100000000000200000000000000f7170100000000000100000000000000f8170100000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000001000000000000000000c000008000000f969000080be02c0db5c02003800ad01da03100452048404aa04de0460067d0b280c7a0c640ed90e100f54102711c1128b13381492142616681608172d19d019431b531b631bbf1c301e521e6f1e8a1eb11ee01e571f861fa71fbf1f3e20602074209920a920c120f120fe200c211e213621462165219021ad211c223c224b225b22f72204231223fb24562565256d257a2508265a268b26c326da26ed26fa2625274b27522779271a283f284628af28d52821296829e429f82ab82b072c292c6b2c982cae2cd82cf12c492d9c30b630e4305c31db316932c132d13210332a33503407357f376238a538c838dc38723a783a863a8c3a923adf3b7f3cdf3e053f1f3f363f453f603f753f8c3fa53fee410643604366436b4384439b43ab43c543fb4302440944424449441a45b145e94544466347e147f147fa471148b6485249ea49754aa64a0a4b424b534b814bd94bf34b474c594cc44c844dda4d074ee14e6b4f844fa34f5b506e50af505451f35116529852be52125328533453dd532c546b54b5546e56280f827a08827910648764a828b52d951170fa7b1088057b1580057b1678057b18f8017b17f00149216003951720029518600350100400438218200214070000000000000080ab780628242382173002551708072820228479f85219080728162252191007280f228479fe52191807280522957ae6551a080728fb21957ade330b22aeba0628ef215219440728e8219579ba5519120728de219579a85519060728d4219579a25519100728ca219579925519040728c02195798e55190c0728b6219577825517080728ac21821728027c7c187c76197c791e7b19d8017c791f7b19d0017c79207b19c8017c79217b19c0017c796b7c7a6a7c72427c7b6c8a9908d4a9097c74438aba107c73447c70457c7b567b1be8017c7b577b1be0017c7b5c7b1b98017c7b5d7b1b90017c7b6d7c756e7b1568017c756f7b1560017c75707b1558017c75717b1550017c757a7b1588017c757b7b1580017c757c7b1578017c757d7b1570017c7582007b15b8017c7583007b15b0017c7584007b15a8017c7585007b15a0018abb18d4ba0ad49a055108357b1248017b16300164367b1c40017b1438017b10280150100624578210280182143801821c4001646382163001821248010184570151070628ab2152050628af2133070810c3570751070628aa218217e0019777088218e801d4780852080628a0218217d0018a77088219c8018a9910821ac0018aaa18821bd801d4b707d4a909d49707c38708510806287d218218b0018a88088219a8018a9910821aa0018aaa18821bb801d4b808d4a909d49808ac7806285f218217900197770882189801d487079678abaa6e885428555508284d21976808d4c808978901c898086e88aa8706284321821780018a7708821878018a8810821970018a9918821a8801d4a707d49808d487075127800008282321390864270353280001082820218215600197550882195801979910821a500197aa18974b08970c0882186801d45808d4a909d42b0ad43c0bd498083d0a6427037b1748013e0b5827038217f8019777209877207b19a0003d086027035207062814201408ffffffff000000008219f001d298087c89510958330aff00958801aba9535517090728ed1f7c87017c897c8a027c8b03977708d4970797aa1097bb18d4ba0ad4a7077c89057c8a047c8b067c8807979908d4a90997bb10978818d4b808d49808978820d487072886003307288a006c9657060b330549212002285184694033050151093c84692033050251093384691033050351092a84690833050451092184690433050551091884690233050651090f846901330507510906285a209577ff49212002ae570628481f951720026459501008c3578557079b77ff82182002e07607975503cf5707c8870701987810510806281d1f791706024921300308951760039518300350100a283f8218600314070000000000000080ab780628d41f7b18f801821770035517200728fd1e821568039577e09558207b1820057b172805951720029518200550100cb3408216200214070000000000000080ab760628ca1e82173002821828027b1708057b1800058217f80151070a645750100e4d5482170005821808057b1710027b1818027b1608024921a003079517b8039518a0035010108e3e8216b80314050000000000000080ab560628421f8218c0038217c8037b18087b18d0037b17d803951720029518d003501012274082192002ab590628761d8218d803821728027b17785518040728591d7b1988009587fc5517200728461d7b16688217d0037c797b19607c79017b19487c79027b19587c79037b19509588dc9579247b19d0037b18d8037c78057c79047c7a067c7b07978808d4980897aa1097bb18d4ba0ad4a8087c79097c7a087c7b0a7c7c0b979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1820027c780d7c790c7c7a0e7c7b0f978808d4980897aa1097bb18d4ba0ad4a8087c79117c7a107c7b127c7c13979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1828027c78157c79147c7a167c7b17978808d4980897aa1097bb187c7919d4ba0ad4a8087c7a189799087c7b1a7c7c1bd4a909821a30027b1a4097bb1097cc18d4cb0bd4b909979920d498087b1830027c781d7c791c7c7a1e7c7b1f978808d4980897aa1097bb18d4ba0ad4a8087c79217c7a207c7b227c7723979908d4a90997bb10977718d4b707d49707977720d487077b173802951760039518d003501014a53e82186003140700000000000000807b188000ab780628d61b8217200282182802821968037b19707b17e0037b18e80382183002821a38028217d803821970037b19387b18f0033302207b1af803ae2706288c1b8218d003957be07c8a017c8c7c86027c850397aa08d4ca0a976610975518d46505d45a0a7c8c057c86047c85067c890797cc08d46c0c975510979918d45909d4c909979920d4a9097b1900057c89097c8a087c8c0a7c860b979908d4a90997cc10976618d46c0cd4c9097c8a0d7c8c0c7c860e7c850f97aa08d4ca0a976610975518d46505d45a0a97aa20d4a9097b1908057c89117c8a107c8c127c8613979908d4a90997cc10976618d46c0cd4c9097c8a157c8c147c86167c851797aa08d4ca0a976610975518d46505d45a0a97aa20d4a9097b1910057c89197c8a187c8c1a7c861b979908d4a90997cc10976618d46c0c7c861d7c851c958a20d4c909976608d465057c8c1e7c861f7b1ad0037b1bd80397cc10976618d46c0cd45c0c97cc20d4c9097b191805551b2007285c1a7ca9197cab187cac1a7ca61b979908d4b90997cc10976618d46c0cd4c9097cab1d7cac1c7ca61e7ca51f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b1938057ca9117cab107cac127ca613979908d4b90997cc10976618d46c0cd4c9097cab157cac147ca6167ca51797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b1930057ca9097cab087cac0a7ca60b979908d4b90997cc10976618d46c0cd4c9097cab0d7cac0c7ca60e7ca50f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b1928057ca9017cab7cac027ca603979908d4b90997cc10976618d46c0cd4c9097cab057cac047ca6067caa0797bb08d4cb0b97661097aa18d46a0ad4ba0a97aa20d49a0a8479e07b1a200552194007283b197c8a597c8b587c8c5a7c865b97aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b5d7c8c5c7c865e7c855f97bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a48037c8a517c8b507c8c527c865397aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b557c8c547c86567c855797bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a40037c8a497c8b487c8c4a7c864b97aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b4d7c8c4c7c864e7c854f97bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a38037c8a417c8b407c8c427c864397aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b457c8c447c86467c854797bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a300352196007281c187c89797c8a787c8b7a7c8c7b979908d4a90997bb1097cc18d4cb0bd4b9097c8a7d7c8b7c7c8c7e7c867f97aa08d4ba0a97cc10976618d46c0cd4ca0a97aa20d4a9097b1978037c89717c8a707c8b727c8c73979908d4a90997bb1097cc18d4cb0bd4b9097c8a757c8b747c8c767c867797aa08d4ba0a97cc10976618d46c0cd4ca0a97aa20d4a9097b1970037c89697c8a687c8b6a7c8c6b979908d4a90997bb1097cc18d4cb0bd4b9097c8a6d7c8b6c7c8c6e7c866f97aa08d4ba0a97cc10976618d46c0cd4ca0a97aa20d4a9097b1968037c89617c8a607c8b627c8c63979908d4a90997bb1097cc18d4cb0bd4b9097c8a657c8b647c8c667c866797aa08d4ba0a97cc10976618d46c0cd4ca0a97aa20d4a909847afc330b80007b196003abba0628f7167c8980007b19307c8981007b19187c8982007b19287c8983007b192095777cff958884007b18d0037b17d8039517d003501016f43a837751070628ba168387492140053309017b19480549215005510758033303978820988820821bd8038216d0034921680101280d82185001c9c8085108cb02821a400533070002e6780cc93a077b1850017b1c6001aec77dc8c3057b13b801ae350628ae1698583b330751080628ad1697590559090628b6167b1bf801510a1797aa05821768017b1720027b1a300233070128053307017b17280295175805330801951a20025010188d3b82175805510706286a16821760057b1768017b1748057b1540058213b801821bf801821c6001019735059ac77b175801330201330701284d956620821768018215f001c857079518200233092050101a234f8213b8019533017b135005821c60018212e801d8c20795552095220182185801c82808821bf80195bbe0330901aa980aff330820ac8bc1017b1bf8017c67117c68107c69127b15f0017c6a13977708d4870797991097aa18d4a909d497077c68157c69147c6a167c6b17978808d4980897aa1097bb18d4ba0ad4a808978820d478047c671f7b17e0017c671e7b17d8017c671c7b17d0017c671d7b17c8017c671b7b17c0017c671a7b17b8017c67187b17b0017c67197b17a8017c67077b17a0017c67067b1798017c67047b1790017c67057b1788017c67037b1780017c67027b1778017c677b1770017c60017b12e8017c620f7c650e7c6c0c7c6b0d7c6a0b7c67097c68087c690a7b143002977708d4870797991097aa18d4a909d4970797bb08d4cb0b975510972218d45208643cd4b808978820d487077b17280297030882177001d473078218780197881082198001979918d49808d487078218880197880882199001d4980882199801979910821aa00197aa18d4a909d49808978820d487077b1720028217a8019777088218b001d487078218b8019788108219c001979918d49808d487078218c8019788088219d001d498088219d801979910821ae00197aa18d4a909821a4005d49808978820d487077b1738027b1cb801abac09fe9517400550101cbc3b821748057b17680128f4fd84770151070628b71382185001c9c80852083dfd7b13b801821840057b1bd803140700000000000000807b16d003ab7806289c138216b8015106557b18f8019766059566e0140700000000000000807b17900082156801015106619558203309206457648550101ee84d83789566e05708e98218f8014911100152081e2824492190004921b801491110012851492190004911100151080c82176801501020c1494921b8011407000000000000008082189000ab7829281713821768017b17108217f8017b1790001407000000000000008082189000ab780628f412821718058218100582190805821a00057b17f8047b18f0047b19e8047b1ae004821720058218280582193005821a38057b17c0047b18c8047b19d0047b1ad804821730038218380382194003821a48037b17a0047b18a8047b19b0047b1ab804821778038218700382196803821a60037b1798047b1890047b1988047b1a80048217e0048218e8048219f004821af8047b1760047b1868047b1970047b1a78048217d8048218d0048219c804821ac0047b1758047b1850047b1948047b1a40048217b8048218b0048219a804821aa0047b1738047b1830047b1928047b1a2004821798048218900482198804821a80047b1718047b1810047b1908047b1a00049517d0035010221d36837751070628c31139075827038388ae870628b6114921e0049517e8044921e804084921f0045108350b1407f8ffffffffffff7f7b179800140700000000000000807b1770011407fcffffffffffff7f7b1798011407feffffffffffff7f7b17a001018217f004821ae0047b18b80033098000e69809c97a087b199001ae987382159001c87505ae750628d311985839330751080628d211975907821b9800ae9b0628d711510a178217e80497aa077b1720027b1a300233070828053307017b17280295176003330808951a20025010248e3682176003510706288f11821768037b17e8047b15e004330c3308012808330c330801017b1878018218d803531804110a8219d0037c977b1768017c97017b1750017c97027b1760017c97037b175801958afc9597047b17d0037b1ad803330b20acbadc097b1c48019588dc9599247b19d0037b18d8037c78017c797c7a027c7b03978808d4980897aa1097bb18d4ba0ad4a8087c79057c7a047c7b067c7c07979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1860037c78097c79087c7a0a7c7b0b978808d4980897aa1097bb18d4ba0ad4a8087c790d7c7a0c7c7b0e7c7c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1868037c78117c79107c7a127c7b13978808d4980897aa1097bb18d4ba0ad4a8087c79157c7a147c7b167c7c17979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1870037c78197c79187c7a1a7c7b1b978808d4980897aa1097bb18d4ba0ad4a8087c791d7c7a1c7c7b1e7c771f979908d4a90997bb10977718d4b707d49707977720d487077b177803951720029518d00350102644328218200282177001aa789a087b1888018217d803821828027b188001330808ac8773088218d003821930027b1940017c897b1920017c89017b1900017c89027b1910017c89037b1908017c89047b1938017c89057b1918017c89067b1930017c89077b1928019589087b19d0039579f87b19d803330a08aca91b087c89087b19e0007c89097b19c0007c890a7b19d0007c890b7b19c8007c890c7b19f8007c890d7b19d8007c890e7b19f0007c890f7b19e8009577f09588107b18d0037b17d8039517d003501028e63283775207c60739096027038387ac79bb074921000549210805024921100551078c0233029788209888204921e80102330c242817848801520860078218c801c96808330c2451087202821a00053307c701e67806c92a077b18c8017b16e001ae676dc86205ac25d80dd6c50833075208d90d965924821ba001ac9bce0d6426510a1796a7248218e8017b1820027b17300233070228053307017b17280295172005330802951a200250102a7d338217200552077c0d821728057b17e8017b1708057b15000564628216e001019625249a677b17d801330c013308012856974708d4670898770f9788319888318219e801c8590979987897029597039518200233092050102c0b478212f8019522017b1210058216e001821cf001d86c0895cc018217d801c8c707955524330901aa970eff8219d803531920fffe8217d003959ae0957b207b1bd0037b1ad803330b02acbae5fe7c76209598de9579227b19d0037b18d8037c78017c797c7a027c7b03978808d4980897aa1097bb18d4ba0ad4a8087c79057c7a047c7b067b1cf0017c7c07979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1820027c78097c79087c7a0a7c7b0b978808d4980897aa1097bb18d4ba0ad4a8087c790d7c7a0c7c7b0e7c7c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1828027c78117c79107c7a127c7b13978808d4980897aa1097bb187c7915d4ba0ad4a8087c7a149799087c7b167c7c17d4a9097c742197bb1097cc18d4cb0bd4b909979920d498087b1830027c78197c79187c7a1a7c7b1b978808d4980897aa1097bb18d4ba0ad4a8087c791d7c7a1c7c7b1e7c771f979908d4a90997bb10977718d4b707821a0005d49707977720d487077b1738027b12f801aba253fe951700057b14d00150102e2a328214d001821708057b17e8012836fe4921b0004921f801330702281b8218000582177001aa7809057b18b0007b12f80182170805017b17a8009517d00350103009308377330c245207d6048387492100053309047b190805492110055107c2023304978820988820330204281784880152089d048218b001c93808330c245108b402821b00053307c701e67803c94b077b18b001ae3769c83405ac452a0bd6c508330752082b0b965924821a9801ac9a200b7b13a8016446510b1396b7247b1220027b17300233070428053307017b17280295172005330804951a2002501032ad30821720055207ce0a821228057b1208057b15000564648213a801019645249a363300013308017b16c001287b81173c02c852087a871c811738027a8718811734027a8714811730027a871081172c027a870c811728027a8708811724028219e001979908821af001d4a909821ae80197aa107a870481172002821bd80197bb18d4ba0ad4a9097a877a89209544017b141005d83008950001c80607955524330901aa97f1fe8219d803531920e2fe8217d003959be0957c207b1cd0037b1bd803330a04acabc8fe7c78207b18f0017c78217b18e0017c78227b18e8019598dc9579247b19d0037b18d8037c78017c797c7b027c7c03978808d4980897bb1097cc18d4cb0bd4b8087c79057c7b047c7c067c7a07979908d4b90997cc1097aa18d4ca0ad4a909979920d498087b1820027c78097c79087c7a0a7c7b0b978808d4980897aa1097bb18d4ba0ad4a8087c790d7c7a0c7c7b0e7c7c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1828027c78117c79107c7a127c7b13978808d4980897aa1097bb187c7915d4ba0ad4a8087c7a149799087c7b167c7c17d4a9097c7a237b1ad80197bb1097cc18d4cb0bd4b909979920d498087b1830027c78197c79187c7a1a7c7b1b978808d4980897aa1097bb18d4ba0ad4a8087c791d7c7a1c7c7b1e7c771f979908d4a90997bb10977718d4b707821a0005d49707977720d487077b173802aba420fe951700057b14c8017b15d00164356406501034bf2f646064538215d0018214c8018216c0018212080528f3fd8217d803330802ac87f80133053304330004281d8215000582177001aa75e3018217d80382100805330802ac8710028218d0037c897b19f001958902821a60037b19d0039577fe7b17d8037b1a30038217680382197003821a78038216f004821be0047c88017b18e8017b1738037b1940037b1a4803abb6219517e0047b10e0017b15d8016445501036b82f64548215d8018210e001018217500197770882186801d487078218600197881082195801979918d49808d478028218000197880882172001d4780882191001979910821a080197aa18d4a909d498088219180197990882173801d47909821a300197aa10821b280197bb18d4ba0ad4a909979920d489038219c0009799088217e000d47909821ad00097aa10821bc80097bb18d4ba0ad4a909821ad80097aa088217f800d47a0a821bf00097bb10821ce80097cc18d4cb0bd4ba0a97aa20d4a909821be804821ae80197aa088217f001d47a0a976c07c8cb0b821788017bb7821780017bb708821740017bb7108217b0007bb7188217a8007bb7208217f8017bb7287bb5307bb0387bb440821c30038215380382174003821848037bbc487bb5507bb7587bb8607bb3687bb9707ab27879ba7c821c480195cc019566017b16f00482179001d87c08ab7c32f68218b80028528217000551072d8217080533003a289c3d8217000551070c821708055010388e3d8217b00051070c8217a80050103a7e3d8217880151070c8217800150103c6e3d821778018477018218b80052078e0682179001c9780852083ef5280d5105c4640733003828453d9518e8048287821ce0048288087b17c004140700000000000000807b18c804ab7c1828700664788277088288330c7b17c8047b18c00401821748977708821860d48707821858978810821950979918d49808d47806821718977708821830d48707821828978810821920979918821ac804821bc004d49808d478057b1aa8047b1ba0048217e0038218e8038219f003821af8037b1760037b1868037b1970037b1a7803821760048218680482197004821a78047b1730037b1838037b1940037b1a4803821758048218500482194804821a40047b1738057b1830057b1928057b1a2005821720048218280482193004821a38047b1700057b1808057b1910057b1a1805821700048218080482191004821a18047b17e0047b18e8047b19f0047b1af8048217685107148217087b1cf80150103e123c821cf80101821788007b1720028217787b1728028217407b173002821780007b1738028217707b1740028217387b174802821760038218680382197003821a78037b1750027b1858027b1960027b1a6802821790007b1770028217107b1778028217b8017b178002821748038218400382193803821a30037b17a0027b1898027b1990027b1a8802821720058218280582193005821a38057b17a8027b18b0027b19b8027b1ac002821700058218080582191005821a18057b17c8027b18d0027b19d8027b1ae0028217f8048218f0048219e804821ae0047b1700037b18f8027b19f0027b1ae8028217a0048218a8047a1508037b1c10037b1718037b1820037a1628030a7b172005951706027b1760033307dc007b176803951720057b177003330706017b177803951708027b178003330772017b178803951820027b1890037b17980333075018017b173003492138030549215003951760037b174003492148030495170005951830035010407b0782150805821b10058216000533070233083309645a0a640151060a64575010427f3a8215300282171802ab75ec04821628028218100264676459501044523e83775207d5047b16e0047b15e804951760039518e004501046f8218216600351065601821778038219680351075001570573055105ce027b19f80164585010482c3651075c058219f8017b1530037b17380349214003ac95be023308017b18f8017b17f001c887076468649650104abd3c8218f801c86808c985057b184003541502b3028217f00101c88707461702bd00461701bf004607ef8217e0048219e8049585037b1540037b1720057b192805282b82173003c9570754170289008217f00101c85707461702bd00461701bf004607ef9555037b15400301951760039518200550104c2b218218600351086f8217300382166803821978037b19f801c95707ac6725821738037b17f001c85707646950104e173cc865057b1540038217f80152079028b6951730037b18f0016458646950105015058218f0018215400328c5951730033309036458501052fd048215400382173803286bff8217300382183803821940037b1720057b1828057b193005281f3309330601017b1628057b193005140700000000000000807b17200501951720057b17e0043307027b17e8043307a018017b1760034921680302492180039517e0047b1770033306017b16780395173003951860035010546d05821540035705c303821730037b17e801821738037b17f80151051164585010567d345107ad0364760164678218f8016459501058293b8217200514080000000000000080d48707ab87d7008217200251070c8217280250105a38388217380251070c8217400250105c28387b16d8017b15e0018217700251070c8217780250105e103882162003821718037b17f0015106418217f001957520280c9566ff9555800051062e8257e051070b8257e8501060e0378257f851070a8257501062d3378257105107d8825718501064c53728cd8217100351070c8217f001501066b337821708028215e0018216d80151070c821710025010689b378217e80151070c8217f80150106a8b37646764588210880582158005821678059511900532008217280550106c6c3782172002520727ff282cff3307017b1530037b17380349214003ae954afd951730033308649550106e2c036459821840038217380382153003282efd951730033309035010700f0382184003821738032843fd82172805821830053309a01c0128c80233073309a01c0128be023309a01c0128b60282172805821830053309a01c0128a60233073309a01c01289c023309a01c012894028216f0048217e8047b17f8015206cd008217e00451070c8217f801501072b5368217900051071c82171033007428a3368217400551070c8217680150107495368217800051070b8217705010768636821988000151090b821778501078763633072d1501330ae81c01330b081d013308239519770528881433072d1501330ae81c01330b081d0133082395197705286f1433073309a01c0128f901281282176005821868053309a01c0128e7013309a01c0128df0133073309a01c0128d501281282176803821870033309a01c0128c3013309a01c0128bb018217f801957520280d9566ff95558000510627ff8257e051070b8257e850107ada358257f851070a825750107ccd358257105107d782571850107ebf3528cc3307780101330a001801330b20180133082b9519770528cf1333072d1501330ae81c01330b081d013308239519770528b6133307c018017b1760034921680301492180034921700308492178033308d018019517600328ea053307201d0128f8143307d4170133081d28573307c81d0128901f330798170133083c28453307e01d01287e1f33076317013308352833330730170133083328293307fe1601330832281f3307d1160133082d281533079a1601330837280b3307581601330842017b1720027b18280233078f1501330a681d01330b881d0133081f951920022807133307501d012870143307381d012868143307381801288e006457281233077819012882003307701c0128150d9511b87b1764177b17383307ae007b174033074019017b1708491110024911289517387b1718491120019517089511b88278288279207b18287b1920827818827910827a0882777b18187b19107b1a087b1764177b173033076019017b17384711409517303300e000284a26520707649728066487288b9511d0647833071819017b174911080149112049111008491118641728a0049511b87b10407b15387b1630c88909ac895064758277977801330a08e4a909e4980657063d5107108258087b18187b1728330701017b172064179519186468502080003882175207238217087b57087b568210408215388216309511483200330733092819012870ff82170882181033092819012862ff9511d07b10287b15207b1618829a087b1710510a3282971051072c7b1782957b180850208200882f5107496476645882195020840039366457502086006533821508280d648550208800642f647601886701330801db66088219107b98087b95107b978210288215208216189511303200330682150828d99511a87b10507b15487b1640648a828608828b7b171851065a330595b7086468018279c895059588ff9577105208f582a91851091957054582b708887701885810d28707520736975501015705dc005105327b197b1b087b1a10645850208a00d62e5107cd00821a10821b088219281782a9183305330701280d33053307012806330701017b15207b17284911305116011352061252090f33063305330801283b51092e3308e8180195172064a950208c00120b5107383307780401330a901901330bb0190133085695193f28871082b60882b8ac653a330501c85707646950208e001335c865057b153001821730821828821920821a187ba7107ba8087ba9821050821548821640951158320095172064853308646950209000fafd645882153082172828b5330778190128c6fd6457284afd828928828720829c183308a11401330905320c827851080982770828d73132009511e07b10187b15107b16086475827a108277c9a7076496ac972b7b1a825708c8a70764695020980079348217c876067b56103307821018821510821608951120320064577b1864a8646950209a0072fd8218825a1028c59511d87b10207b15187b1610330980006475ae98268256108257ab761064577b1850209e006901821801825708c8670778789566012899008b870b48110c52071595170d9889068699c00078190c33090228528b871052072295170e98890c8699e00078190c97893498993a9599800078190d330903282d95170f9889128699f00078190c97892e98993a9599800078190d97893498993a9599800078190e33090401825610825a84883f86888000c96a0a7878ac9a2d7b19825708c8670795180c5020a00084338217c87606017b5610330782102082151882161095112832006457646864965020a2007efc646982561028c59511e07b10187b15107b16086489828a08828818330b01aaba13520a13520810827a103306330501282b5108173308e8180182101882151082160895112028e2088298827a1082798286088285c9a909ac69317b17827808c8a80764586469646564a65020a600ef32c8560682177b7610330782101882151082160895112032007b17821764a864695020a800e8fb8217827a1028bf9511b87b10407b15387b16306475827795780151084d977901e48908330908e4980657063e5107108258087b18187b1728330701017b1720641795191864685020aa0013fc82175207238217087b57087b56821040821538821630951148320033073309c81901284afb8217088218103309c81901283cfb9511e87b177b18084711100164175020ac007221009511a07b177b180864177b17403307ae007b17489518087b18507b17583307981a017b1710491118024911309518407b182049112802951710649828b09511a07b177b18089517087b17403307ae007b174864187b18507b17583307101a017b1710491118024911309518407b18204911280295171064982873ff8277019511b87b10407b15387b16307b18987904330814330a6e0601532971027f330814951b2e14094b598638d6c56d347b1910491108102733037b143300ffe0f505016472821710d6720798770b821908ca9709bf9206976930989932ca39099894119899108499fe07964c64bfc606976631986630c8a9097c9c7c9901c8a6067c65017c6678b9ff78bcfe78b50178b69588fc95bbfcac20ac5417633697793098993296997b14989911969b64bfb7079777319877309588fec8a7077c7b017c77951c1cc88c0c78cb0178c764970155170a149588ff95191cc889098677307897281c9777019588fec8a7077c79017c77951a1cc88a0a78a90178a701951a1cc88a0a9a8b14330801821733095020b0001382104082153882163095114832009511987b10607b15587b16506473807034649c7b184884080133022b3309000011840704c8b80552070849114828376457510c23330664c4821948c8c905649c017dc98999c085990195cc01c89606ab5cf2280764c4330601c87605644c3309000011018237db82097b1b307b1a2851072e823608ae65288407085207577c3838330201c956077b1c207b1918ad829b005208a9007b1710648728a40082352082362864576468821a4864cb5020b2006e013308015207b700826c186457821828821930821060821558821650951168320c7b16388137307b17107c38387b1808823720823828481330307b13461338017b17406486821a4864cb5020b4001f01520767821738c95705955501019555ff5105c900826920330830821740b49002b6005107eb2842521802129578019877019888017b18102806491110018238207b18408235288138307b1838957601019566ff510628825920821740821838b49002b8005107ec330801016487821060821558821650951168320082164064676458821918821a48821b205020ba008e003308015207d7825a186467821828821930b4a002bc003308015207c18217109a777b17483306ff01821748c867075117ff45825920821740821838b49002be009566015107e52839826a18821740821828821930b4a002c00033080152077eff330882168217107a6730821708786738286aff821610821710d87608285eff821710d876082855ff9511d87b10207b15187b161064a6647c5139000011286485828a2064c764987b1b087b1cb4a002c200821c6458821b08647933070152092051061a828a1864c7646864b9821020821518821610951128320a33070182102082151882161095112832009511a07b177b180864177b17403307ae007b17489518087b18507b17583307b81a017b1710491118024911309518407b18204911280295171064982896fb9511a87b10507b15487b1640647c827a827b106486520a0a84b80151081e0384bb01510b910064c082cb186494c869093308510b46330c3303e0003302f0006467646528149556010195cc01c98505c956086465aacb27aa95557d565906e98466ff00ac360bac260d95560428dd95560228d895560328d3646701aa96317d6b64496476640c590b0c84b7ff00330be0000151089d02ae989502c886077d77330bc0afb78d02520a1128900264496476640c01510a860282c3087b19187b161055192027330251091ac89608646a017da78977c085770195aa01c87202ab8af2ac32aa01285502646a9565078455f8c89606c956068468073309aaa5147dab89bbc085bb0195aa01c8b909ab5af27b1c087b13330c510821846bf8c8b50ac85b0bc8b808017dab89bbc085bb0195aa01c8bc0cab8af2986303140a0101010101010101330bff00ff001408ff00ff00ff00ff007b182814000000000001000100140801000100010001007b1820c89c022830821530c89505821638c96303846c03821b28d2b407984808d2b808c88707821820ca8707987730c87202520c97005103820064593307c000e673077b17389778037b18303304531304ba8485e007c8950564980182808507ff987707980b06828c08d4b707d2a707c8470785cbff98bb0782861098cc06d4cb0bd2ab0b856cff98cc07986606d46c0c828618d2ac0cc8cb0bc8b707856bff98bb07986606d46b0bd2ab0b958820c87b04ab58aa2853ff821918821610821c088213ac325d28090133088467fc00977703c8790397cc03821918821610018237953308857bff98bb07987706d4b707d2a70795ccf8c87808520ce6821a28d2a807988808d2a808c88707821820ca8707987730c87202821c088213ae32b3007cca38c923085a1a010c510a1a491120281a521a02129587019888019877017b172028087b182033080182c7207b173882c52881c7307b1730958601019566ff51061a825920821738821830b49002c4005107ec330701288300825a18821738821810821918b4a002c600647833070152086a8217209a777b17283306ff01821728c867073308ffaa8740825920821738821830b49002c8009566015107e32834ab987bfddb6809db6606520a82fd82c82882c720828c186468821050821548821640951158320c821620821720d876072809821720d876070182105082154882164095115832009511c0647833078905017b17304911382b9517307b174911080149112049111008491118641728fbf78279088277829c18320c9511807b10787b15707b166849115820330a03781a608296204911284911387b17487b18505106f70082972851075b0182957b19088298107b18109578ff9788039888039588017b18955908977803977706c98705956618017b1920829951091b821a508217488218208288f882aa18b4a002cc00520738017b15188267107b17587c69188268f8826778196051082152180112977704821810c8870782785108073308280e8277083308012806330801018269e87b18287b1730511902198268f05219011e978704821810c8870782785108073309280e827808330901280633090101826708977704821a10c87a0a82a782aa087b19387b1840951828b4a002ce008215185207a7008219209599109555c8956638520543ff2860829718510768829510977804c858087b18207b190882969577ff9777049877049577017b17956608018269510917821a508217488268f882aa18b4a002d0005207528257825908951828b49002d200520742955510956610821720ab75ce821908821a829708ac7a0d282d330a829708540726829797a804c88709821a50821748829882990882aa18b4a002d40051070833070128053307018210788215708216689511800032009511c07b10387b15307b162880707b18980b0433080a33096e0601532b7102da0033080a951b261402000000005917b7d17b1210491108102733047b143303ffe0f50533066401970c20821710d67c0c98cc2d821708ca7c05bf500a97a530985532ca45059857119855108455fe07ca6707bf7a0a97aa3198aa30c895057c57017c55c89a0a7ca20178b7ff78b5fe7ca778b201830a9588fc78b795bbfc64c0aca3a7541c635897c730987732967a7b1498aa1196a764bf7c0c97cc3198cc309588fec89c0c7cc7017ccb951c1ec88c0c78c70178cb531a0a2b97aa019588fec8a9097c97017c99951a1ec88a0a78a70178a9281d640c561063b083ca551a0add9588ff95171ec8870786a930787901951a1ec88a0a9a8b0a330801821733095020d800bcf68210388215308216289511403200951178ff7b108000808934849a10520a1484992052094a8210800095118800286af5330a810095197f0164ab987c0484770f88720a867a30957757db2a0778979599ff95baff64c7520ce295b7fe56278000659aab8100641ac87a0a283d330a810095197f0164ab987c0484770f88720a867a30957737db2a0778979599ff95baff64c7520ce295b7fe56278000359aab8100641ac87a0a0133090264873308f517015020da0005f6821080009511880032003309501a0133088000284ff43309501a01330880002843f49511d87b10207b15187b16107b1751098501330a330b0fe4b90b95bbf1958c0784ccf8c98c079584089a93330c7d08017b1c08280995aa01ae9a5b01c8a8057d5557054ebfa7058455075205ebaeba26c8a8058255c8a4068266140c8080808080808080d46505d25c0c520c0995aa10acbae0ae9aa2009aa5c88a0a017dac570c93009555ff95aa01ab53f42807018455ff00821c08c85c0c7cc6330c04aac650330c03aac626330201330c02abc6f60095a001ae90eb00c8080c7dcc3305013306bfafc6c90028ea0095a601ae96d200330201c886067c665125e000426c605225ed0052330c9faf0c6528b80095a601ae96ae00c886067c663302015125f0002b6c665225f4005a330c8faf6c602894009a5a01ac9a27ff287a846ce0003305a000aa5c2a287d956c70ff531c30402873955c1fff33060cac6c0e845cfe003305ee00ab5c5f330cc0afc05995a002ae904fc8080c7dcc581cbf34284c955c0fff561c0241330cc0afc63a95a502ae9530c885057d5c3305bfadc52e95a003ae901fc8080c7dcc5a1cbf25950a01ac9aadfe330a82177b78087b7910282333022810330501280b33050228063305030182177b7a08787210787511330a01017b7a82102082151882161095112832009511907b177b18087b19107b1a1864177b1750330770017b17589517107b17603307ca007b17683307301a017b1720491128024911409518507b18304911380295172064b82816f29511a07b177b180864177b17403307ae007b17489518087b18507b17583307d81a017b1710491118024911309518407b182049112802951710649828d8f19511f07b10087e79989a046487532a71025b96a84703988813968a1027bfa909979a3098aa3296aa7b1498ab1198aa1084aafe0796bb64bfb909979931989930330b6e0601c8ba0a7cac7caa01c8b9097c9b017c99781a05781c04781b07781906330a016489283c330a0533086e06015319642e989a0296aa7b1498ac1196ca64bfa909979931989930c889097c9a017c99781a07781906330a0364c90155190a1495abff951803c8b8088699307889281f97993198993095abfec898087c89017c88951a03c8ba0a78a90178a801951a03c8ba0a9abb0533080133095020de00aaf282100895111032009511b0647833075015017b174911081b64177b1740330770017b174833076805017b1710491118014911309519407b19204911280195171028b2f0009511c87b10307b15287b16205618271d978a02330b000001c8ba0a81aac8ba0a320a33085c3028d201330a5caba80a33085c5c28c501330a0003aca804018499015109fd0088892f1801330a11949a95ac0897cb023309cc1001c89b0b81b68a8b0b8a660bd86b06da6c0a95a604976c02c89c0c81cc8acc0bd8cb0cdac60a95a602976c02c89c0c81cc8acc0bd8cb0cdac60a95a601976c02c89c0c81cc8acc0bd8cb0cdac60a95a601976c02c89c0c81cc8acc0bd8cb0cdac60a97ac02c89c0c81cc8acc0bd8bc06d3cb0b88bb01c86a0ac8ba0a330c21acacf40297ab02c89b0b80b99896153302ef02511a210d80bc0498c215510a0c80bafc84aaffff1f01856bffc82b0b510b30330cbfa80b330aef02e4a6039522ff330a54110101aa639f02c86a097c99be9c0caccb0c956601ab62ee64260184660152060a01551820db008b8914330a780501c8a9097c99868b0178191797892c98993cc8a9097c9c97893098993cc8a9097c9669b9989902781c18781619978b3498bb3cc8ab0b978c3898cc3cc8ac0c84880fc88a0a9598fe4611167cbb7ccc7caa471114781b1a781c1b781a1c46111d7d951a14c88a0b330c5c78bcc8a9094619ff7546097b811914811a187f1b1c28020133085c6e2828849900015109dc0133085c27281a33085c72281433085c74280e97992f5909c30133085c22017a784717084717064717043308000279780a01821030821528821620951138320053187f99018b89105209be00330629016e823303730d01330ac30d01330ce50e01330928330b220128c1008b8914330a780501c8a9097c99868b0178190b97892c98993cc8a9097c9c97893098993cc8a9097c9669b9989902781c0c78160d978b3498bb3cc8ab0b978c3898cc3cc8ac0c84880fc88a0a9598fe46110a7cbb7ccc7caa471108781b0e781c0f781a104611117d951a08c88a0b330c5c78bcc8a9094619ff7546097b811908811a0c7f1b10017a797a7a04797b0878780a33080a78780b282fff8b89115209633306e6016e823303650a01330abd0a01330c8d0b0133092c330bd000017b1664756427648664383300f0009511d07b10287b15207b16187b1c083303821c307b1c987008979901c8980c8476ff007b1a1028ef03646864796457510904fe286f848afeff1f513a1eb802f9fd8489e0ff1f5139e0a602edfd8389c048fd5619f9e1fd83895031fd5619f1d7fd838a1014fd330bf0acabcafd83890008fd56295df6c1fd83890000fd56291dfab6fd8389b0ecfc5619faabfd838900fff1330aaf22f5ac9a9cfd5538f0010e98fd3309800078797a78042850fe3309181c013308ef0264372837ed3309001c0133082264a7282aed00951130ff7b197b1a08532801014f7d7300013302bf330c03ad32197d73ff00330c025a13bf0f7d7cfe0089ccc085cc010195c3fd00c8370c7dcc581cbfcd007b17107b1318330205330c7e09017b1c207b1228ae9817285b7b17107b1818330c017b1c20491128ac984aaca847ae9aa50064177b1780003307ae007b1788009518087b1890007b1798009517107b17a000330770017b17a8009518207b18b0007b17b8003307f81a017b17503307042840d89807db790a7b1a409517407b1780003307ae007b1788009517107b179000330770017b1798009518207b18a0007b17a8003307881b017b1750330703017b1758491170951880007b18607b176895175064b828d0eb3309643a3300f20028f1fe510912ae890fc8970c7dcc89ccc0dbc90a017b1a306489ae8a3a330903e49a029522fd95ac01ae2c0f3309b81b01642764c8287df9c92c09c87a0a3303c00151090e7dac9599ff95aaffad3cf5c8290901c8970a52090bab891364b72845f3ae891c7da25a12bff328167da88487ff005708177a173c3308012876aa89dc648a3300f200286afe7cac0184781f84cc3f5427df00337ca20297c306842c3fd4c30c5327f0002a7ca70397883d98882b97cc0684773fd4c707d4870751370000119f2811978706d4c707280997870cd4c707017a173c3308015327800017987a0b330802510a0c9877108e7895880301c898087b19407b18489517307b1780003307ae007b17880095173c7b1790003307f4007b1798009517407b17a000330700017b17a8009517107b17b000330770017b17b8009518207b18c0007b17c8003307381b017b17503307052893fe9511c87b10307b15287b1620828528828920825a2064763308277b190864977b1ab4a002f600647833070152085d8168951714330901019516145020f8008df97c1714522780002581181882150864578219b49002fa0052072d33082764578219b49002fc0028227c181e7c191f825a18c98909c868088215086457b4a002fe005107d93307010182103082152882162095113832009511e87b10107b15087b1664758277648650200201d8f452071c826828826720828a183308430501330902b4a002040151071333070182101082150882169511183200825708646882101082150882169511182896f47c847c8901958802c89302ab0421ac32840064b4ac2b8a00c8a3030151091a7c359533019599ffab65f52854ac40146423abc8cf280c6423644b821a10abc8c3821c088214c8c404330801017dc995ca0157090d64acbf970759071d2825aa4a4c7cca0195cc0284997f979908d4a909bf970757070e858801ab4cd3280533080184870182102882152082161895113032003309e81b016437642828dbf63309e81b01642764482811ed3307d01b0128b9f082890851096a019511e07b10187b15107b1608330b7b18828833027d080133047d0901330380003306c0280c95bc040164cbae9c0f01c8b80c7dc595bc015905f28450ff00c802057c555115044451150321521502ef00d89c05c8c80ada540a7caa84aac000ab3adb0095bc0228c3d89c0ac8c805daa4057c555120e000326c555220ed004258159fa00028b600d89c0ac8c805daa4057c555120f0001f5220f400386c5a581a8f41289800845ae000512aa00076288c00955a70ff531a302a288100950a1fff33030cae3a4933038000ad6555286d950a0fff561a02676c5a3306c0af6a5e95bc02d89c0ac8c805daa4057c5a84aac000ab3a4995bc03d89c0ac8c805daa4057c5a84aac000aa3a1fff2830840afe00522aee002a330380003306c0af651e95bc02d89c0ac8c805daa4057c5a84aac000ab3a0995bc0328f2fec8c806c9c909821a7ba67ba908c8b809c9bc0c7b787b7b087b79107b7c188210188215108216089511203200490732009511b87b10407b15387b1630827a7b1898ab0433081433096e0601532b71028300330814951b2e14074b598638d6c56d347b1710491108102733007b143303ffe0f5053305640164a4821710d67a0a98aa0b821708ca7a0cbfc406976c3098cc32ca0c0c98c71198cc1084ccfe07ca5707bf7606976631986630c89c0c7cc77ccc01c896067c62017c6678bcff78b7fe78b20178b69588fc95bbfcac43ac541a633697a73098773296777b14987711967b64bfba0a97aa3198aa309588fec89a0a7cab017caa951c1cc88c0c78cb0178ca647a01551a0a149588ff95171cc8870786a9307879281c97aa019588fec8a9097c97017c99951a1cc88a0a78a70178a901951a1cc88a0a9a8b14330801821733095020080106e882104082153882163095114832009511d064783307301c017b17491108014911204911100849111864172824e69511907b10687b15607b1658828a97a902330ba00001c8b9098199828608828810c8b9097b1720330b330c3209330c646b2807646b648c017b18107b16183307013306f73308330964a5330010010a12016478ae6746570807017b1808510853648650201201431151071e017b173308646950201401ad18645a9757023308e80001c887078177c88707330b330c64693207330c821b18283f5218ffcc00140700000000000000808218207b8728a200645a9757023308300101c887078177c88707330b330c3207330c821b182849821b18821c10013305f78217647633080a12015317f7295217ffa500140800000000000000808217207b78646782106882156082165895117028911464652833821b18821c10013305013307013306f7330833090a12015317f71a5217ff8200140800000000000000808217207b7828118217208218087b787b75087b78100182106882156082165895117032003307d01c012803e03307a01d017b172849113001491148491138084911403308b01d01951728289ee464672866df3307a01d017b172849113001491148491138084911403308b01d019517282878e43307a01d017b172849113001491148491138084911403308b01d019517282857e4951178ff7b1080007b15787b1670648664756487502022015801837752070f826b08978720987a20aeab20140700000000000000807b57018210800082157882167095118800320083885108d40064a77b157b1610826a330c3306491120016478017b182833070040e67805c96c077b1b30ae5722c85609ac69cf005709cb007b1a08510c168217207b17587b1c68330701280ac8560728333307017b1760951740330801951a587b19385020240105028217405207aa008217487b1720821738647c821b30821a08017b1738ac678400ac5b64821720c8670764a8645964a67b1c1850202601b515821c18821b30c95b0bc8560a8217107b7a821828c958087b7b08821938649652085aff140700000000000000808215aa7c13ff280b330c330949112001017b5c8217207b57087b59102806ff8215510cf4fe821720502028018e1228e7fe33073309a01c01283fde3309881c016467821838280ee38217488218503309a01c012824de9511c87b10307b15287b1620827a08330901510a1a01827b7cb895a2ff95b5017b757b720851080101330cff00abc85f330c09accaf9007cb8027cb9017cbc037cb604978808d4980897cc10976618d46c0cd4c8087cb9067cbc057cb6077cb508979908d4c909976610975518d46505d45909979920d4980895aaf795bb097b7b7b7a089887208e7928a3006c8a570a0a3306491118284c84a84033060151083984a82033060251083084a81033060351082784a80833060451081e84a80433060551081584a80233060651080c84a801330607520868491118ae6208330901284c7b170895171864587b1a1064697b1250202a013b148217c96707c865058218087b857b87088567079b77ff821818821910e07907976903cf9707c878089887208e7928089887208e7901649782103082152882162095113832003307701c012886e99511c87b10307b15287b162082ab0864866478510b3982a7105107387b177b181882a77b17107b1908649850202c01fd0c51073d6475821810821950202e01ad1382171050203001d810282e51091e28055109197b18186498649550203201cd0c821818645964752817646528133305821908821818280982190882181801885701db55067b86087b89107b8782103082152882162095113832009511b87b10407b15387b16306475827a95a7015107810097a8018fa9da9708330704e47806330824d6860933075209679669243308fd140bfeffffffffffff7fac9b54510a1482570896a8247b17187b182833070228053307017b17206417330802951a185020340100ff82175207198217087b57087b5682104082153882163095114832008217088218103309b81c0128b1db3309b81c0128a9db9511b87b10407b15387b16306475827a95a7015107810097a8018fa9da9708330704e47806330824d6860933075209679669243308f9140bfcffffffffffff7fac9b54510a1482570896a8247b17187b182833070428053307017b17206417330804951a185020360164fe82175207198217087b57087b5682104082153882163095114832008217088218103309b81c012815db3309b81c01280ddb9511b87b10407b15387b16306475827a95a70151077d97a8018fa9da9708988939330752096d330904e498069769073308f1140bf8ffffffffffff7fac9b54510a1482570897aa077b17187b1a2833070828053307017b17206417330808951a1850203801ccfd82175207198217087b57087b5682104082153882163095114832008217088218103309b81c01287dda3309b81c012875da9511b87b10407b15387b16306475827a95a70151077097a8018fa9da970898893b3307520960330904e49806976905570954510a1482570897aa057b17187b1a2833070128053307017b17206417330801951a1850203a0141fd82175207198217087b57087b5682104082153882163095114832008217088218103309b81c0128f2d93309b81c0128ead951092a827b0001330aff0001acba1e7c8cc87b0b78bc827b000195880195bb019599ff7b7b00015209e5330732009511a0fe7b1058017b1550017b17330a6b1501330701330b15330833090a6433050149210801330880150195170833090f50203e01a164177b174001330742017b17480133076805017b1710017b15180149213001951740017b1720017b15280195170895181001502040013401821b0801330701951a08330833090a6400951168ff7b1090007b1588007b1680008285288276828720825a183308b4050133090c7b17187b1a10b4a00244013308015207d8007b168267089578109579147b1750330770017b17587b18603307d6007b17687b19707b17783307e019017b1720491128034911409518507b18304911380395192082161864677b150864585020460193e5510708330801287d3308c005013309026467821a10b4a00248013308015207668217827a82a77b175082a9087b195882a8107b186082ac187b1c6882ab207b1b7082aa28330b017b1a78aab913821808520913520c103309330801281b821808510c10951950646750204a0125e528128278827908016467821a10b4a0024c0164780164878210900082158800821680009511980032006489828a08828818330b01aaba0b520a0b520808283e51080b3308401c0128d8e4829982980851082c829b827a00013309ff0001aca91e7cbcc87a0a78ac827a000195bb0195aa019588ff7b7a00015208e532009511f8481104552880000d951905781804287f8b890b52091c951906988a0686aac000781a0484883f8688800078180528608b8910520929951907988a0c86aae000781a04978a3498aa3a95aa8000781a0584883f868880007818062834951908988a1286aaf000781a04978a2e98aa3a95aa8000781a05978a3498aa3a95aa8000781a0684883f8688800078180701827a0001951b043308ff0001aca81d7cbcc87a0a78ac827a000195bc0195aa017b7a000164cbab9ce6330795110832006489828a08828818330b01aaba0b520a0b520808283e51080b3308401c0128c4e3829982980851082c829b827a00013309ff0001aca91e7cbcc87a0a78ac827a000195bb0195aa019588ff7b7a00015208e5330732009511907b10687b15607b1658828a2882797b19408276088285207b1a2082a92033082264577b1910b4900254013308015207670251061e027b153302330a3303a149110801000133002233045c82154064687b1618280fc95808c89a0a8216185108c0013307c885097b193801c875097c9b95bc81ac3c12aa0b0faa4b0c957701ab78ec2898017d988486ff007b123057080b9599017b1928285d7c9b0184681f84bb3f330cdf00ae6c337c9c0297bb0684cc3fd4cb0b330cf000acc62e959c047b1c287c990397883d98882b97bb0684993fd4b909d48906281d9599027b1928978806d4b806280f9599037b192897880cd48b0601c8a70595174c646882190850205601cbe97c174c522780001664593303a182123033002233045c28d9007c17577c1856bf87078477ff0082123033045c5217010f64593303a133002228b700ac257d01821840c82808510216821718ae720c7d875a17bf0b286401ab72610151051c821718ae7512821740c857077d775a17bf0b284801ab754501821720827a18c9250982177b1a30b4a0025801520716017c174c52278000188118508217821910b490025a0151072328fa007c18567c1957c9890995174cc878088217821a30b4a0025c015207de00330701532680001798680b33070251080c9867108e77957703016459c857023303a133002233045c01821838821528330a0133078000ac7654fe98670b330a0251074afe9866108e6a95aa03283efec88a0a01ac2a8c008215510230ae6236821740c827077d7764285817bf76510a21ae6a29821740c8a7077d7764825817bf6264a6281b330828173308520ae53306280e6468aa62d928486482ab6a44821720827a18c98609821740c878086457b4a0025e013308015207133308226457821910b4900260016478016487821068821560821658951170320033080128ed330b801a01821740646864293300f20028eaeb330b681a018217408218186429645a3300f20028d4eb951130ff7b10c8007b15c0007b16b800827508827955250304a0005105da018287207b17308287287b172833076c017b1720140720000000080000007b171833076805017b1710017b19389517387b17708217207b177849218000024921900049219800024921a0008217187b17a8004621b000038217107b174049114801951880007b1860491168019518707b18504911580164969519408217308218285020640118e0520754019555ff9569015205992843017b15088287207b17308287287b17283306000233076c017b1720140720000000080000007b171033076805017b17187b196495017b15389517387b17708217207b177849218000024921900049219800024921a0008217107b17a8004621b000038217187b174049114801951880007b1860491168019518707b1850491158019519408217308218285020660179df5207b5009566ff95550152069b821728827a183308f11701330903821730b4a00268016478330701520891008217088218c88707957500fe33060002140720000000080000007b1710017b15389517387b17708217207b177849218000024921900049219800024921a0008217107b17a8004621b000038217187b174049114801951880007b1860491168019518707b18504911580195194082173082182850206a01d4de5207109566ff95550152069c33072806330701018210c8008215c0008216b8009511d0003200951178ff7b10800082777c77330a810095197f0164ab847a0f98770488a20a86ac3095aa57db2c0a789a9599ff95baff5207e495b7fe56278000289aab8100641ac87a0a33090264873308f5170150206e01fad7821080009511880032003309501a01330880002844d6827a827908648764a82890da951168ff7b1090007b1588007b168000827b10827608510b1f33079abcc86b099599ff017c9a520a16957701c87c0a9599ff520af233057b1b10280f7b1710c97b05ac7b5601647b0133071f645c64697b1b0801510c467d9a95990195ccffada7f5541520547b1618491120089517187b1740330762017b17483307301e017b1750491158024911709517408285208288287b1760330601284f7b18951718646864595020740152e1821718510792008218561520b47b16187b15209517187b1740330762017b17483307501e017b17503306019517407b17608285208288287b1658491170014911680195195064577b185020760140dd52078c0082170851073b9517107b17183307ae007b17203307601e017b1750491158024911709517187b17603306017b1668951950645782185020780101dd52074d330628488217208218287b17307b18389517307b1740330770017b17483307101e017b17504911580249117095194082188287208288287b19604911680195195050207a01b7dc64760164678210900082158800821680009511980032003309f81d01645764b828aed83307011409e0ffffff1f000000ae8907330732009511d87b10207b15187b161095861f3305080003955948278499fc013e04000003977420994420819ad44a047a943a0400000384aaff00520ae59557002082770006986605510734011407ffffffff00000000aa760d015648e0ffff3f090183620164675328c10f2a69278d7918c6920a84aa7f9099ffe092098e99977707c97a0ac8a9099597ff079577800401330b0020c8b509829a38078b7906d09a0c84cc01510c1d979c03955b0020c8cb0b82bb0806330cffcf7c07d2b7075207219599019b97ffd2a7075107d4006a7997970395580020c8870782770806016a748a9706d44707977702c8750b80bc80ca04836798aa01aea76b7b12089557002082730006bf3c008b0805c868028568ffbea808817744077b188e88c82808838a01aea72433076577c9370797a825988220ae270bc97207657751072b955700207a7a44070180ca0881cc8407e0c837077aba510aa300481a0c821408821028d000330701955800204628480782102082151882161095112832007b180850207c018b011408ffffffff0000000064793307aa86d38218085209c1fe28c964675428c00f1f69278d7818c6820884887f977707c978089587ff0795778004018b7906d09a0884880151081e979803955b0020c8b80882880806330bffcf7b07d287075207fdfe9599019b97ffd2a7075207dffe2872ff979a0333020020c8250bc8ab0b82b808063303fedc430ad2a8087bb8080682140882105208169552002082283807dc9309d298087b283807019748019588019779209899207a9c7a9804957320976a05c8a30951008b00640754107f2a330700000002e67007697a8dab18c6b70784777f97aa07c9a7079577ff079577800495770101837bff8bb70684b23f97bb02c8b50c80cb7ac9970c01979a2098aa207aa47aac047aab08481a0c510b077ab90c01977a03955c0020c8ca0a82ac08069b2801d4c8087ba80806955b002082b838079b7701d487077bb738076404019557002081784007817a0006970705be9707978805bea808ae87149777209877207a749737209877202868fe973720987720285ffe33086927033307657795881f8488e0ae8714c9780765793307510962fe520809285bfe51084333076577c9870756171f0d9a79206597510745fe3303080003953b002081ba4007482b4407013307000000027bb80006aca71c510a12552a80001383a4ff282833072813fe3304281f64a70169798d9a18c6a70784777f979907c997079577ff0795748004018b4b0697b70395390020c87909829c0806330701cf470ad4ca0a7b9a08069532002082293807812a4007cfb70bd4b9097b29380797aa0148087a8a0448180848180c974402c843037a3828a5fd3303080003953948278499fc330a01013e0400000397a420994420819bd44b047a943a0400000384bbff00520be5510794029511d87b10207b15187b16109570e09709209537002082750006989920809a04819bbf500c98a901541c1fd60097ba05bfa00c97ca2098aa2080a4048446015206bf009848013306000000027b185644000000041651042a6486552400010a958bff282069688d8518c6560684667f978807c986069568ff07958b80042805330b0181a87b180897b52098551ec83505815280a608abc20d7a5651061648160c284780aa0c7aa60851063f7a6a0c28398bb606976a0395380020c8a808828a080684bc3f3305fedcc50cd2ca0a7b8a0806520a169532002082283807dc650ad2a8087b2838070195380020828500068218be8909974404bf4000821b0801817a400797aa05be5a02979c05be0c0cae2cd10097ca2098aa2080a6048467015207c1007b1b08986b013305000000025646000000041651062a64b5552600010a95b4ff282069578d7818c6850884887f977707c978089587ff0795748004280533040197472098771ec87305815780a608abc7137a5651062248160cbeb909821b08285e80aa0c7aa60851064f7a6a0cbeb909821b08284a8b4a0697a703330c0020c8c308c887078278080684453f3306fedc5605d258087b78080652081d953c002082c73807dca608d287077bc73807beb909821b082809beb909821b080151098900649a54197f2a330700000002e6790769788d8a18c6a70784777f978807c987079577ff07957a800495aa010183a8ff8b8a06848c3f978802c8380880867a809798019707209877207a7b7a78047a760848170c5106077a600c0197a70395380020c88707827808069bcc01d4c8087b78080695360020826738079ba801d487077b67380701979705be0707ae270c9777209877207a790182102082151882161095112895370020462748073200953700204627480732009511e87b10107b15087b16531910698d7a84a207c82706ae671a642c647a648b017cb578a595aa0195ccff95bb01520cf3c82804c929028423f8844807c8360a51083eaea64e9748038480388445f8825b8d88848c38955808018285d00b0bcfc509d4b9097b69956608958808645baca6eb281f647ac8970bacb7242831aea61364480182897b69956608958808aca6f6c83408842907c89a0baeba137c8b78ab95aa019599ff9588015209f382101082150882169511183200531910558d7a84a207c8270baeb713642c647a0178a895ccff95aa01520cf8c92909849af8c8ba0aaeab22978238140c0001010101000000d6c202972c20d42c0c017bbc95bb08acabfb849907c89a0bacba0d2815647ac8970baeb70e78a89599ff95aa015209f832005109167c7a7c8babba139599ff9588019577015209f033073200c9ba073200259588888888081140129144228924922491441249249144128924914824922449924422914822914824128944444444449244141121228a982489249148128944224992482412499248240a49929248241249424248241249224912224412210920522289a42449922449922449294549922449922449922492282491249344880811401211892422224400494444a41011112122440049442222422411894422899224491291244992244992244924499224499224491249922449922491244922499224499224499288081140241191888888442289a424499224499224892449922449922449224992244992244992489224499224494492241149922449922449922492244992244992248924499224499224492225499224499224492292244992244992244924499224499224491249922449922449924492244992244992241149922449922449922492244992244992248924499224499224492249922449922449924424914824121111a1a44844944484221191442491a424492411911a910891444444c4144989482444222249249248449248922449922449924824128944229148241289449148922489244992242589442291241289449224229148248944229144928888109124918888002289441240c424154a2252441491081140249188002289888888888888888888888888888888888888888888888888888888888888888850124a224244001140041001c488882412495292481289488d48844822a25446842812894412918824222549922449922449244992244992244912499224499224499244922449922449922411112222222211114522914824128944229148241289442291444428421121a224a44424119144249122112511911a9108111111c5144949924849244422228924112192884492889424499224922449124992244992244992449224499224499224892449922449922491242222424424224544448c08258a4444494a44121149249122115112911a91081111514c49144924128944221289242249492449224224118944229144a42449922449922489244992244992244922499224499224912449244992244992248924111151a1222212894a11119188221189888888444424220a458c44229124128944924824124992482412492291482449221249a248241289248988489224892422114522894488448844884444a294449108209252119324499224499288242222222222222222222222222222222222222222224984189148442412111111111189888888888888888888888888888888888888888818111111111111111122444488081191aa294444448522222244444444142222221a910a91448809211189882221268424468448444412914222524485882285884444445446041023122142442422424444440ad1a210012422112211222211222291124912924292502442442442244245441421228988880a45442221221121858444841412121111891049128910498849082124124248a4902242420a2922242452229284a4902414424824849048881021422424a4909042420a29a490420a19114248242424a44242528984449248922449492a214924552914899424492a4992249952284949522149489224492a41aaa0201534499292a452929492d2922449922423250591544aa54c1249a5841424849024a5294826495292942a2829a42409a924254929a5a4a0a424a92a28254994940a9a94244922294922492425912492443225524a12942949aa8252929424494a094992524a4aaa829492a4aaa094249524499224530a4a5292544812921205534a2449482489544a122909892491d2242909210920218225499224892449494a529248224992a4243592a492a4244d920a49529294928424a954d29224d51496244992248948492a05899424494992248a9482249948125224499924c92449489624a59204494a429292492409494912129524499224495241a9142a2549952435494a2991242191245292a4521289524a115526292991a4949aaa448c4889a41a49a2a425492225494a49925492644992041004900002484a9224499224a2489222d29224499224499224499224495252244996244992244992a448222992a4344992499290244942494a2649929014492429494a5232494a2191229192529244922489444992a424c99424498824494924299594492291a454522649491212491225499292a42c49125212922429a9a4242909494d122549252148024888209324499224922429494a2925924892242949912425a9a24892264905495244922422912549525212a214599224252521ca14828842244492a42849921452222549520a204912659124919424492452124924854292444a2824691491228a24224992a28824494992225149955296244b92529212492221912452524a244948248994a41422912492489290929424a98448244992923492a492242569921424a5509448482489649244424a912412910829092949929424494992942449499292244924252249c22425923052923411125292a4249524499224a9922492944892441151142562492293a4908842088924a42449492a49922449522549242991244b922489420889554592a49424922a0a811008218490084108420821025112a2849451422489442423852421494992248a8844242212119292242212118908c92492948a242999922409952453924c5252542429459284240949922428299984242993242212118988444422429224495252a1a4448284a44252a124490ac92429495590242121494a29292949a2c894a4942a49c99292922449929a2429544225249124550891b24849a48890a48894a450084952084522242291448a2849922249524411224992224952494a9224559254128224808408b22449922491242949494a124924499294a44692549294a44952419294429148499212529254554d922a5a2229882a2821a5aa1402482921a52a2553354200a924492593542304904a4a264921219148484a4824129290482424114915942409a0914445a58a269224114992949a240912494a2692540992242549114051c592a4489042424912922449545222924892244992244992242929a594244992244992244952522a284949922449a525492149924a524a0a520a1294920a525549c92429494a924aa424494902489224352541494a9292909024a9444a92942480244952531294a42429090949924a5252922480244952531294a42429090949924a5252922449929a92a024254949484822a6144922151185a44a84241411212222221144528d8848494812122549249124249244520a92424a48aa244992244992529282ca125a4444254952424a8a985224899484a42489245292449248222591249244121989294512a554922425a4a4882945924825494a92a48444540992949244c99424498a24294924492249922429494a924909124a922489244a1249922291242912494a8884949024494a888424294b92492292484946495291241555a54a2549524852424b925248454252918888144492440248c824498810899044249228491049922489480009a94992102112218948249124882449484289948800324912224422241189249204495219115144459624494912a2a410441422a5121191a49225494a493495294924924848244952a920512449242449928c54104912098924492a482549120989a42491a04544142a092095240991212985484422800848a124494922462249128924491225124b92529288244954924a929488292992d2882429410095448592241193249148124922892492444a441293a4248922484992884c925492922413892422926844244992922409952415492a912244842092904ab52449c424124922229248499212221992528844922411494948922412144821952489a825494949495212892489442431a224194912491249104821952489a8494a4a929224298924922412914452328a20254922324952494a924c249288486292a449125144498a92542b49922449924a96a4a454525a4992a492242945496a49922401245992a49292944a925402", "08000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "05000000000000000000000000000000000000000000000000000000000000": "00000000", "04000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af39b7de5fcfb9fb8a46b1645310529ce7d08af7301d9758249da4724ec698eb127f489b58e49ae9ab85027509116962a135fc4d97b66fbbed1d3df88cd7bf5cc6e5d7391d261a4b552246648defcb64ad440d61d69ec61b5473506a48d58e1992e630ae2b14e758ab0960e372172203f4c9a41777dadd529971d7ab9d23ab29fe0e9c85ec450505dde7f5ac038274cf012105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b30746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981cc0746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981cc2105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392e9326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b662105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392e151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b0746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981cc151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40bff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b30746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981cc00", - "00fe00ff00ff00ff952fd5905548e08a9a7d8fca521139f7e2573a2207d6a6": "5000156a616d2d626f6f7473747261702d7365727669636506302e312e32320a4170616368652d322e30012550617269747920546563686e6f6c6f67696573203c61646d696e407061726974792e696f3e98710044000000000020002f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a777269746520726571756972657320746861742074686520706f696e74657220617267756d656e7420697320616c69676e656420616e64206e6f6e2d6e756c6c756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d415860436f6d7061637420656e636f646573206c656e6774684572726f72617373657274696f6e206661696c65643a206e65775f6c656e203c3d2073656c662e636170616369747928292f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7665632f6d6f642e727300aafefeffdefefeffdefefeffdefefeffdefefeffb6fefeffdefefeffdefefeffdefefeffaefefeffc4fefeffcafefeffb2fefeffbcfefeffdefefeffdefefeffdefefeffdefefeffc0fefeffd2fefeffdefefeffdefefeffcefefeffa2fefeffbcfefeffb2fefeffb8fefeffacfefeffc4fefeffcafefeffbafefeffd0fefeffb0fefeffcefefeffaafefeffaefefeffc8fefeffa8fefeffb6fefeffa6fefeffc0fefeffccfefeffd8fefeffc2fefeffc6fefeffd4fefeffdcfefeff80fefeff8afefeff84fefeff86fefeff82fefeffa4fefeffdefefeff04fffefffefefeffd2fefeffe6fefeff0cfffeff14fffeff1cfffeff1cfffeff1cfffeff3efffeff38fffeffc2fefefff6fefeffd8fefeff2000ffffcafefeffbcfefeffb6fefeffb0fefeff1cfffeff34fffeff24fffeff2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f697465722f61646170746572732f656e756d65726174652e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a636f70795f6e6f6e6f7665726c617070696e67207265717569726573207468617420626f746820706f696e74657220617267756d656e74732061726520616c69676e656420616e64206e6f6e2d6e756c6c20616e642074686520737065636966696564206d656d6f72792072616e67657320646f206e6f74206f7665726c6170756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a7265616420726571756972657320746861742074686520706f696e74657220617267756d656e7420697320616c69676e656420616e64206e6f6e2d6e756c6c756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a777269746520726571756972657320746861742074686520706f696e74657220617267756d656e7420697320616c69676e656420616e64206e6f6e2d6e756c6c756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d4158602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f736c6963652e72734572726f726d656d6f68617368636f72656b657973626f6f74536f6d65206f6620202d3e204e6f6e65476f74206461746144726f704f75744f66426f756e6473496e646578556e6b6e6f776e53746f7261676546756c6c426164436f72654e6f436173684761734c696d6974546f6f4c6f77416374696f6e496e76616c69644f6b457272636f64655f68617368636f64655f6c656e496d706f727465646175746f5f6163636578706f727465644578706f727465645472616e7366657262616c616e63653f696d706f72746564526573756c743a207472616e736665724c6f6f6b65645570707265696d61676550726f76696465646d696e5f6974656d5f6761736d696e5f6d656d6f5f676173656e646f776d656e74726567697374726174696f6e437265617465536572766963655570677261646564657374696e6174696f6e616d6f756e746761735f6c696d69745a6f6d62696679656a6563746f72456a65637474617267657444656c6574654974656d7373746f726167655f6974656d73536f6c696369746c656e466f726765744c6f6f6b757073657276696365496d706f72746974656d734578706f7274426c6573736d616e6167657261737369676e64657369676e61746541737369676e717565756544657369676e6174655969656c64436865636b706f696e7450616e696350726f76696465736572766963655f6964636f756e7452616e646f6d53746f72616765526566696e6552616e646f6d53746f72616765416363756d756c61746542656e63686d61726b556e72657175657374457870756e67654e6f74596574556e72657175657374737563636573735f61667465724e6f74596574457870756e6765556e70726f766964656473696e6365556e72657175657374656470726f76696465645f73696e6365756e7265717565737465645f73696e63655265726571756573746564756e7265717565737465645f617472657265717565737465645f73696e6365426f6f747374726170205365727669636520526566696e652c20684c6f6f6b696e672075702e2e2e000000000000207769746820636f64655f68617368204372656174656420736572766963652020696e746f20616363756d756c6174654578706f7274656420736c6963653a2001000000000000000000000000000000736572766963655f726567697374727944657369676e61746564206b6579732052657475726e696e672073657276696365732f626f6f7473747261702d736572766963652f7372632f6c69622e727363616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c7565426f6f747374726170205365727669636520416363756d756c6174652c2068204020244465636f64656420696e737472756374696f6e3a2050616e696320696e737472756374696f6e20657865637574656421436f64652070726f766973696f6e20726573756c74656420696e20417474656d74696e67207472616e736665722c206761733d5472616e73666572206f66202077697468202067617320726573756c74656420696e204661696c656420746f20637265617465215570677261646564214761732072656d61696e696e673a20417474656d7074696e67207472616e736665723a20205a6f6d62696679696e6720736572766963652e20456a6563746f723a20235a6f6d6269666965644661696c656420746f207a6f6d62696679202d20696e76616c696420636f64655f686173683f4661696c656420746f207a6f6d62696679202d206c6167676172647320696e2073746f726167652f6c6f6f6b75703f456a656374696e672073657276696365202344656c6574696e67206974656d3a204661696c656420746f2072656d6f7665206974656d3a20206974656d732064656c65746564207375636365737366756c6c792c20206b657973206e6f7420666f756e64496d706f72746564206461746120696d706f72742d4578706f7274656420206974656d73536f6c69636974656420206f66206c656e67746820517565727920726573756c743a20202866693a202941737369676e696e6720636f72652020746f207175657565204661696c656420746f2061737369676e2141737369676e656421426c6573736564207365727669636573206d3a20232c20613a20232e20763a20232e2061613a205969656c64656420686173682050726f766964656420707265696d61676520746f203a20436865636b706f696e746564216974656d2d2d62616c616e6365206c6f77496e737472756374696f6e206e6f742068616e646c65643a2063726561746564726571756573746564756e7265717565737465646c6f6f6b65645f7570636f756e745f72616e646f6d5f73746f726167653f3f3f5265636569766564207472616e736665722066726f6d202077697468206d656d6f207472616e736665722d636f756e740b000000000000000c000000000000000b00000000000000070000000000000006000000000000000e000000000000000d00000000000000f8f8ffff03f9ffff0ff9ffff1af9ffff21f9ffff27f9ffff35f9ffff0ef6feff0cf6feff0cf6feff0cf6feff0cf6feff1af6feff0cf6feff0cf6feff0cf6feff12f6feff28f6feff2ef6feff16f6feff20f6feff0cf6feff0cf6feff0cf6feff0cf6feff24f6feff36f6feff0cf6feff0cf6feff32f6feff2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7665632f737065635f66726f6d5f697465725f6e65737465642e7273617373657274696f6e206661696c65643a206e65775f6c656e203c3d2073656c662e636170616369747928292f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7665632f6d6f642e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a7375625f707472207265717569726573206073656c66203e3d206f726967696e60756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a2068696e743a3a6173736572745f756e636865636b6564206d757374206e657665722062652063616c6c6564207768656e2074686520636f6e646974696f6e2069732066616c7365756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d415860756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274735f6d75742072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d415860436f6d7061637420656e636f646573206c656e6774682f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e72734572726f720000004af3feff48f3feff48f3feff48f3feff48f3feff56f3feff48f3feff48f3feff48f3feff4ef3feff64f3feff6af3feff52f3feff5cf3feff48f3feff48f3feff48f3feff48f3feff60f3feff72f3feff48f3feff48f3feff6ef3feff2f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f686f73745f63616c6c732e72734661696c757265206f6e6c7920696e2063617365206f6620626164206d656d6f7279206f722062616420736572766963652049443b20626f74682061726520676f6f643b207165642f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f697465722f7472616974732f6974657261746f722e727363616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c75652f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f6d656d2e72732829756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a7265616420726571756972657320746861742074686520706f696e74657220617267756d656e7420697320616c69676e656420616e64206e6f6e2d6e756c6c756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a777269746520726571756972657320746861742074686520706f696e74657220617267756d656e7420697320616c69676e656420616e64206e6f6e2d6e756c6c756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a7375625f707472207265717569726573206073656c66203e3d206f726967696e60756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d415860436f6d7061637420656e636f646573206c656e6774682f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e72734572726f724974657269746572536f6d65736565644e6f6e654974656d4f75744f66426f756e6473496e646578556e6b6e6f776e53746f7261676546756c6c426164436f72654e6f436173684761734c696d6974546f6f4c6f77416374696f6e496e76616c69644f6b4572724d6170436c6f6e656469746b6579496e7075746e625f6974656d734f75747075746974656d7373657276696365732f626f6f7473747261702d736572766963652f7372632f6c69622e7273000000000000000b000000000000000c000000000000000b00000000000000070000000000000006000000000000000e000000000000000d00000000000000756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d4158604f75744f66426f756e6473496e646578556e6b6e6f776e53746f7261676546756c6c426164436f72654e6f436173684761734c696d6974546f6f4c6f77416374696f6e496e76616c69642f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e727362616c616e63653f7472616e7366657272656473657276696365732f626f6f7473747261702d736572766963652f7372632f6c69622e727300000000000b000000000000000c000000000000000b00000000000000070000000000000006000000000000000e000000000000000d00000000000000e3feffffeefefffffafeffff05ffffff0cffffff12ffffff20ffffff30ecfeff60ecfeff52ecfeff58ecfeff46ecfeff6cecfeff78ecfeff5cecfeff86ecfeff4eecfeff80ecfeff3cecfeff4aecfeff74ecfeff36ecfeff56ecfeff34ecfeff64ecfeff7cecfeff92ecfeff68ecfeff70ecfeff8eecfeff96ecfeff7eecfeffc2ecfeffb0ecfeffbcecfeffa2ecfeffd4ecfeffe2ecfeffc0ecfefff2ecfeffacecfeffeeecfeff96ecfeffa4ecfeffdeecfeff92ecfeffbaecfeff90ecfeffcaecfeffe8ecfefffcecfeffd0ecfeffd8ecfefff8ecfeff04edfeffacecfeffc6ecfeffbcecfeffc2ecfeffb6ecfeffceecfeffd4ecfeffc4ecfeffdaecfeffbaecfeffd8ecfeffb4ecfeffb8ecfeffd2ecfeffb2ecfeffc0ecfeffb0ecfeffcaecfeffd6ecfeffe2ecfeffccecfeffd0ecfeffdeecfeffe6ecfeff756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d415860436f6d7061637420656e636f646573206c656e6774682f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e72734572726f72636f64655f68617368636f64655f6c656e496d706f727465646175746f5f6163634578706f727465645472616e736665724c6f6f6b65645570707265696d6167656d696e5f6974656d5f6761736d696e5f6d656d6f5f676173656e646f776d656e746d656d6f68617368636f72656b65797364617461726567697374726174696f6e437265617465536572766963655570677261646564657374696e6174696f6e616d6f756e746761735f6c696d69745a6f6d62696679656a6563746f72456a65637474617267657444656c6574654974656d7373746f726167655f6974656d73536f6c696369746c656e466f726765744c6f6f6b757073657276696365496d706f72746974656d734578706f7274426c6573736d616e6167657261737369676e64657369676e61746541737369676e717565756544657369676e6174655969656c64436865636b706f696e7450616e696350726f76696465736572766963655f6964636f756e7452616e646f6d53746f72616765526566696e6552616e646f6d53746f72616765416363756d756c61746542656e63686d61726b756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207573697a653a3a756e636865636b65645f6d756c2063616e6e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a204c61796f75743a3a66726f6d5f73697a655f616c69676e5f756e636865636b6564207265717569726573207468617420616c69676e206973206120706f776572206f66203220616e642074686520726f756e6465642d757020616c6c6f636174696f6e2073697a6520646f6573206e6f7420657863656564206973697a653a3a4d4158606e65775f6c61796f75742e73697a65282960206d7573742062652067726561746572207468616e206f7220657175616c20746f20606f6c645f6c61796f75742e73697a652829602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e7273617373657274696f6e206661696c65643a206164646974696f6e616c203e20302f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f6e756d2f6d6f642e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d4158604f706171756556616c4b65797365746564323535313962616e646572736e61746368626c736d657461646174615b5d5b5d2c202f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d74797065732f7372632f66697865645f7665632e72732f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6d706163742e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a636f70795f6e6f6e6f7665726c617070696e67207265717569726573207468617420626f746820706f696e74657220617267756d656e74732061726520616c69676e656420616e64206e6f6e2d6e756c6c20616e642074686520737065636966696564206d656d6f72792072616e67657320646f206e6f74206f7665726c6170756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a2068696e743a3a6173736572745f756e636865636b6564206d757374206e657665722062652063616c6c6564207768656e2074686520636f6e646974696f6e2069732066616c7365756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a204c61796f75743a3a66726f6d5f73697a655f616c69676e5f756e636865636b6564207265717569726573207468617420616c69676e206973206120706f776572206f66203220616e642074686520726f756e6465642d757020616c6c6f636174696f6e2073697a6520646f6573206e6f7420657863656564206973697a653a3a4d4158756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d4158602f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e72732f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f706f6c6b61766d2d6465726976652d302e32342e302f7372632f6c69622e72732f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6d706163742e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f697465722f7472616974732f6974657261746f722e7273756e6b6e6f776e206572726f7220636f64653a206372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f726573756c742e72734f7572206f776e2041504920696d706c2068617320726573756c74656420696e20737563636573732076616c7565207768696368206973206f7574206f662072616e67652e756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d415860756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274735f6d75742072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d4158606372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f686f73745f63616c6c732e727343616e6e6f74206661696c2065786365707420666f72206d656d6f7279206163636573733b2077652070726f76696465206120676f6f6420616464726573733b2071656443757272656e742073657276696365206d7573742065786973743b207165644661696c757265206f6e6c7920696e2063617365206f6620626164206d656d6f72793b20697420697320676f6f643b207165644f75744f66426f756e6473496e646578556e6b6e6f776e53746f7261676546756c6c426164436f72654e6f436173684761734c696d6974546f6f4c6f77416374696f6e496e76616c6964000000000b000000000000000c000000000000000b00000000000000070000000000000006000000000000000e000000000000000d000000000000007affffff85ffffff91ffffff9cffffffa3ffffffa9ffffffb7ffffff2f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a2068696e743a3a6173736572745f756e636865636b6564206d757374206e657665722062652063616c6c6564207768656e2074686520636f6e646974696f6e2069732066616c7365756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a204c61796f75743a3a66726f6d5f73697a655f616c69676e5f756e636865636b6564207265717569726573207468617420616c69676e206973206120706f776572206f66203220616e642074686520726f756e6465642d757020616c6c6f636174696f6e2073697a6520646f6573206e6f7420657863656564206973697a653a3a4d4158756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d4158604c61796f75744572726f722f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f706f6c6b61766d2d6465726976652d302e32342e302f7372632f6c69622e72734661696c757265206f6e6c7920696e2063617365206f6620626164206d656d6f72793b20697420697320676f6f643b207165646372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f686f73745f63616c6c732e727363616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c75656372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f6d656d2e72734f75744f66426f756e6473496e646578556e6b6e6f776e53746f7261676546756c6c426164436f72654e6f436173684761734c696d6974546f6f4c6f77416374696f6e496e76616c69640000000b000000000000000c000000000000000b00000000000000070000000000000006000000000000000e000000000000000d000000000000007bffffff86ffffff92ffffff9dffffffa4ffffffaaffffffb8ffffff756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a204c61796f75743a3a66726f6d5f73697a655f616c69676e5f756e636865636b6564207265717569726573207468617420616c69676e206973206120706f776572206f66203220616e642074686520726f756e6465642d757020616c6c6f636174696f6e2073697a6520646f6573206e6f7420657863656564206973697a653a3a4d4158756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207573697a653a3a756e636865636b65645f6d756c2063616e6e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a777269746520726571756972657320746861742074686520706f696e74657220617267756d656e7420697320616c69676e656420616e64206e6f6e2d6e756c6c756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a2068696e743a3a6173736572745f756e636865636b6564206d757374206e657665722062652063616c6c6564207768656e2074686520636f6e646974696f6e2069732066616c7365756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a204c61796f75743a3a66726f6d5f73697a655f616c69676e5f756e636865636b6564207265717569726573207468617420616c69676e206973206120706f776572206f66203220616e642074686520726f756e6465642d757020616c6c6f636174696f6e2073697a6520646f6573206e6f7420657863656564206973697a653a3a4d4158756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d415860606e65775f6c61796f75742e73697a65282960206d7573742062652067726561746572207468616e206f7220657175616c20746f20606f6c645f6c61796f75742e73697a652829602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e727373657276696365732f626f6f7473747261702d736572766963652f636f6d6d6f6e2f7372632f6c69622e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a7265616420726571756972657320746861742074686520706f696e74657220617267756d656e7420697320616c69676e656420616e64206e6f6e2d6e756c6c756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77617373657274696f6e206661696c65643a206e65775f6c656e203c3d2073656c662e636170616369747928292f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7665632f6d6f642e727300000000000000000000000000000000000000000000000000000000000000000000000000002f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f72616e642d302e392e312f7372632f726e67732f786f736869726f323536706c7573706c75732e727373657276696365732f626f6f7473747261702d736572766963652f636f6d6d6f6e2f7372632f6c69622e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a2068696e743a3a6173736572745f756e636865636b6564206d757374206e657665722062652063616c6c6564207768656e2074686520636f6e646974696f6e2069732066616c73652230782e2e2e756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f772e2e2e00000000000001000000000000000000000000000000756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207573697a653a3a756e636865636b65645f6d756c2063616e6e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a204c61796f75743a3a66726f6d5f73697a655f616c69676e5f756e636865636b6564207265717569726573207468617420616c69676e206973206120706f776572206f66203220616e642074686520726f756e6465642d757020616c6c6f636174696f6e2073697a6520646f6573206e6f7420657863656564206973697a653a3a4d4158606e65775f6c61796f75742e73697a65282960206d7573742062652067726561746572207468616e206f7220657175616c20746f20606f6c645f6c61796f75742e73697a652829602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e7273617373657274696f6e206661696c65643a206164646974696f6e616c203e2030756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a636f70795f6e6f6e6f7665726c617070696e67207265717569726573207468617420626f746820706f696e74657220617267756d656e74732061726520616c69676e656420616e64206e6f6e2d6e756c6c20616e642074686520737065636966696564206d656d6f72792072616e67657320646f206e6f74206f7665726c6170756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f774572726f722f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f697465722f7472616974732f616363756d2e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72736d656d6f727920616c6c6f636174696f6e206f6620206279746573206661696c65642f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f737472696e672e72736120666f726d617474696e6720747261697420696d706c656d656e746174696f6e2072657475726e656420616e206572726f72207768656e2074686520756e6465726c79696e672073747265616d20646964206e6f742f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f666d742e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f737472696e672e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207573697a653a3a756e636865636b65645f6d756c2063616e6e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a204c61796f75743a3a66726f6d5f73697a655f616c69676e5f756e636865636b6564207265717569726573207468617420616c69676e206973206120706f776572206f66203220616e642074686520726f756e6465642d757020616c6c6f636174696f6e2073697a6520646f6573206e6f7420657863656564206973697a653a3a4d41586361706163697479206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e7273617373657274696f6e206661696c65643a206164646974696f6e616c203e2030606e65775f6c61796f75742e73697a65282960206d7573742062652067726561746572207468616e206f7220657175616c20746f20606f6c645f6c61796f75742e73697a652829602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a636f70795f6e6f6e6f7665726c617070696e67207265717569726573207468617420626f746820706f696e74657220617267756d656e74732061726520616c69676e656420616e64206e6f6e2d6e756c6c20616e642074686520737065636966696564206d656d6f72792072616e67657320646f206e6f74206f7665726c6170756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77292073686f756c64206265203c206c656e20286973202972656d6f76616c20696e64657820286973202f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6e756d2e727330783030303130323033303430353036303730383039313031313132313331343135313631373138313932303231323232333234323532363237323832393330333133323333333433353336333733383339343034313432343334343435343634373438343935303531353235333534353535363537353835393630363136323633363436353636363736383639373037313732373337343735373637373738373938303831383238333834383538363837383838393930393139323933393439353936393739383939756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6d6f642e7273617373657274696f6e206661696c65643a206172672e706f736974696f6e203c20617267732e6c656e2829617373657274696f6e206661696c65643a2069203c20617267732e6c656e28292f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f697465722e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a2068696e743a3a756e726561636861626c655f756e636865636b6564206d757374206e657665722062652072656163686564756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a7375625f707472207265717569726573206073656c66203e3d206f726967696e60756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20696e76616c69642076616c756520666f7220606368617260756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a6765745f756e636865636b656420726571756972657320746861742074686520696e6465782069732077697468696e2074686520736c696365756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207374723a3a6765745f756e636865636b65642072657175697265732074686174207468652072616e67652069732077697468696e2074686520737472696e6720736c696365756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a636f70795f6e6f6e6f7665726c617070696e67207265717569726573207468617420626f746820706f696e74657220617267756d656e74732061726520616c69676e656420616e64206e6f6e2d6e756c6c20616e642074686520737065636966696564206d656d6f72792072616e67657320646f206e6f74206f7665726c6170756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f70616e69636b696e672e72730000000000617373657274696f6e20606c6566742020726967687460206661696c65643a20696e646578206f7574206f6620626f756e64733a20746865206c656e20697320206275742074686520696e646578206973200a20206c6566743a200a2072696768743a2020726967687460206661696c65640a20206c6566743a203d3d213d6d6174636865732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f636f756e742e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f76616c69646174696f6e732e727301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030304040404040000000000000000000000756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f697465722f7472616974732f616363756d2e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d41586063616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c7565010000000000000000000000000000003a2020202020207b202c20207b0a2c0a7d207d2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6275696c646572732e727328280a292c0a5b5d2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f697465722e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f7061747465726e2e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207374723a3a6765745f756e636865636b65642072657175697265732074686174207468652072616e67652069732077697468696e2074686520737472696e6720736c69636572616e676520737461727420696e64657820206f7574206f662072616e676520666f7220736c696365206f66206c656e67746820617474656d707420746f206164642077697468206f766572666c6f77617474656d707420746f2073756274726163742077697468206f766572666c6f77617474656d707420746f206d756c7469706c792077697468206f766572666c6f77617474656d707420746f206e65676174652077697468206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f736c6963652f6d656d6368722e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d4158600000000000000000000000000000000000000001000000000000002a0000000000000000000100000000005e00000000000000540400002b00000000000100000000005e00000000000000500400000300000000000100000000005e000000000000008e040000180000000e020100000000007900000000000000600700000900000000000100000000005e000000000000006c0400000f00000000000100000000005e000000000000009e0400001000000000000100000000005e00000000000000f70000000f000000b80301000000000088000000000000003100000009000000b40601000000000077000000000000009f00000019000000000000000000000008000000000000000800000000000000420400000000000000000000000000000800000000000000080000000000000094050000000000000000000000000000080000000000000008000000000000009605000000000000000000000000000008000000000000000800000000000000a005000000000000af070100000000000900000000000000b807010000000000080000000000000020080100000000000c000000000000002c080100000000000c00000000000000380801000000000009000000000000003007010000000000040000000000000041080100000000000c000000000000000000000000000000200000000000000001000000000000006a090000000000000000000000000000080000000000000008000000000000003001000000000000000000000000000080000000000000000100000000000000700900000000000000000000000000000800000000000000080000000000000090050000000000000000000000000000040000000000000004000000000000002e010000000000000000000000000000080000000000000008000000000000006806000000000000000000000000000008000000000000000800000000000000a205000000000000000000000000000008000000000000000800000000000000660600000000000000000000000000000800000000000000080000000000000032040000000000000000000000000000200000000000000001000000000000006a09000000000000000000000000000008000000000000000800000000000000640600000000000000000000000000000800000000000000080000000000000022040000000000000000000000000000080000000000000008000000000000002a040000000000000000000000000000020000000000000002000000000000002c01000000000000000000000000000008000000000000000800000000000000820800000000000000000000000000000800000000000000080000000000000080080000000000000000000000000000080000000000000008000000000000002a050000000000000000000000000000080000000000000008000000000000008405000000000000000000000000000008000000000000000800000000000000a805000000000000020a0100000000001a000000000000001c0a01000000000001000000000000001d0a0100000000000d0000000000000054070100000000000400000000000000600a01000000000010000000000000004c070100000000000400000000000000a00a0100000000000a00000000000000500a0100000000001000000000000000aa0a0100000000002500000000000000300000001d000000aa0a01000000000025000000000000004500000019000000aa0a01000000000025000000000000003c00000019000000aa0a01000000000025000000000000003400000019000000aa0a0100000000002500000000000000470000001a0000000000000000000000000000000000000001000000000000007401000000000000aa0a01000000000025000000000000002a00000034000000fa0a0100000000001e00000000000000180b01000000000003000000000000001b0b01000000000002000000000000001d0b0100000000001500000000000000320b0100000000001b00000000000000400a01000000000010000000000000001c0a01000000000001000000000000004d0b0100000000001b00000000000000680b0100000000001800000000000000800b0100000000000c000000000000008c0b0100000000000600000000000000920b0100000000001100000000000000a30b0100000000001100000000000000b40b0100000000000900000000000000bd0b0100000000000f00000000000000cc0b0100000000001500000000000000e10b0100000000000100000000000000e10b0100000000000100000000000000e10b0100000000000100000000000000f8070100000000000800000000000000e20b0100000000001e00000000000000000c0100000000000900000000000000090c01000000000026000000000000002f0c0100000000002f000000000000005e0c0100000000001200000000000000300a0100000000001000000000000000700c0100000000000f000000000000007f0c010000000000170000000000000001000000000000000000000000000000960c0100000000001d00000000000000b30c0100000000000f00000000000000c20c0100000000000e00000000000000d00c0100000000000700000000000000d70c0100000000000900000000000000e00c0100000000000600000000000000e60c0100000000000a00000000000000f00c0100000000000b00000000000000fb0c0100000000000e00000000000000090d01000000000006000000000000000f0d0100000000000100000000000000100d0100000000000f000000000000001f0d0100000000000a00000000000000290d01000000000011000000000000003a0d0100000000000900000000000000430d0100000000001500000000000000580d01000000000006000000000000005e0d0100000000000600000000000000640d0100000000000600000000000000900a01000000000010000000000000006a0d0100000000000d00000000000000770d01000000000015000000000000008c0d01000000000002000000000000008e0d0100000000000d000000000000009b0d0100000000000500000000000000a00d0100000000000100000000000000ac0d01000000000019000000000000000000000000000000010000000000000001000000000000007a01000000000000aa0a01000000000025000000000000007b00000046000000aa0a01000000000025000000000000006900000031000000aa0a0100000000002500000000000000b10000001d000000aa0a0100000000002500000000000000ac00000021000000aa0a0100000000002500000000000000cb0000003e000000aa0a0100000000002500000000000000c900000036000000aa0a0100000000002500000000000000ce0000003c000000aa0a0100000000002500000000000000d600000040000000aa0a0100000000002500000000000000d500000035000000aa0a0100000000002500000000000000f700000019000000aa0a0100000000002500000000000000b60000003e000000aa0a0100000000002500000000000000c100000022000000aa0a0100000000002500000000000000be00000051000000aa0a0100000000002500000000000000c600000043000000aa0a01000000000025000000000000000501000022000000aa0a01000000000025000000000000000201000021000000aa0a0100000000002500000000000000010100004b000000aa0a01000000000025000000000000000e0100001e000000aa0a01000000000025000000000000005500000050000000aa0a0100000000002500000000000000fc00000049000000000e010000000000170000000000000048070100000000000400000000000000170e0100000000000b0000000000000000080100000000000800000000000000aa0a01000000000025000000000000001d01000024000000aa0a0100000000002500000000000000280100000e000000aa0a01000000000025000000000000001d0100002f000000e00e0100000000008b000000000000003400000005000000970f0100000000007900000000000000600700000900000000000000000000000000000000000000010000000000000062040000000000007e120100000000005e00000000000000540400002b0000007e120100000000005e00000000000000f70000000f000000e3130100000000008500000000000000b307000009000000000000000000000001000000000000000100000000000000f80500000000000040130100000000005b000000000000006703000004000000000000000000000000000000000000000100000000000000f605000000000000931401000000000054000000000000002a00000009000000000000000000000008000000000000000800000000000000a6050000000000000000000000000000080000000000000008000000000000009405000000000000000000000000000008000000000000000800000000000000a20500000000000014170100000000005e00000000000000540400002b00000000000000000000000800000000000000080000000000000042040000000000000000000000000000100000000000000008000000000000003a040000000000000000000000000000080000000000000008000000000000008c05000000000000000000000000000008000000000000000800000000000000a005000000000000000000000000000010000000000000000800000000000000140600000000000000000000000000000800000000000000080000000000000098050000000000000000000000000000080000000000000008000000000000002a05000000000000000000000000000008000000000000000800000000000000ac05000000000000000000000000000008000000000000000800000000000000a4050000000000000000000000000000080000000000000008000000000000001a040000000000000418010000000000250000000000000042000000340000008f170100000000009a17010000000000a617010000000000b117010000000000b817010000000000be17010000000000cc17010000000000000000000000000008000000000000000800000000000000a4050000000000000000000000000000080000000000000008000000000000007a050000000000000000000000000000080000000000000008000000000000000e08000000000000000000000000000008000000000000000800000000000000420400000000000000000000000000000800000000000000080000000000000088050000000000000000000000000000080000000000000008000000000000007005000000000000b5190100000000005e00000000000000f70000000f0000000000000000000000010000000000000001000000000000006c06000000000000261a01000000000025000000000000008e0000004f0000000000000000000000000000000000000001000000000000005208000000000000dd1c0100000000005e00000000000000540400002b000000401d0100000000000900000000000000491d0100000000000800000000000000811d0100000000000c000000000000008d1d0100000000000c00000000000000991d0100000000000900000000000000a21d0100000000000400000000000000b61d0100000000000c000000000000000000000000000000200000000000000001000000000000006a0900000000000000000000000000000800000000000000080000000000000050080000000000000000000000000000800000000000000001000000000000007009000000000000000000000000000008000000000000000800000000000000900500000000000000000000000000000800000000000000080000000000000094050000000000000000000000000000040000000000000004000000000000004e080000000000000000000000000000080000000000000008000000000000006806000000000000000000000000000008000000000000000800000000000000a205000000000000000000000000000008000000000000000800000000000000660600000000000000000000000000000800000000000000080000000000000032040000000000000000000000000000200000000000000001000000000000006a09000000000000000000000000000008000000000000000800000000000000640600000000000000000000000000000800000000000000080000000000000022040000000000000000000000000000080000000000000008000000000000002a040000000000000000000000000000020000000000000002000000000000004c08000000000000000000000000000008000000000000000800000000000000820800000000000000000000000000000800000000000000080000000000000080080000000000000000000000000000080000000000000008000000000000002a0500000000000000000000000000000800000000000000080000000000000042040000000000000000000000000000080000000000000008000000000000008405000000000000000000000000000008000000000000000800000000000000a805000000000000dd1c0100000000005e00000000000000f70000000f000000c41f01000000000048000000000000000c200100000000007700000000000000d10000000900000083200100000000007900000000000000040300000900000083200100000000007900000000000000830200000900000083200100000000007900000000000000900200001c000000832001000000000079000000000000002b020000110000001c210100000000007800000000000000ce040000050000000000000000000000200000000000000001000000000000006a090000000000000000000000000000200000000000000001000000000000006a0900000000000000000000000000009000000000000000010000000000000076090000000000000000000000000000080000000000000008000000000000006a06000000000000c6220100000000000100000000000000c8220100000000000200000000000000ca220100000000005500000000000000a6000000200000001f2301000000000060000000000000002d0100003d0000001f23010000000000600000000000000031010000050000001f2301000000000060000000000000001401000014000000922601000000000062000000000000005200000017000000f42601000000000060000000000000002d0100003d000000f4260100000000006000000000000000310100000500000034260100000000005e00000000000000f70000000f00000054270100000000008500000000000000b307000009000000d9270100000000001400000000000000ed270100000000002300000000000000210000001200000010280100000000004500000000000000ed270100000000002300000000000000560000000d000000ed270100000000002300000000000000820000000d0000009d290100000000002700000000000000020200001a0000009d29010000000000270000000000000005020000200000009d290100000000002700000000000000080200001a0000009d2901000000000027000000000000000b0200002000000000000000000000000100000000000000010000000000000012090000000000009d290100000000002700000000000000540200000a0000009d290100000000002700000000000000ba0200000a0000009d290100000000002700000000000000d8020000200000009d290100000000002700000000000000120300000a0000009d290100000000002700000000000000870300000a0000009d2901000000000027000000000000009a030000180000009d290100000000002700000000000000980300000e0000009d290100000000002700000000000000cb030000160000009d290100000000002700000000000000ce0300000a0000009d290100000000002700000000000000dc0300001c0000009d290100000000002700000000000000d70300000e000000742d01000000000062000000000000005200000017000000fc2a0100000000005e00000000000000f70000000f0000000000000000000000010000000000000001000000000000002a09000000000000092e0100000000002700000000000000200300000e00000000000000000000000000000000000000010000000000000014090000000000005b2e010000000000200000000000000017000000590000007a320100000000004800000000000000c2320100000000007700000000000000d10000000900000039330100000000007900000000000000040300000900000039330100000000007900000000000000900200001c000000b2330100000000002c00000000000000ca0000001b000000b2330100000000002c00000000000000cc000000170000000000000000000000080000000000000008000000000000004c0900000000000000000000000000000800000000000000080000000000000050090000000000000000000000000000080000000000000008000000000000004e09000000000000313501000000000079000000000000006007000009000000d0350100000000006b000000000000003f000000090000003b360100000000002c00000000000000ea000000130000003037010000000000010000000000000030370100000000000100000000000000313701000000000002000000000000003337010000000000030000000000000099380100000000004800000000000000e1380100000000007700000000000000d10000000900000058390100000000007900000000000000040300000900000058390100000000007900000000000000830200000900000058390100000000007900000000000000900200001c000000583901000000000079000000000000002b0200001100000000000000000000000800000000000000080000000000000096090000000000005e3b010000000000820000000000000095000000010000009e0900000000000018000000000000000800000000000000ac09000000000000b009000000000000b609000000000000573c01000000000015000000000000006c3c0100000000000d00000000000000e03b0100000000007700000000000000b60100000d000000e03b0100000000007700000000000000b40100000d000000793c0100000000007800000000000000ea01000017000000000000000000000000000000000000000100000000000000a209000000000000473d01000000000075000000000000007e0200000e000000793c01000000000078000000000000008d0500001b000000b80900000000000018000000000000000800000000000000bc09000000000000c009000000000000b6090000000000001d3e01000000000078000000000000008d0500001b0000007e3f01000000000011000000000000008f3f01000000000079000000000000002b020000110000008f3f010000000000790000000000000083020000090000008f3f0100000000007900000000000000900200001c0000008f3f010000000000790000000000000004030000090000002840010000000000480000000000000070400100000000007700000000000000d100000009000000000000000000000008000000000000000800000000000000d40900000000000005420100000000001200000000000000ee41010000000000160000000000000004420100000000000100000000000000174201000000000078000000000000005b00000017000000ba430100000000007800000000000000d104000005000000ba430100000000007800000000000000e00400000d000000ba430100000000007800000000000000490500000d000000ba4301000000000078000000000000004d0500000d000000ba430100000000007800000000000000e105000030000000ba4301000000000078000000000000003a08000009000000000000000000000010000000000000000800000000000000560a0000000000007d440100000000007900000000000000c000000011000000b9480100000000007a00000000000000e3000000050000005849010000000000200000000000000078490100000000001200000000000000000000000000000008000000000000000800000000000000520a00000000000038490100000000001000000000000000484901000000000010000000000000008a49010000000000090000000000000093490100000000000900000000000000384901000000000010000000000000009c49010000000000170000000000000093490100000000000900000000000000be490100000000007a000000000000004800000015000000be490100000000007a000000000000005b00000009000000be490100000000007a00000000000000670000000d000000be490100000000007a000000000000006500000011000000be490100000000007a000000000000005500000011000000384a0100000000008000000000000000b100000018000000194c0100000000008200000000000000950000000100000001000000000000000000000000000000784d01000000000002000000000000000000000000000000180000000000000008000000000000006a0a000000000000720a000000000000a40a0000000000008b4d0100000000007d000000000000006701000009000000104e0100000000007900000000000000890200002d000000894e0100000000007c00000000000000c901000020000000894e0100000000007c00000000000000c901000011000000cc4f0100000000001200000000000000de4f010000000000220000000000000000500100000000001c000000000000001c5001000000000021000000000000003d5001000000000021000000000000005e500100000000001f000000000000007d500100000000007d0000000000000052000000150000007d500100000000007d00000000000000670000004b0000007d500100000000007d00000000000000680000003400000006000000000000000400000000000000500000000000000080700000000000001000000000000000000c0000000c0000800000000000000008000000000000000000c000af7a0100855203c166420d00002900004900006700009e0000b10000c10000ec00001401002701004401005a01006b0100890100f301001802002c0200900200b50200c902000703007b03009b0300e10300080400350400610400ce0400860500a60500ec05001306004006006c0600d90600b00700c80700de07000c08003608008c0800130900480900bd0900f209001c0a004f0a003b0b00430b00630b00b20b00120c004b0c007e0c00bd0c001b0d00e90d00190e008a0e00bc0e00720f00970f00b40f00d20f00ee0f00ff0f001610002a10004810007c1000b410005711008a11005c1200691200961200c91200ff12001913003613005d1300cb13009d1400c51400e01400fc1400a41500d01500a21600af1600dc1600f216002817004217005f1700861700f41700cf1800ff18004219007719008819009f1900b01900c11900d81900ea1900fc19000f1a00211a00541a00851a00151b00451b00001c00281c00431c005f1c00071d00331d00f81d00191e00601e00881e00b61e00e31e00511f00fc1f000a20005620006a2000862000a82000c62000e520003821004c21006821008a2100a82100c72100412200552200772200862200e72200f722001723002c23006623007d2300942300f32300142400252400452400562400762400872400c12400f12400f324001b25002c25004225005325008d2500bd2500bf2500df2500e825001626003026006a26009a2600cc26000c27004b2700932700802800952800b12800c82800f028003429005429006229009f2900a72900ce2900142a001c2a005d2a00652a00a42a00252b002f2b00432b00612b00cd2b000f2c00472c00652c00d82c00e22c00f62c00142d004c2d00a72d00b12d00f32d00112e002f2e004d2e006b2e00ba2e00d82e00f62e00372f00412f005e2f00682f00902f009a2f00ae2f00cc2f00de2f00f82f000a30003230003a30004c3000b83000e730004231004a3100b03100e531000c32006032007b3200dd32000e33002a33007a33008d3300ba3300ea3300073400423400683400773400ac3400de34002c36005836008a3600ab3600bd3600d936001d37005137006d3700f837002c38004838006f38008c3800933800c13800fe38003239004e39007f3a00d33a00073b000e3b007d3c00b13c00cd3c00f83c002c3d00333d006a3e00933e00c53e00cc3e00753f00a83f00dd3f000140004040006c4000a14000b94000e841002542005742005e4200a24200d44200f042001b43005c43008e4300ad4300e444003b45006d4500894500ea46003e47007047008c47009f4700db47000d4800294800904800e448000f49002949005e4900754900ad4900b04900dc4900f44900614a00934a00af4a00e94a004d4b007b4b00024c00244c00344c00934c00c54c00fc4c00094d001c4d00494d00704d008b4d00ad4d00d94d00104e002e4e00734e00a54e00c74e00d24e00f24e00204f00494f007b4f00ab4f00d84f00fd4f00655000935000ca5000f05000145100455100775100a65100d351000a52002e5200555200645200895200c15200db52000253001953005e5300905300c75300eb53000d54001a54002d54005a54008154009c5400d15400ec5400145500325500df55000a56002656005956006d5600a75600c55600fd56003057005d5700ca5700ff57001f58006c5800985800e758001359004b59007459009c5900e15900145a004f5a00ed5b002f5c003c5c004d5c00745c00995c00b15c00e75c002f5d003e5d00715d00b25d00e15d00145e002b5e008b5e00af5e00be5e00f15e005e5f00c75f00de5f001c60005360007f6000b26000d360002f6100dc6200996900a269001c6a00506a00746a00086b001e6b003c6b00a56b00bf6b000d6c00276c00926c00c96c00186e00486e00c46e00396f006c6f00a66f00f46f001a70002970005f70008f7000c37000e370001d71003f71004e7100807100be7100307200897200bd7200e072002773003173005a73008c7300ab7300dc7300ef73002374004274007474009a7600e87600267700d87700177800577800707800817800b27800c37800fb78000b7900537900827900927900ed7900047a00367a00657a00977a00d37a00097b00177b00387b00547b00867b009b7b00d97b00f47b00757d00957d00d27d003c7e00697e00a37e00c77e00f57e00087f002f7f00597f00877f009a7f00c17f00eb7f001980002c8000538000848000b88000cb8000f280000d81005081006381007d81009e8100c78100da81003382004b82007182008a8200cb8200dc8200108300b38300d783005084008c8400e684002e85003c8500648500928500a58500cc8500fb85002d86004186006886008b8600988600bc8600c98600ec8600f986001c8700408700638700c08700ec8700088800398800c588000b89002789003d89006089006789008a8900918900c78900f68900f88900238a002a8a00438a004a8a00808a00af8a00b18a00d38a00dc8a00088b003b8b00718b00a08b00cd8b00fd8b002d8c009a8d000d8e00668e00948e00bf8e00c88e00fe8e00828f00bc8f00ed8f00149000439000609000859000b79000ec9100f991000f92001d9200c592004d9300ae93000a9400149500429500a59500d59500ed95001b96005696007e9600b59600c99600fd96009b9700d89700989800a99800b99800d19800f698001a9900739900849900949900ac9900d19900f599001b9a003f9a00689a007f9a009d9a00b19a00c59a00d99a00ed9a00019b00159b00299b003d9b00519b00659b00799b008d9b00a19b00b59b00c99b00dd9b00f19b00059c00199c002d9c00419c00559c00699c007d9c00919c00a59c00b99c00cd9c00e19c00f59c00099d00129d002c9d00459d00649d00729d007b9d00869d009f9d00b99d00ca9d00d39d00de9d00259e002d9e005a9e00629e008f9e00979e00d39e00db9e00f29e00149f00389f00789f007f9f00a69f00b39f00ca9f00e19f00f89f0042a0004aa0005fa00076a00094a000a8a000bca000d0a000e4a000f8a0000ca10020a10034a10048a1005ca10070a10084a10098a100aca100c0a100d4a100e8a100fca10010a20024a20038a2004ca20060a20074a20088a2009ca200b0a200c4a200d8a200eca20000a30009a30023a30024a30031a30051a3007da30093a3000da4002fa4003ba4006fa40091a400a7a400dfa4001ba50033a5003aa50045a50072a500e8a50014a60034a60063a6008da60064a70093a700c1a70067a80006a900e8a9002caa0048aa004faa0072aa0079aa009caa00a3aa00d9aa0008ab000aab0035ab003cab0055ab006cab00a2ab00d1ab00d3ab00f6ab00fdab002aac0034ac006aac0099ac00c5ac00f4ac0023ad0097ad0008af000daf0012af0017af001caf0042af0082af00b4af00cbaf00ecaf000bb00030b0004eb000c1b000d5b000f6b0000ab1001ab10057b10067b1007eb100d9b10016b20059b2009cb200dfb20016b30043b300afb300dab3001ab4004bb4005db4006fb40081b400b9b400d1b400f2b40007b50018b50029b5009ab500d9b50018b6003fb60057b600b7b60001b70026b70051b70082b700c3b700d3b700e3b700f5b7003cb80083b800bfb800c1b800cfb800e9b800fdb8000cb9001db90045b9005fb90088b900a1b900d8b900dbb90005ba0008ba0029ba005bba0096ba0099ba00a8ba00b9ba00d2ba0012bb0015bb0055bb0058bb008fbb0092bb00a3bb00bcbb00c7bb00f9bb0025bc0047bc0060bc00b9bc00f9bc00fcbc001dbd004fbd0057bd006fbd00a1bd00b2bd00d6bd00eabd00ffbd0028be005abe0065be0097be00dfbe00ffbe0023bf003bbf004bbf0066bf0092bf00b9bf00d2bf00ebbf001ec0003ac00053c0008ac000aac000c9c000e8c0001bc10037c10050c10072c1008fc100a8c100d7c10054c20087c200a5c200c2c200dfc200f8c20008c30049c30073c300a8c300b8c300fbc3000ec4001cc40030c40044c40058c4006cc4007bc40085c40088c4008ec400a5c400bbc400cfc400e3c400f7c4000dc5002ac5002dc50033c50047c5005dc5008bc50097c500abc500d1c500e5c500fac5000fc60024c60034c6003ac6004ec60063c60069c6007dc6008bc6009fc600a5c600bfc600d3c600e2c600f6c600fcc60010c7004ac7005ec70072c70087c7009ec700b1c700c8c700d6c700ecc70000c80024c80032c80046c80080c80097c800abc800bac800cec800ebc800ffc80032c90040c90042c90057c9007bc9001dca005cca00dcca00e6ca00f9ca0017cb0082cb00c3cb00fbcb0019cc008bcc0095cc00a8cc00c6cc00fecc0058cd0062cd00a3cd00c1cd00dfcd00fccd001ace0067ce0085ce00a3ce00e3ce00edce0009cf0013cf003acf0044cf0057cf006bcf007fcf0093cf00a0cf00bfcf00e2cf0048d000ead00000d10098d100b4d100d0d100ecd10025d20041d2005dd20097d200fed2004dd3007cd30030d4009ad400c7d4002bd5006fd500c6d500cbd500d0d5004ed60056d600b7d6001dd70045d70092d700fad70063d8008cd80024d90033d90043d90051d900a2d900c3d9002bda0073da0095da00ffda003ddc0069dc00b4dc00dadc006edd008fdd00f7dd005fde0081de00ebde0066df00a9df00b9df00cadf00d9df001de0003be000d2e00012e10046e10071e100d5e100fee10010e2001fe2002ee2003ce20069e20082e200bee200b1e30000e4002ae400e0e400e7e40035e70075e80088e800bce900e3e900a7ea00fdea0098eb004cec00aeec00e7ec000ced0027ed0047ed006ded0033ee00f7ee001def002aef005cef006aef0091ef00bcef0079f00087f000cef00036f1006bf100aff10001f20031f20076f2007bf30091f3000cf40028f40080f400dbf40009f500ebf5000df60021f60061f60077f600aaf60071f70087f7009df700b3f700cff700e2f700f5f70008f80010f80029f8002af800aaf80068fa0016fc0038fc00c3fc007b0001cf00013e01019801011902013e02019802011903013e03014e0301530301060401af0401cd0401540501d90501ef0501a306010e0701380701ed07011b0801310801890801b20801dd0801fc08014809012c0a01430a01650a019d0a01e60a01f10a01fa0a01220b01c00b01db0b01e70b01060c01520c015d0c01660c018e0c012c0d01630d017f0d01eb0d01550e01810e011e0f01340f01d80f01ee0f014710017a1001f610015c1101c511012e1201431201941201fa1201601301c61301db13018a1401301501ae1501c21501141601201701761701c71701e917013f18016c1801821801ac1801c51801401901da1901e71901601a01d81a01f21a011f1b01bf1b01fe1b011b1c01661c01bb1c01d81c01ed1c01381d01ae1d01cb1d01e01d01f51d01401e01d51e01f31e01081f011d1f01321f017f1f01c51f013a20019120012b21016c2101892101c92101ed2101252201322301e62301062401102401a924017626014c27017e29011f2a01cf2d01412f01d92f019c35010236018a36010437016237019737012838014738016a3801873801af38011d39013139014539015e3901dd39015a3a01753a019e3a010c3b01253b01aa3b01ce3b01383c01583c01843c01f23c010b3d015f3d016c3d012844690000288076000028fc768219285709748218209517309516307b18645a821b1050100419e7008217307b17088215187c17387b171051051b3308013306016457501006783d015107487b1630951640017b6582193051090f821840510809501008c6f0008219089599fe330807821710db9807821058821550821648951160320033072501013308a200284e2e013307201f013308a40028412e018219105709598215086467645850100c51e6008216510622330801646750100e003d01510746491120019517187b7682192052091028199517207b7682192051090f82181851080950101041f000821038821530821628951140320033072501013308a20028d82d013307201f013308a40028cb2d015107830033070e33080133060e50101498ef0051077d7b16087b171049111895170833090e9516086458501016f4060182191857096c82150882181082177b18501018a8e50051051b330801330601645750101a563c015107557b1608951620017b6582190851090f82182051080950101ca4ef0082103882153082162895114032003307201f013308a400283b2d013309b0520133070133080e28000e0133072501013308a200281f2d013307201f013308a40028122d019511c87b10307b15287b1620648598882052086764767a15109517107b19649850101e57d900975704c87608ac686582175105397b18080182687b181095181033090864755010202006018267087b171095181033090864575010220c06016457821808956610ab86d182103082152882162095113832003307c70101330a005201330b20520133081695191f28fa32013307c4000133086128752c019511c87b10307b15287b1620648598882052086764767a15109517107b196498501024bad800975704c87608ac686582175105397b18080181687a181095181033090464755010268305018267087b171095181033090864575010286f05016457821808956610ab86d182103082152882162095113832003307c70101330a005201330b20520133081695191f285d32013307c4000133086128d82b0133090564873308dd010128a31801951148ff7b10b0007b15a8007b16a00064957b182849114895195049115001491158510541017b1733078052017b170833079852017b1720281082171801821510c9750551052e0182185833070002e67506951748330a01330b206469821c0850102c3ecf00492198004921900049218800492180009518800033092082172850102e2204015207a8007b1510646733067b17189577ff7b173001821798008218900082198800821a8000821558821b487b17787b18707b19687b1a60abb50f9517488218205010302dce0098573b5207170197570557071001821850c88707d8870852080301951860330920501032a63d019555017b1558821730aa673eff4921980049219000492188004921800095188000330920821728501034880301956601510778ff821718ae7614ff82164882155051066c98673b5207b80097660533080164675010366039015107b20049218000019517607b768219800052094f28588298088299330a7b18407b193828229517508278821a488279087b1838140800000000000000807b19408217aa8a388218408219387b78107b79082834951780007b768219800051090f8218605108095010385fec008217140a0000000000000080280d140a0000000000000080017b7a8210b0008215a8008216a0009511b80032003307c4000133086128d829013307db1e0133084528cc29013307201f013308a40028bf2901951148ff7b10b0007b15a8007b16a00064957b182849114895195049115001491158510541017b1733078052017b170833079852017b1720281082171801821510c9750551052e0182185833070002e67506951748330a01330b206469821c0850103a33cd00492198004921900049218800492180009518800033092082172850103c1702015207a8007b1510646733067b17189577ff7b173001821798008218900082198800821a8000821558821b487b17787b18707b19687b1a60abb50f95174882182050103e22cc0098573b5207170197570557071001821850c88707d88708520803019518603309205010409b3b019555017b1558821730aa673eff49219800492190004921880049218000951880003309208217285010427d0101956601510778ff821718ae7614ff82164882155051066c98673b5207b80097660533080164675010445537015107b20049218000019517607b768219800052094f28588298088299330a7b18407b193828229517508278821a488279087b1838140800000000000000807b19408217aa8a388218408219387b78107b79082834951780007b768219800051090f82186051080950104654ea008217140a0000000000000080280d140a0000000000000080017b7a8210b0008215a8008216a0009511b80032003307c4000133086128cd27013307db1e0133084528c127013307201f013308a40028b427019511f8fe7b1000017b15f8007b16f0006475491178951a80004921800008492188005109fe0164867b1533078052017b1708140700000000000000807b172833079852017b17187b1820281982173084770152070e02821910821738c979095109d001821888007b191033070001e67905951778330a08330b406459821c0850104809cb00330c017b1538330701017b17307b1c58646750104ac0d10083775207ad9788209889209517b000646850104cad058218b0008217287b1860aa788f8217b8007b17508217c0007b17484821c8009518c800330904646750104eb1ff005207f6008117c8007b17404921e0004921d8004921d0004921c8009518c800330920646750105087ff005207cc008217e0008218d8008219d000821ac80082168800821b787b17a8007b18a0007b1998007b1a9000abb62b9517788218183300529511f07b10087b15827c6485330901330a08330b4064c833006c082854cb0098673a5207df019767065707d80182188000c88707d887085208ca01847807821c585208cc018218607b788218507b78088218487b78108218900082199800821aa000821ba8007b78187b79207b7a287b7b308218407a78389566017b168800821738d87c079555ff95cc018216205205bffe2882fe8215605105273308016457501054ae3401510780014921c800019517e8007b758219c800520914284efe9517c8007b758219c800510941fe8218e800510839fe501056e5e700282ffe82a80882a933077b18707b196828249519800082988217788299087b1868140800000000000000807b19708215aa87d7008218708219687b58107b590828d2008215880082178000510566957608280b9555ff9566405105538269f8826751092f7b17603308017b195864975010580434015107c9004921c80001951890008219587b898219c80052091228c59518c8007b898219c8005109b9821890005108b250105a3be70028a9821780000182167882155106377b176098673a52079900976606330808330508646750105ca53301510790007b15c8009518900082157b868219c800520912281c9518c8007b868219c8005109108218900051080950105edee60014070000000000000080017b57821000018215f8008216f0009511080132003307c400013308612865240133075e0001330866285924013307201f013308a400284c24013307201f013308a400283f24013307db1e01330845283324013307201f013308a40028262401951150ff7b10a8007b15a0007b16980064956486491150951958491158084911605105a6017b1733078052017b1708140700000000000000807b172033079852017b17187b1628281c82163084660182173852061001821510c975058216285105bc018218603307aa02e67509951750330a08330b187b1938821c085010607ec700646750106245ce0083775207d6007b15103305017b15300197882098892095176864685010642802821768821820aa8799821778821870821968821660821a507b1790007b1888007b198000aba62b9517508218183300669511f07b10087b15827c6485330901330a08330b1864c833006408282ec800330718d6760752079001976703976805c9780757078301821858c88707d887085208760184780752087b018218900082198800821a80007b78107b79087b7a9566017b1660821738aa5715ffd875077b1730955501821628646750106876cd00837751073fff28eafe821560821758510572957608280b9555ff9566185105608269f8826751092e7b17383308017b1930649750106a7631015107150149218000019518688219307b898219800052091228c6951880007b89821980005109ba8218685108b450106cafe40028ab8298088299330a7b18487b194028678217580182195051096c1408abaaaaaaaaaaaa0aae89ca006475979803979705c98706330808646750106e0431015107bc0049218000089518687b868219800052094028499517588278821a508279087b1840140800000000000000807b19488217aa8a388218488219407b78107b790828349518800049088219800051090f82186851080950107012e4008217140a0000000000000080280d140a0000000000000080017b7a8210a8008215a000821698009511b00032003307c40001330861288b210133075e0001330866287f21013307201f013308a400287221013307db1e01330845286621013307201f013308a400285921019511807b10787b15707b16687b1818828808ae98133308ff140a000000000000008028be00491140951a4849114801491150510993007b17330533078052017b1710017b192833070040e67906951740330a01330b0164586469821c10501072d0c400821850c86805ac85eb008217407b1708ac57ea007b16207b15505705ca00821648c98509c86808821718501074a4f90052075e821928821720c979095209a495184882870882887b1738140900000000000000807b18308217821a08aa9a56ff281082a80882a9330a7b18387b1930018218388219307b78107b7908017b7a82107882157082166895118000320064658216085106253308016467501076372f0151076a4911600195185882177b8682196052091428fdfe95186082177b868219605109f0fe8218585108e9fe50107871e20082173308ff140a0000000000000080289b3307c211013308a600280520013307505201283b30013307e20101330968520133082c28c91f013307201f013308a40028de1f019511e0fd7b1018027b1510027b1608026485491170951a7849117808492180005109d2017b1733078052017b170833079852017b17187b152028168466015206f101821910821728c979095109b601821880007b191033076be67906951770330a08330b98006469821c0850107a47c3003307017b16286468330601017b18507b17584821880195188801330904645750107c26f8005207a5811788017b17484821880195188801330904645750107e09f800520788811788017b17404921880195188801330908645750208000ecf70052076aff821788017b173895178801330980003308502082005731019518880133098000645750208400bff70052073dff95170801951888013309800050208600993101492188019518880133090864575020880094f700520712ff821788017b173095178800951808013309800050208a0067310182158000821770ab752d95177082181833008c009511f07b10087b15827c6485330901330a08330b980064c8330062082864c30033089800d68507520734019656980057062c01821778c87606d8760752071f01846707520724019518880033098000646750208e00fb30018217387b6780008217307b6788008217487a6790008217407a6794009555017b158000821728821958d879068218509588ff959701821520520892fe2853fe82a80882a9330a7b18687b196028229517788278821a708279087b1860140800000000000000807b19688217aa8a798218688219607b78107b79082875821770821578510739140887f21aca6b28af01ae878c00967698003308086467502090006b2c01510784004921880108951708017b7682198801520912281d951788014907821988015109118218080151080a50209200a4df008217140a0000000000000080280d140a0000000000000080017b7a8210180282151002821608029511200232003307c40001330861281c1d0133075e000133086628101d013307db1e0133084528041d013307201f013308a40028f71c01951110fe7b10e8017b15e0017b16d80164957b18304911509519584911580849116051056a017b1733078052017b1708140700000000000000807b172833079852017b1720281082171801821510c9750551054a01821860330759e67506951750330a08330bb8006469821c08502094005ec0009517688218305020960005a300821768821828aa87be007b1510646733067b17189577ff7b173801951720019518683309b80050209800192f01821560821750ab752d95175082182033009a009511f07b10087b15827c6485330901330a08330bb80064c833006a082817c1003307b800d67507520728019657b80057072001821858c88707d887085208130184780752081801951820013309b80050209c00b02e019555017b1560821738aa6727ff95176882183050209e0055a200821768956601821828ab8762ff821718ae760aff9517505020a000407482175082155851077514084316b290852c6401ae87c7009676b80033080864675020a200652a015107bf0049212001089517687b768219200152094f28598298088299330a7b18487b194028229517588278821a508279087b1840140800000000000000807b19488217aa8a398218488219407b78107b790828359517200149078219200151091082186851080a5020a40063dd008217140a0000000000000080280d140a0000000000000080017b7a8210e8018215e0018216d8019511f00132003307c4000133086128db1a0133075e000133086628cf1a013307db1e0133084528c31a013307201f013308a40028b61a01951168ff7b1090007b1588007b168000648b491158951a6049116008491168510900017b1733078052017b170833079852017b17187b1b202818645b84670152071d01821910821728c979095109e2008218687b191033070004e67905951758330a0864b6330b106459821c085020a6001dbe00646b3309017b1528645a330601017b1a387b194048117895187833090464b764b55020a800f9f20052079e8117787b173049117895187833090864575020aa00def200520783821668821758821578ab76109517588218185020ac0086bc0098673c5207fe009767045707f700821860c88707d887085208ea00847807821940821a385208e9008218307a787b75089566017b1668821728d8790695aaff959901821b20520a6aff2827ff82a80882a9330a7b18507b194828229517608278821a588279087b1848140800000000000000807b19508217aa8a6a8218508219487b78107b7908286682165882156051062d98673c5207840097660433080864675020ae001e280151077d491178089517707b76821978520910281a9517787b7682197851091082187051080a5020b0005edb008217140a0000000000000080280d140a0000000000000080017b7a8210900082158800821680009511980032003307c4000133086128d6180133075e000133086628ca18013307db1e0133084528be18013307201f013308a40028b118019511d0fd7b1028027b1520027b16180264957b18304911509519584911580849116051054d017b1733078052017b1708140718000000000000807b172833079852017b1720281082171801821510c9750551052d0182186033074be67506951750330a08330bd8006469821c085020b20018bc009517688218305020b40073a100821768821828aa87a1007b1510646733067b17189577ff7b173801951740019518683309d8005020b600d32a01821560821750ab75109517508218205020b8008ebb003307d800d67507520728019657d80057072001821858c88707d887085208130184780752081801951840013309d8005020ba00872a019555017b1560821738aa6744ff9517688218305020bc00e0a000821768956601821828ab877fff821718ae7627ff9517505020be000b71821750821558510775140885f612da4b682f01ae87c7009676d80033080864675020c0003c26015107bf0049214001089517687b768219400152094f28598298088299330a7b18487b194028229517588278821a508279087b1840140800000000000000807b19488217aa8a398218488219407b78107b790828359517400149078219400151091082186851080a5020c2003ad9008217140a0000000000000080280d140a0000000000000080017b7a8210280282152002821618029511300232003307c4000133086128b2160133075e000133086628a616013307db1e01330845289a16013307201f013308a400288d1601951170fb7b1088047b1580047b167804649a6486491160951968491168014911707b1708510a580233078052017b171033079852017b17207b1628281c821730847701951968821a1852075402821738c97a0a510a0c028218707b1a18330730e67a097b1938951760330a01330b5001330501821c105020c400ebb900821738330801017b18307b17407b1548492100044921f8034921f0034921e8039518e80333092064675020c600bfee00520790821700048218f8038219f003821ae8037b1730037b1828037b1920037b1a1803492100044921f8034921f0034921e8039518e80333092064675020c8007cee0052074dff821700048218f8038219f003821ae8037b1750037b1848037b1940037b1a38039517e8033309900033085020ca00ce27019518e8033309900064675020cc0036ee00520707ff951758039518e803330990005020ce001028019517e8033309800033085020d0009527019518e8033309800064675020d200fded005207cefe9517c8019518e803330980005020d400d7270195181803330920951748025020d600c5270195183803330920951768025020d800b327019518580333099000951788025020da00a027019517789518c801330950015020dc008e2701821670821760ab762d9517608218203300de009511f07b10087b15827c6485330901330a01330b500164c833006608288cb90033085001d68607821548520709019667500157070101821868c88707d887085208f400951878330950015020e0002a27019566017b1670821738d875088217409577ff955501821628520739fe951968821a18821738c97a0a520afcfd82988217608299087b1850140800000000000000807b1958ab87132879829808829933077b18587b195001821858821950821a087ba81064a87ba90828668217608215685107361408c4300cc3300cc300ae87719676500133080164675020e200ad220151076a4921c801019517787b768219c801520912281c9517c80149078219c80151091082187851080a5020e400e9d50014070000000000000080821808017b878210880482158004821678049511900432003307c40001330861286c13013307db1e01330845286013013307201f013308a40028531301951168ff7b1090007b1588007b168000648b491158951a6049116008491168510900017b1733078052017b170833079852017b17187b1b202818645b84670152071d01821910821728c979095109e2008218687b191033070004e67905951758330a0864b6330b106459821c085020e600bab600646b3309017b1528645a330601017b1a387b194049117895187833090864b764b55020e80096eb0052079e8217787b173049117895187833090864575020ea007beb00520783821668821758821578ab76109517588218185020ec0023b50098673c5207fe009767045707f700821860c88707d887085208ea00847807821940821a385208e9008218307b787b75089566017b1668821728d8790695aaff959901821b20520a6aff2827ff82a80882a9330a7b18507b194828229517608278821a588279087b1848140800000000000000807b19508217aa8a6a8218508219487b78107b7908286682165882156051062d98673c5207840097660433080864675020ee00bb200151077d491178089517707b76821978520910281a9517787b7682197851091082187051080a5020f000fbd3008217140a0000000000000080280d140a0000000000000080017b7a8210900082158800821680009511980032003307c400013308612873110133075e0001330866286711013307db1e01330845285b11013307201f013308a400284e1101951148ff7b10b0007b15a8007b16a00064957b182849114895195049115001491158510547017b1733078052017b170833079852017b1720281082171801821510c975055105340182185833070002e67506951748330a01330b206469821c085020f200c2b40049219800492190004921880049218000951880003309208217285020f400a5e9005207ab007b1510646733067b17189577ff7b173001821798008218900082198800821a8000821558821b487b17787b18707b19687b1a60abb5109517488218205020f600afb30098573b52071b0197570557071401821850c88707d88708520807019518603309205020f8002723019555017b1558821730aa673aff49219800492190004921880049218000951880003309208217285020fa0008e900956601510775ff821718ae760fff82164882155051066d98673b5207ba0097660533080164675020fc00df1e015107b30049218000019517607b768219800052094f28598298088299330a7b18407b193828229517508278821a488279087b1838140800000000000000807b19408217aa8a398218408219387b78107b79082835951780007b768219800051091082186051080a5020fe00ddd1008217140a0000000000000080280d140a0000000000000080017b7a8210b0008215a8008216a0009511b80032003307c4000133086128550f013307db1e0133084528490f013307201f013308a400283c0f019511c87b10307b15287b1620330a18d6a80a7b1910520a9200978903978a05c99a0a570a8500c8a709d8790952097b5108449575100182565706497b1a0898672052074d8257f87b177a1618951718821810502000014fbb0082171082186469502002012fe800821a0895aae8955518520ac6821030821528821620951138320033072011013308a20028ae0e013307c70101330a005201330b20520133081695191f280e15013307c4000133086128890e015107ac0033070e33080133060e5020060156d0005107a5007b16187b172049112895171833090e645850200801b3e700821928570995008215188218209517189516187b18821a08821b1050200a019bc6008217187b17087c17207b171051051c330801330601645750200c01fc1c015107657b1618951630017b6582191851091082183051080a50200e0149d0008219089599fe330807821710db980782104882154082163895115032003307201f013308a40028d00d013309b0520133070133080e2895ee0033072501013308a20028b40d013307201f013308a40028a70d015107ac003307073308013306075020120174cf005107a5007b16187b1720491128951718330907645850201401d1e600821928570995008215188218209517189516187b18821a08821b1050201601b9c5008217187b17087c17207b171051051c3308013306016457502018011a1c015107657b1618951630017b6582191851091082183051080a50201a0167cf008219089599fe330807821710db980782104882154082163895115032003307201f013308a40028ee0c013309b0520133070133080728b3ed0033072501013308a20028d20c013307201f013308a40028c50c019511b07b10487b15407b16389595047b18087b1710ac9579649633070133080150201c01811b0151077057057a3308017b18645750201e016bce0051076c7b15187b172098672049112852076a7a163495173495181850202001d4b800951718821808646950202201b3e500821728821820821918821a107ba7107ba8087ba982104882154082163895115032003307385201286d1c013307201f013308a40028210c014901013309b052018217645828e5ec003307c70101330a005201330b2052013308169519342871120151076333070c33080150202601c9cd00510760816849010c7b17084911107a181864179518183309045020280122e5008267087b1718641795181833090850202a010de50082171082180882197b57107b58087b5982103082152882162095113832003307201f013308a40028880b013309b0520133070133080c284dec00808934849a10520a05ee00849920520966ee0028d30101808934849a10520ac0ee0084992052091eef0028bc0301808934849a10520a75ef008499205209d3ef00287806019511b87b10407b15387b1630647982761407ffffffffffffff7fc8760888881714070000000000000080d367079387330816ac78b6039777023308880201c887078177c88707320714070000000000000080aa76960382950851067703330801646750203401ae1901520706023307201f013308a40028ba0a018296088295105106500398673c520796039766043308086467502038017d190152079e013307201f013308a40028890a0182960882951051061f0398673b52077103976605330801646750203c014c19015207a4013307201f013308a40028580a018296187b19088297107b1751060a028215955508280c9566ff9555185106f9018259f8825751092d7b17183308017b19106497502040010119015107e002491128019518208219107b8982192852091028c69518287b898219285109bc8218205108b6502042013dcc0028ac82970882951051077f021408c4300cc3300cc300ae87d90296765001330801646750204601a718015207ff003307201f013308a40028b3090182960882951051064902330801646750204a018018015207d8003307201f013308a400288c09018296187b19088297107b1751067f018215955508280c9566ff95551851066e018259f8825751092d7b17183308017b1910649750204e0135180151072102491128019518208219107b8982192852091028c69518287b898219285109bc8218205108b65020500171cb0028ac8296088295105106b60198673c52072002976604330808646750205401e3170151071902330708283882960814070000000000000080aa769e0182951051067f0198673b52073302976605330801646750205801ac170151072c02330701017b17289517207b7682192852095d012866018296187b19088297107b175106e1008215955508280c9566ff9555185106d0008259f8825751092d7b17183308017b1910649750205c0158170151075101491128019518208219107b8982192852091028c69518287b898219285109bc8218205108b650205e0194ca0028ac8217088277085107bd001408abaaaaaaaaaaaa0aae875501977803977705c98705330808330608645750206001f61601520783003307201f013308a4002802080182170882770851077c1408abaaaaaaaaaaaa0aae872101977803977705c98705330808330608645750206201b616015207433307201f013308a40028c3070182170882770851073d1408abaaaaaaaaaaaa0aae87ee00977803977705c987053308083306086457502064017716015107de007b16289517207b758219285209102836951728490782192851092c8218205108263300660128b3c9003306019517287b7682192851091082182051080a502066019bc90082104082153882163095114832003307201f013308a400283107013307201f013308a400282407013307201f013308a400281707013307db1e01330845280b07013307db1e0133084528ff06013307db1e0133084528f306013307db1e0133084528e706013307201f013308a40028da06013307db1e0133084528ce06013307db1e0133084528c206013307db1e0133084528b606013307201f013308a40028a906013307db1e01330845289d06013307201f013308a400289006019511d07b10287b15207b1618828a08828918330b01aaba27520a275209247b17103307013308013306015020680142150151078a003305330801285c51090c50206a0134e10028677b1710828782787b180882750833070133080150206c011115015107595705635105183308017b18645750206e01f8c7005107526476280633060101821808d86807c9860ac96809db7a09ac59456467645950207001bf18018217107b757b76087b75100182102882152082161895113032003307201f013308a40028c805014901013309e0520182176458288ce60033074004013308a60028ab0501827910570910648a827708649864a928f7f80033071206013308a200288b0501330905648733082b07012856f2006479827a140b00000000000000806487abba0f33085007013309042837f2009511f07b10087b193308440701330bf85201330904641a50207801b1f70082100895111032007c77977903330a300e01977702330b680e01c8b7078177c8a9098299c87b0a648764a828eaf1009511f07b100864797c7a6487510a1f9599017b193308ac0701330b385301330903641a33007e012854f7009599087b193308aa0701330b185301330902641a50207e013cf70082100895111032009511f07b100864797c7a6487521a071d7b193308aa0701330b585301330902641a330082012808f7007b193308ac0701330b385301330903641a50208201f3f6008210089511103200951118ff7b10e00064798277140affffffffffffff7fc8a70b88bb17140a0000000000000080d3a70793b7977702330ae40201c8a7078177c87a0a6487320a959818959a38959b40959c489592509593587b19687b18703308e853017b18787b1a800033080854017b1888007b1b90007b1898007b1ca0007b18a8007b12b0007b18b8007b13c00033082854017b18c8009518687b18d00033084854017b18d80033020733084d0801330a78530133090d330b07951c707b125020880176f4008210e0009511e800320033081809013309058210e0009511e8002875f0009599087b19703302e855013308090901330a340701330905330b04282604959c20959824959a289599087b197033096855017b19609519707b1958491150083309c807017b194833026854017b12407b1a38491130093309ec08017b19287b12207b1818491110063308e608017b18083308da0801330adf0801330905330b077b1233009801288af200959c289599087b19703308c854017b18209518707b1818491110093308af07017b180833026854013308890801330a8e0801330905330b067b123300b601286cf000959c209599087b19703308a855017b18209518707b1818491110053308fb08017b180833028855013308f50801330a3807013309062869029599087b197033024855013308c90801330acf0801330906330b05282203959c980095988800959a90009599087b197033098854017b19609519707b19584911500433093007017b194833090854017b19407b1a3849113009330a7208017b1a287b19207b18184911100633086c08017b180833026854013308e00701330a610801330908330b0b7b125020980182f1008210e0009511e800320033080e090133090a8210e0009511e80028c2ee009599087b19703302a8540133087b0801330a820801330907330b07287302959c089598287b187033081853017b18209518707b1818491110033308b308017b180833020855013308b60801330a340701330906286401959c089598289599307b197033091853017b19409519707b19384911300c330a2c08017b1a28330a0854017b1a207b18184911100c33082008017b18083302e8530133085a0801330aaf0701330907330b097b125020a201a9ef008210e0009511e8003200959c289599087b19703308e855017b18209518707b18184911100833081008017b1808330268540133081d0901330a240901330907330b0a7b123300b6012888ee009599087b197033021853013308d80701330a2e0901330908330b052876019599087b19703302e854013308940801330a9f080133090b330b0d2858019599087b19703308330901330b085601330913951a703300ba012800f3009599087b19703302c855013308000901330a3c0701330909330b04281c01959c089598287b187033081853017b18209518707b1818491110033308b308017b180833020855013308ac0801330a340701330907330b047b123300b60128ceed00330b047b123300b60128c1ed009599087b19703302f852013308080801330a580701330908330b0428af009599087b19703302e854013308d40801330a580701330906330b04289100959c289599087b197033082855017b18209518707b18184911100433083407017b180833026854013308bc0801330ac20801330906330b077b125020b60147ed008210e0009511e80032009599087b19703308460901330b285601330917951a705020ba01f2f1008210e0009511e80032009599087b19703302e854013308c00701330a580701330908330b0401951c707b125020be014bec008210e0009511e800320033085d09013309098210e0009511e800280aec009511e87b101064798177977702330a440301c8a7078177c87a0a6487320a33085c070133090482101095111828daeb009599047b19083302a854013308920901330a85090133090d282d33086f090133090782101095111828aeeb009599047b19083302a854013308760901330a85090133090f01330b0d951c087b125020cc01a9eb0082101095111832003308660901330909821010951118286ceb009511a87b10506479817a330b016487adab18520a6233089f090133090a8210509511582845eb00959c04521a02799599087b19483308a854017b18209518487b1818491110113308c709017b180833026854013308ae0901330ab9090133090b330b0e7b125020d201c6eb0082105095115832009599047b19483302a854013308180801330aa90901330908330b05951c487b125020d401f4ea00821050951158320095980895990c7b19483309a854017b19409519487b1938491130113309f109017b192833026854017b12207b18184911100e3308e309017b18083308d80901330ab9090133090b330b0e7b125020d6010eec0082105095115832003309e409951720079518ac007b18d0017b19107b19d8019518a800330a5e0a3309620a7b18e0017b1a707b1ae8017b17f0017b19207b19f80133077857017b1700014921080103492120019518d0017b18100149211801039517a003951800015020da01a5f6821bb003590b062873378216a8033308400701330702330904646a0a64018215a0039517000751051f33080164575020dc01dd0b01520706284f3749210007019517d001017b75821900075109118218d00151080a5020de0122bf00821aa00082a7103308b800d687095109062825369679b800590906281b3682a808c88909d88909510906280c369677b80082a9c887077b18b0007b18b8007b19c0007b17c800951700079518b0005020e00148628219000714080000000000000080ab89289517b0005020e20105738217084607951118068210e8018215e0018216d8019511f00132001407ffffffffffffff7f7b1780003305d800140718000000000000807b1760330784017b17683307a857017b175849114821384c7b1840281a951700079518b0005020e401cb6182190007821840aa898d7b19288217100759070628d12f821808077b18187b18d0007b17d8009517d0005020e6017ba600837751070628882f978820988920951700079518d0005020e8010ae382170007821840ab870628672f82191007d65908510806284e2fca590a590a0628452f82180807c88a0ad88a0b510b0628352f7b18e0007b18e8007b17f0007b1af8005209589517e0005020ea01f775821728520706288a2d3308015020ec01350a01520706283f2f49210007019518d0013305d8008217287b8782190007510926ff28712d9517d0015020ee01f7ef8217f8008218e800aa78ae9587d8008489077b17e800510906289b2d8285958808951700013309d0005020f001c50d01821760aa75837b15d001951800013309d0009517d8015020f201a80d019517d0017b17a0038217687b17a8038217587b1700074921080701492120079518a0037b18100749211807019517a802951800075020f40187d500821bb802590b0628312d8216a8028215b0023308400701330703330904645a3300f6010a640151062933080164675020f8014b090152070628412d49210007019517a0037b7682190007520912281d951700077b76821900075109118218a00351080a5020fa0182bc00821ad00182198000c89a07887817959b01d3ba0793875417170728c6129777023308580301c887078177c887077b1b7832077b1a9000951ce8017cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17d8027cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17d0027cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17c8027cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d48707821808027b18a0008216180282182002821510027b17c0027b1698037b18f0059517000733098000951828025020fe01830b018217d8017b1788008217e0017b1798009517c0028218a0006459646a330000029511b07b10480a09287db20084780152080628f21533073858017b17a0034921a803014921c0034921b003084921b803951760039518a00350200202cbf1821b7003590b062885308216680382156003330733083309646a330004020a6401520506281e22330801645750200602050701520706281a314921a00301951750047b758219a003510915222803228217d8017b175004951750047b17a0038217207b17a80333078859017b1700074921080702492120079517a0037b17100749211807019517a8059518000750200a0238f1821bb805590b0628722c8216a8058215b0053308400701330702330904645a0a6401520606289f15330801646750200c0271060152070628002f49210007019517a0037b768219000751099615288415821ae00184a70751070628622c821ce80114075655555555555505ac7c06284e2c97c70397c805c97808c88a099517a00395185004aea90628682c7b1ca000821bd8017b1b90007b1a98007b1aa0037b19307b19a8037b17500433070c067b17580433076859017b1700074921080701492120077b18100749211807019517780595180007502010025df0821b8805590b0628ae2c82167805821580053308400701330702330904645a0a640152060628d51b33080164675020120296050152070628d72d49210007019517a0037b76821900075109cc1b28ba1b821be801590b0628ca2b8216d8018215e0013308e00d0195170007330909645a50201602d4ae00821700075217020728e52c520606284f243308016467502018023605015107942d2825249519d80182971082980882997b17b0037b18a8037b19a0039517a00333001c029511b07b10483300fe082873a6009517a0037b175004330790087b1758043307785a017b170007492108070149212007951850047b1810074921180701951758069518000750201e0257ef821b6806590b0628552a82165806821560063308400701330702330904645a0a6401520606286f1433080164675020200290040152070628392d4921000701951750047b7682190007510966142854148117f8019516d8017c68197c69187c6a1a7c6b1b978808d4980897aa1097bb18d4ba0ad4a8087c691d7c6a1c7c6b1e7c6c1f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1868047c68117c69107c6a127c6b13978808d4980897aa1097bb18d4ba0ad4a8087c69157c6a147c6b167c6c17979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1860047c68097c69087c6a0a7c6b0b978808d4980897aa1097bb18d4ba0ad4a8087c690d7c6a0c7c6b0e7c6c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1858047c68017c697c6a027c6b03978808d4980897aa1097bb187c6905d4ba0ad4a8087c6a049799087c6b067c6c07d4a9097a178c0697bb1097cc18d4cb0bd4b909979920d498087b185004951850043309205020240221ac008477ff007817c00695178c067b17a0038217707b17a8039517c0067b17b003330780017b17b8033307985a017b1700074921080702492120079517a0037b171007492118070295179006951800075020260282ed821ba006590b0628732882169006821598063308400701330702330904645a0a640152060628d421330801646750202802bb02015107dc2b28aa218117f8017a17c006951cd8017cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1768047cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1760047cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1758047cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17500433076a099518c0067b18a0038218107b18a803951850047b18b0037b17b8033307f858017b1700074921080702492120079517a0037b17100749211807029517f8049518000750202c02d8eb821b0805590b0628e3268216f804821500053308400701330702330904645a0a6401520606285311330801646750202e0211010152070628862949210007019517a0037b768219000751094a112838110a083307b85a017b1700074921080701492120074921100708492118079517a80695180007502032025deb821bb806590b0628cd268216a8068215b0063308400701330702330904645a0a640152060628af1f33080164675020340296000151076c2828851f951cd8017cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17b8037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17b0037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17a8037cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad48a0a8218f8018219000297aa20d4a7077b17a0039517a003330038029511b07b10480a0a28f4aa0033074858017b170007492108070149212007492110070849211807951778039518000750203a02c2e9821b8803590b0628772482167803821580033308400701330702330904645a0a64015106e20c330801646750203c02fdfe0052071a0f28cc278216d8018215e80114075555555555555505821ce0014921c006ae570628172584c707510706280e25975703975805c97808c88c079519a003aec7062899257b16387b1c507b15a00052051a3307289c0c821898009588e8821c900095cc1851087d0c7b1898007b1c90007b1c2805951728057b17a003330542047b15a80333071859017b1700074921080701492120077b191007492118070195173005951800075020400254ca00821b4005590b0628322282163005821a380533084007013307023309047b1a88000a64015106293308016467502042021afe00520706282a2249210007019517a0037b7682190007520912281d951700077b76821900075109118218a00351080a5020440251b1008217280582781059080628a021827708330046029511b07b10483309330a0a0328c5a7009519a00351170118ff951728057b17a0037b15a80333072859017b1700074921080701492120077b191007492118070195174805951800075020480289c900821b5805590b0628cf218215500582164805330733083309645a0a6401510629330801646750204a0256fd0052070628f92149210007019517a0037b7682190007520912281d951700077b76821900075109118218a00351080a50204c028db0008217c0069577015207062862217b17c0069519a0032867fe951cd8017cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17b8037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17b0037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17a8037cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17a0039517a003330050029511b07b10480a102853a4009517a0037b1750043307ae057b1758043307885a017b170007492108070149212007951850047b181007492118070195177006951800075020520230e6821b8006590b0628c72182167006821578063308400701330702330904645a0a6401510650093308016467502054026bfb005207880b282d248117d8017a17c0069517c0067b17a0038217107b17a8033307b858017b1700074921080701492120079518a0037b1810074921180701951798049518000750205802b3e5821ba804590b0628eb21821698048215a0043308400701330702330904645a0a64015106e80b330801646750205a02eefa0052070628162349210007019517a0037b76821900075209d00b28da0b8117680282185802821960027a1794037b1898037b19f0059517a003330980009518d80150205e0294fe000a7b17c00633075858017b1700079517c0067b1750048217207b175804492108070149212007951850047b1810074921180701951720049518000750206002f9e4821b3004590b0628f92082162004821528043308400701330702330904645a0a64015106610c33080164675020620234fa00520706288222492100070195175004821a707b76821900075209490c28570c951cd8017cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1768047cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1760047cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1758047cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad48a0a8218f80197aa20d4a7077b1750047b18c00695175004330066029511b07b10480a0e28a6a0008477ff0051170707287d1f33076a09951850047b18a0037b17a8039517c0067b17b0038217207b17b8033307a859017b1700074921080702492120079517a0037b17100749211807029517c00595180007502068021ae3821bd005590b0628fe1d8216c0058215c8053308400701330702330904645a0a64015106490d330801646750206a0255f80052070628f12049210007019517a0037b76821900075209310d283b0d951cd8017cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1768047cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1760047cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1758047cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a8088215f801978820d487077b1750049517500464580a0d0183795319030f5119030728c721988a20017b15a000987b207a17c0068119a8007a1bc4067a18c8067a1acc069517c006649850206e020b9e007a17f0057a18f4059517c0067b17a0033307d0017b17a8039517f0057b17b0033307c2017b17b8033307c859017b1700077b150807492120079517a0037b17100749211807029517d805951800075020700217e1821be805590b06280a1e8216d8058215e0053308400701330702330904645a0a64015106da0a33080164675020720252f60052070628181e49210007019517a0037b76821900075209c20a28cc0a951700079518d0013309d8005020760210fa00951700077b1750048217687b1758043307e85a017b17a0034921a803014921c003951850047b18b0034921b803019517d8079518a003502078027ae0821be807590b06286d1c8216d8078215e0073308400701330702330904645a0a64015106b208330801646750207a02b5f50052070628c61e4921a00301951750047b768219a00352099a0828a408330828140a0801821898008388015228e8030828ab147b1898007a1850049517a8007b17a0038216707b16a803951750047b17b0037b16b8033307c85a017b1700074921080702492120079518a0037b18100749211807029517d0069518000750207e0239c1008217e006590706289b187b17a0008217d0067b1790009518a003951750048215d8067b17a0037b16a8033307700a017b1700074921080701492120077b18100749211807019517e8069518000750208002e5c000821bf806590b062876188216e8068219f006951700077b158800645864958219a000645a50208202349e00821700075217020728281851062933080164675020840299f40052070628681849210007019517a0037b7682190007520912281d951700077b76821900075109118218a00351080a50208602d0a700821588008216900051062a3308016467502088024df40052070628291849210007019517a0037b768219000752091428a0fe951700077b7682190007510993fe8218a00351088bfe50208a0281a7002880fe8216d801821740ab7606282d138215e0018217e8017b1798003308e90d019517000733091450208e02279d00821800078217407b169000ab78e60d3308821a9800286d107f17f001951ad80182a81082a90882aa7917c0067b1860047b1958047b1a50049517c0067b17a00333072c017b17a803951750047b17b003330788087b17b8033307f859017b1700074921080702492120079517a0037b17100749211807029517f8059518000750209202f4dd821b0806590b06281f1a8216f805821500063308400701330702330904645a0a6401510652063308016467502094022ff30052070628281b49210007019517a0037b768219000752093a06284406951bd80182b9108117f00182ba088118f4017b1960048119f8017b1a580482ba7a1798037a18f0057a19c0067b1a5004951a500450209802494c951798037b1700078217707b1708079518f0057b1810077b1718079518c0067b1820077b172807951750047b173007330764047b1738073307385a017b17a0034921a803044921c003951700077b17b0034921b80304951740069518a00350209a0208dd821b5006590b0628b017821548068216400633070233083309645a0a640151068406330801646750209c0247f20052070628af1a49210007019517a0037b768219000752096c06287606951700077b76821900075209200d2842118217c0068215a000ae750628311a3308620ac975077b175004951750047b17a0037b18a8039517c0067b17b0037b18b80333073859017b1700074921080703492120077b1910074921180702951760059518000750209e0253dc821b7005590b06281d18821568058216600533070233083309645a3300a0020a64015106860a33080164675020a2028ef10052070628101a49210007019517a0037b768219000752096e0a28780a9877207a179403951794037b1750048217107b1758043307c857017b17a0034921a803024921c003951750047b17b0034921b803019517e0029518a0035020a402c2db821bf002590b0628d91a8215e0028216e8023308400701330702330904646a0a64015105430c33080164575020a602fdf00052070628051b4921a00301951750047b758219a00352092b0c28350c951700077b76821900075109118218a00351080a5020a80232a4003307013308015020aa02b9f00052070628ff173307083308015020ac02a4a3005207062884168218500449210007087b170807492110077b18a003951700079518a0033309085020ae02f0ba00821b1007590b0628621682160007821508073308d0070195170007330908645a5020b002d39900821700075217020728fc155106510f33080164675020b20237f00052072a0f283a19951700077b76821900075109118218500451080a5020b40281a3008217a0038215a8035107e0041408c4300cc3300cc300ac8706282a199676500133080164675020b602e9ef0052070628d3184921000701951750047b76821900075209d30a28f50e951700077b76821900075109118218a00351080a5020b8021ea3008117c006951850043300ba029511b07b10489777209877200a0c28299c008477ff007817f0059517f0057b17a003330780017b17a8033307a858017b1700074921080701492120079518a0037b181007492118070195171005951800075020bc02e2d9821b2005590b0628451582161005821518053308400701330702330904645a0a64015106370e33080164675020be021def005207100e285318951700077b76821900075109118218a00351080a5020c00267a200951700075020c2026f990082174007531751ac0081184807521802a3009578af951700075020c402ef96008477ff005217074d098117c0063300c602951178ff7b1080007b1578647595170833097033083300f8082822f2003307c858017b17a0034921a803014921c0034921b003084921b8039517b0049518a0035020c8020cd9821bc004590b062818198216b0048215b8043308400701330702330904645a0a64015106480933080164675020ca0247ee0052072309286b193307e858017b17a0034921a803014921c0034921b003084921b8039517e0049518a0035020cc02aad8821bf004590b062880178215e8048216e004330733083309645a0a64015106eb0833080164675020ce02eaed005207c60828321895170007821a707b76821900075109158218500451080e5020d00231a100821a7001951794037b1700077b1a0807951798037b1710078218207b181807330770099519f0057b1920077b1828079518a0037b1830077b17380733076858017b175004492158040449217004951700077b176004492168040495173804951850045020d202f0d7821b4804590b06287014821540048216380433070233083309645a0a64015106640233080164675020d4022fed00520706280c164921000701951750047b768219000752094c022856029517a0037b768219a0035109118218500451080a5020d60264a000951700073300ee0128d9d2951700077b76821900075109118218a00351080a5020d8023ea0007e17c006951850043300da029511a87b1050648979170695180664973300fc0828918f008477ff00521707653307285a017b17000749210807014921200749211007084921180795171006951800075020dc0210d7821b2006590b0628341682161006821518063308400701330702330904645a0a64015106450233080164675020de024bec0052076228ae163307185a017b17000749210807014921200749211007084921180795172806951800075020e002afd6821b3806590b062892158215300682162806330733083309645a0a64015106e90133080164675020e202efeb00520706282b1649210007019517a0037b76821900075209d10128db01951700077b76821900075109118218a00351080a5020e402249f0082165004821558045106ba0a98673c51070628de1397660433080864675020e60294eb00520706284a1433070828820a951700077b76821900075109118218a00351080a5020e802d99e00951750048218a0005020ea027d93008477ff005117070728ac123308d50d019517000733090b951a5004330b205020ec02ba9400821700075217025a0a282812951700074907821900075209220628440a951700077b76821900075109118218a00351080a5020ee026d9e003308cc0d0195170007330909951a5004330b205020f00268940082170007521702080a283a10951700077b76821900075109118218500451080a5020f2022c9e0081179403821898038219f005951aa0035020f402de96008477ff007817c0069517c0067b175004330780017b1758043307a858017b170007492108070149212007951850047b181007492118070195178004951800075020f602f7d4821b9004590b0628811082168004821588043308400701330702330904645a0a64015106360133080164675020f80232ea0052070628eb114921000701951750047b768219000752091e01282801951700077b76821900075109118218a00351080a5020fa02679d0082165004821558045106fd0898673b51070628f21197660533080164675020fc02d7e9005207ca0828c211951700077b76821900075109118218a00351080a5020fe02219d0033070133080150200003a8e90052070628161033070433080150200203939c008219a000520706280c1049210007047b170807492110077a19a003951700079518a00333090450200403dfb300821b1007590b0628e11082160007821508073308f0070195170007330908645a50200603c29200821700075217020728601051068b0033080164675020080326e900520706281d1249210007019517a0037b7682190007520973287e951700077b76821900075109118218500451080a50200a035d9c0081159403821698039518a0033309800095170c0750200c03c3ec007a1508077b1600079517000733000e039511a07b10587b15507b164864789517183300760628885b28d307951700077b76821900075109118218a00351080a50201003fc9b00821898007b18a0037b18a803821790007b17b0038217307b17b8034921c0038217a00051076b023306958918281382175095771882198800959918aa573e02829ae864678218407b1aa000aa8a19029578017b1890005108a80c7b1988009595e88258087b1898003308620a8256107b17c0069517c0067b1750047b18580433077859017b170007492108070149212007951850047b1810074921180701951790059518000750201203eab3008219a0055709400c7b15505706710c8215900582189805951700077b1838821a9800646b50201403399100821700075117022b0c821690005105273308016457502016039ce70051075c0c4921000701951750047b7582190007520912281d951700077b75821900075109118218500451080a50201803d59a008215308217a00051072a33080150201a0355e7005107080c4921000701951850048217a0007b878219000752091428d9fe951800077b87821900075109ccfe821850045108c4fe50201c03879a0028b9fe951700077b76821900075109118218a00351080a50201e03699a008217a00051076982155095550828138217a0009577ff9555187b17a0005107508256f8825751062b7b179800330801646750202003c5e6005107cb0a49210007019518a0037b868219000752091228c1951800077b86821900075109b58218a0035108ae50202203fe990028a48215385105431407abaaaaaaaaaaaa0aac750628600e975703975505c9750533080864575020240365e60052070628770e49210007089517a0037b7582190007520914287205951700077b7582190007510965058218a00351085d0533004e032895990064957b15a8037b17c0039517a0033300260328664f64677b15a8037b17c0039517a0033300260328514f9517a003502026034a4f281d057b18880082171007590706289d108218080764867b18a0037b17a803492150049517a0039518500433090850202803bfaf00510706284c10821750047b17a0008217880051071f0233080150202a03a3e50052070628bb104921a0030195185004821a9800821788007b878219a003520905022819023307d858017b17a0034921a803014921c0034921b003084921b8039517c8049518a00350202c03e9cf821bd804590b062825108215d0048216c804330733083309645a0a640151062a330801646750202e032ae5005207062835104921a00301951750047b768219a0035209142837049517a0037b768219a00351092a048218500452081b04281e049517a0037b758219a0035109118218500451080a5020300347980033070182184082159000ab85cf0728f2039517a0037b758219a0035109118218500451080a502032031b9800811794037a17a0033307c50d019518a003330034039511b07b10487b15407b1638491110047b18086475330701330801330010012874e4008477ff0051170707283a0e951750049518c002330036039511f87b1064893308ff3300ee0828ef910082165004821740aa76f6038219600459090628050f82185804811794037b18a00050203803048d008477ff0078172f0395172f037b17c006330780017b17c8063307e857017b17a0034921a803014921c0039518c0067b18b0034921b803019517f8029518a00350203a0374ce821b0803590b0628bd0e8215f802821a000333084007013307023309047b1a500a640151051003330801645750203c03aee30052070628ed0e4921a003019517c0067b758219a0035209f8022802039518a003821a98007b878219a00351091b8218500451081450203e03df9600821a98008218a00028078218a0000198a73b5207d50a97a6055706ce0ac86507d857075207c40a97a705c857077b1550047b155804821990007b1960047b176804510a93017b1790009567e0987705c887079577017b179800017b18a0007c57197c58187c591a7c5a1b977708d4870797991097aa18d4a909d497077c581d7c591c7c5a1e7c5b1f978808d4980897aa1097bb18d4ba0ad4a808978820d478027b12b8037c58117c59107c5a127c5b13978808d4980897aa1097bb18d4ba0ad4a8087c59157c5a147c5b167c5c17979908d4a90997bb1097cc18d4cb0bd4b909979920d489037b13b0037c59097c5a087c5b0a7c5c0b979908d4a90997bb1097cc18d4cb0bd4b9097c5a0d7c5b0c7c5c0e7c570f97aa08d4ba0a97cc10977718d4c707d4a707977720d479097b19a8037c57017c5a7c5b027c5c03977708d4a70797bb1097cc18d4cb0bd4b7077c5a057c5b047c5c067c580797aa08d4ba0a97cc10978818d4c808d4a808978820d487077b17a0037b1218077b1310077b1908077b1700079517c00695180007330920951a0007330b2050204003568b008217c006511702fa058218a0005118ffe8059555209566e09588015206a1fe821790007b17580482159800951750043300420328154c648595175004502042030c4c3307013308015020440386e10051072a0b330708330801502046037394005107d60a49210007087b170807492110077b15a003951700079518a00333090850204803c5ab00821b1007570b910a82160007821508073308e90d0195170007330914645a50204a03aa8a00821700075117020e0b51062a330801646750204c0311e1005107430b330701017b1700079517a0037b7682190007520912281d951700077b76821900075109118218a00351080a50204e034794008217d00182188000c87808888817821978d3970793875417160728afd6821848d078078477015107a3d628a9d69517a0037b758219a0035109118218c00651080a50205003ff930051062733080164675020520384e0005107b90b4921a003019517c0067b768219a003520912281d9517a0037b768219a0035109118218c00651080a50205403bd93000a7b17c0063307f857017b17a0039517c0067b1750048217207b1758044921a803014921c003951850047b18b0034921b80301951710039518a00350205603a3ca821b2003570ba30982151003821618033308400701330702330904646a0a6401510527330801645750205803e1df005107060a4921a00301951750047b758219a003520912281d9517a0037b758219a0035109118218500451080a50205a031a9300811794038218f00582199803951a000750205c03cc8b008477ff0078172f039517f0057b17a0038217207b17a803951898037b18b0037b17b80395172f037b17c003330780017b17c80333070858017b1750044921580403492170049517a0037b1760044921680403951730039518500450205e03cac9821b4003570bbd0882153003821638033308400701330702330904646a330060030a640151052733080164575020620304df00510777094921a00301951750047b758219a003520912281d9517a0037b758219a0035109118218500451080a502064033d920082184082159000aa85be017b154803821788007b175003821798007b1758033305800a019517a0033308800a01330066039511a07b10587b15507b164864767b1808491110109517289518083300f8049511c07b10387b15307b1628828a8289086476641764a833000a28bfc28217a003821840ab871249215004492158040849216004281b8217b0038218a8038219a0037b1760047b1858047b195004018217d8027b17b8038217d0028218c802821ac002811994037b17b0037b18a8037b1aa0039517500495184803951aa00350206803ab95007b15a0034921a80310951750049518a00350206a0329228477ff00521707d008821560048217580451056b957608280b9555ff9566405105588269f882675109337b17a0003308017b199800649750206c03a6dd00510799024921a003019518c006821998007b898219a00352091228c19518a0037b898219a0035109b58218c0065108ae50206e03db900028a4821758040182165004821590005106357b17a00098673a5207b20897660633080864675020700343dd00510792084921a003089518c0067b868219a003520912281d9518a0037b868219a0035109118218c00651080a502072037c900082184001d38507887701aa852bfc510727fc510528330801645750207403efdc00510748074921a00301951750047b758219a00352091428fefb9517a0037b758219a0035109f1fb821850045108e9fb33004e0328219000951800073305d8007b878219000752090628b4d18218d00152080628aad150207603ff8f0028a0d133071206013308a20028a0cd007c1708077817a0033307a10d01330af85a01330bb05c0133080b9519a00328f7d30033071206013308a2002871cd003307e604013308652865cd0033071206013308a2002858cd0033071206013308a200284bcd003307201f013308a400283ecd003307201f013308a4002831cd0033071206013308a2002824cd003307201f013308a4002817cd003307201f013308a400280acd003307201f013308a40028fdcc003307805c012833dd007c17c8067817f0053307e80701330af85a01330b985c013308089519f005284bd3003307605b012808dd0033071206013308a20028bccc0033071206013308a20028afcc003307c8520128e5dc007c170807781750043307e80701330af85a01330b205c013308089519500428fdd20033071206013308a2002877cc003307201f013308a400286acc003307201f013308a400285dcc003307201f013308a4002850cc003307b105013308612844cc003307cf0a01330a405701330bc85c0133082b9519c00628a3d2003307201f013308a400281dcc0033071206013308a2002810cc003307201f013308a4002803cc003307b857017b1700074921080701492120074921100708492118073308f05b01951700072878cb0033071206013308a20028cecb0033071206013308a20028c1cb007c1708077817a0033307e80701330af85a01330b785b013308089519a0032818d20033071206013308a2002892cb0033071206013308a2002885cb0033071206013308a2002878cb0033071206013308a200286bcb007c1708077817a0033307e80701330af85a01330b505c013308089519a00328c2d10033071206013308a200283ccb003309b052013307013308082801ac0033071206013308a2002820cb0033071206013308a2002813cb0033071206013308a2002806cb0033071206013308a20028f9ca0033071206013308a20028ecca0033071206013308a20028dfca0033072011013308a20028d2ca003307b1050133086128c6ca0033071206013308a20028b9ca0033071206013308a20028acca003307201f013308a400289fca003309b052013307013308042864ab0033071206013308a2002883ca0033071206013308a2002876ca00781700073307cf0a01330af85a01330b905b0133082b9519000728d1d00033071206013308a200284bca0033071206013308a200283eca0033071010013308612832ca0033071206013308a2002825ca007c1708077817a0033307e80701330af85a01330bc05b013308089519a003287cd00033071206013308a20028f6c9007c1708077817a0033307e80701330af85a01330b385c013308089519a003284dd0003307201f013308a40028c7c900781700073307cf0a01330af85a01330bd85b0133082b951900072822d0003307b10501330861289dc90033071206013308a2002890c90033071206013308a2002883c9007c1708077817a0033307e80701330af85a01330b085c013308089519a00328dacf003307201f013308a4002854c9003307201f013308a4002847c9003307201f013308a400283ac9003307db1e01330845282ec9003307201f013308a4002821c9003307201f013308a4002814c9003307db1e013308452808c9003307201f013308a40028fbc8003307485b012850d9003307201f013308a40028e5c8003307db1e0133084528d9c8003307201f013308a40028ccc8003307201f013308a40028bfc8003307201f013308a40028b2c8003307201f013308a40028a5c8003307201f013308a4002898c8003307201f013308a400288bc8003307201f013308a400287ec8003307201f013308a4002871c8003307201f013308a4002864c8003307201f013308a4002857c8003307201f013308a400284ac8003307201f013308a400283dc8003307201f013308a4002830c8003307201f013308a4002823c8003307201f013308a4002816c8003307201f013308a4002809c8003307201f013308a40028fcc7003307201f013308a40028efc7003307db1e0133084528e3c7003307201f013308a40028d6c70033071206013308a20028c9c70033071206013308a20028bcc7003309b052013307013308082881a80033071206013308a20028a0c70033071206013308a2002893c70033071206013308a2002886c70033071206013308a2002879c7003307201f013308a400286cc70033071206013308a200285fc70033071206013308a2002852c7007817a0033307e80701330af85a01330b305b013308089519a00328adcd007c1708077817a0033307e80701330af85a01330b685c013308089519a003288bcd003307201f013308a4002805c7003307201f013308a40028f8c6003307201f013308a40028ebc6003307201f013308a40028dec6003307201f013308a40028d1c6003307201f013308a40028c4c6003307201f013308a40028b7c6003307201f013308a40028aac6003307201f013308a400289dc6003307b105013308612891c6003307cf0a01330a405701330be05c0133082b9519c00628f0cc0033071206013308a200286ac60033071206013308a200285dc6003307a85b0128c0cc0033071206013308a2002847c60033071206013308a200283ac60033071206013308a200282dc6007817a0033307e80701330af85a01330b185b013308089519a0032888cc003307201f013308a4002802c6003307201f013308a40028f5c5003307201f013308a40028e8c5003307201f013308a40028dbc5003307201f013308a40028cec5003307201f013308a40028c1c5003307db1e0133084528b5c50033071206013308a20028a8c5003307201f013308a400289bc500951180fc7b1078037b1570037b166803837551056e0c64869777209877209788209888207b1740027b184802951760029518400233007a03951120fe7b10d8017b15d0017b16c8016486647564873300880628ec6f836850207c03c4860082186002140700000000000000807b1820aa781e0c81175803821868027b1818821870027b189800821878027b18107a17a4003307e4099518a4007b182002821880027b187b17280233074856017b176002492168020249218002951720027b177002492178020195174002951860029515600250207e0339be821b5002570bcb0b821a480233084007013307023309047b1a90000a64018216400251061f33080133050164675020800372d3005107cf0b7b15600295152002017b56821960025109118218200251080a50208203ba8600821898005708800b8217187b17a8007b18b0004921b8003306087b16c0004921c8005108b20714070000000000000080140812000000000000807b1730140718000000000000807b17787b188000140800000000000000807b187033076856017b174833074007017b1768330776017b174033077856017b17383307c856017b170849112800062028149517d00050208403a9b88217b00051073c07951760029518a80050208603034d82176002821878aa879b099517d000951860023309d8005020880373d6008217d00082188000c878089588ed888817821970d3790993895a19099b00aa6979015219091a018217e80098783c52087009977704570769098218e000c88707d8870952095b098219d8007b1840027b1848027b1950027b175802951760029518400250208a0381188215c8008217b800ab75123308e056019517b80050208c03c0663307d800d67507520738099657d800570730098218c000c88707d887085208220984780752088a0982188000958801286951190a720252191583008218d8007c19e0009517600250208e03648d008215c8008217b800ab751233081057019517b8005020900358663307d800d675075207c4089657d8005707bc088218c000c88707d887085208ae088478075208160982188000958804017b78821860027b7808821868027b7810821870027b78189555017b15c8002893fe8215c8008217b800ab751233082857019517b80050209203ed653307d800d6750752074d089657d800570745088218c000c88707d887085208370884780752089f089518d0003309d80050209403e6d4009555017b15c8002838fe951cd8007cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1738027cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1730027cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1728027cc7017cc87cc9027cca03977708d4870797991097aa187cc805d4a909d497077cc9049788087cca067ccb07d498088115f80097aa1097bb18d4ba0ad4a808978820d487077b1720028217487b1760024921680201492180027b167002492178029517a80195186002502096033dba821bb801570b2c07821ab0018216a8013307023309048218687b1a98000a6401510670023308016467502098037acf0051073d0749216002019517c0017b768219600252095a022864028218e80014075555555555555505ac87cb068216e0008467075207c0067b19509787036489978805c97808c868087b188800ac688e068217d8007b17587b16607b1998005209d3008215c8008217b800ab75123308f856019517b80050209a03bb633307d800d6750752073f069657d800570737068218c000c88707d887085208290684780752089106821880009588027b78821898007b78089555017b15c80051085f018216609566082810821898009588ff956618510849017b1898008265f8826751052b7b179000330801645750209c0389ce0051077a054921600201951820027b858219600252091228c0951860027b85821960025109b4821820025108ad50209e03c2810028a395661882178800aa7635ff8269107b16000257090c058268089517c0013300a0039511a07b10587b15507b16486475648764980a1328dd74951700027b172002330742047b1728029517c0017b17300233077c017b17380233078856017b176002492168020249218002951820027b187002492178020295170802951860025020a203d59900821b1802570ba10482150802821a100233084007013307023309047b1a90003300a4030a640151052833080164575020a60399cd0051077d044921600201951720027b7582196002520914282fff951760027b7582196002510922ff8218200251081aff5020a803cf8000280fff8215585105ae011407abaaaaaaaaaaaa0a330608ae753605975703975505c9750533080864575020aa0333cd00510710057b166002951720027b758219600252098201288b01951760027b76821960025109118218c00151080a5020ac036b80009517c0019519200264583300ae039511f87b109788209888203300f008287f7a9517c0017b17f0018217407b17f8018217387b1760024921680201492180029518f0017b18700249217802019517d801951860025020b00338b7821be801570b0d048215e0018216d801330702330904821868645a3300b2030a640151062733080164675020b40374cc005107440449216002019517f0017b7682196002520912281c951760027b76821960025109108218f0015108095020b603ad7f8217c001330608821830aa8774f98217d0018218c8018219c0018215c800821ab8007b1770027b1868027b196002aba5109517b8008218085020b803c1603307d800d67507520751039657d800570749038218c000c88707d8870852083b038478075208d603821880007b78821860027b7808821868027b7810821870027b78189555017b15c8009517d00033008403289bb1951760023306087b7582196002510910821820025108095020ba03fd7e821850561815cbf8821728d087078477015207c7f828b9f89517b8007b17c00133076c047b17c8013307a856017b1760024921680202492180029517c0017b17700249217802019517200295186002951560025020bc03ccb5821b3002570b7803821a280233084007013307033309047b1a98000a64018216200251061f33080133050164675020be0305cb0051076f037b1560029515c001017b56821960025109108218c0015108095020c0034d7e8219c8001407427b09ed25b49700ac9724038218c00084870752071903951760023300c2039511a87b10507b15487b164064967b18187b1710330701330801330054082898ca009517b8005020c40339158217b8008215c000510739140885f612da4b682f01ae87f3029676d80033080864675020c60368ca005107eb0249212002089517c0017b7682192002520912281c951720024907821920025109108218c0015108095020c803a17d82151051053398573b5207bd0297550533080133060164575020ca0317ca005107b3027b1620029517c0017b7582192002520912281c951720027b75821920025109108218c0015108095020cc03517d82172051072a3308013305015020ce03d3c90051077c027b1520029518c0018217207b8782192002520912281c951820027b87821920025109108218c0015108095020d0030a7d8215680282166002821770027b17980051062733080164675020d20380c900510736024921400201951720027b7682194002520912281c951740027b7682194002510910821820025108095020d403b97c64578218980082107803821570038216680395118003320033071206013308a2002846ba0033071206013308a2002839ba003307201f013308a400282cba003307201f013308a400281fba003307cf0a01330a405701330b60570133082b95196703287ec0003307b1050133086128f9b9003307b1050133086128edb9003307b1050133086128e1b9003307b1050133086128d5b9003307b1050133086128c9b9003307b1050133086128bdb900330710100133086128b1b90033071206013308a20028a4b90033072011013308a2002897b90033071206013308a200288ab90033074b0501330866287eb90033074b05013308662872b90033074b05013308662866b90033074b0501330866285ab9003307201f013308a400284db9003307201f013308a4002840b9003307201f013308a4002833b9003307db1e013308452827b90033074b0501330866281bb90033075c16013308a200280eb9003307681401330a505e01330b705e0133082b95196703286dbf0033071206013308a20028e7b80033071206013308a20028dab80033071206013308a20028cdb80033071206013308a20028c0b8003307201f013308a40028b3b8003307201f013308a40028a6b8003307db1e01330845289ab8003307201f013308a400288db8003307db1e013308452881b8003307201f013308a4002874b8003307201f013308a4002867b8003307201f013308a400285ab8009511987b1060837a838995171864a85020d603d121811830821718821a20821b288119347b177b1a087b1b10951718641a3300d803951110fe7b10e8017b15e0017b16d8019511e8f97b1aa0007b17087a18a8007a19ac00951700073300d8012855717c17185107119517403308208210609511683200330882106095116832009511b8fd7b1040027b1538027b163002837a83899517980164a85020da0320228217a80133089800d687095209ea05967698005706e2058218a001c86809d889095209d40582199801967a9800c88a0a7b18687b18707b19787b1a7b1a8000510786048487075207bb05958590003307220e017b172033074500037b1750140700000000000000807b17083307fd0d017b171833075e0a7b17483307620a7b1710280e956668ff9555980051062b047b164081578259f0955870ff7a178c007b19900095179c00330980005020dc03c5c70082162064673300de039511a07b10587b15507b16487b1710951728951810330002059511c07b10387b15307b162882857b1733070133080133001228a8c500837793788387017a181c0151071e047a1798019518980164673300e0039511b07b10487b15407b1638491110047b18086475330701330801330004012868c5008477ff0052170754047b15383307013308015020e20352c50051073204330780003308015020e4033e785107c803647595199c00d87907c99508c95909db780953298000800395189c00330980003306800064575020e603fdc800951798013309800064585020e80376b700821798017b17605107158216a00149113080007b155833058000280e8217087b17304911588000017b16283307013308015020ea03c7c40051079a033307033308015020ec03b477510719036476821918d87907c99608c96909db7809531903ea024616023f4616013f46063f82176051073049216801037b167001492178010333080182153064575020ee036fc4005107bb0333090164583300f20328c7778217587b1768017b1570018217287b1778013307033308015020f00340c40051077f033308033309015020f2039b7795178c007b1798018217487b17a001951790007b17a8018217107b17b001951768017b17b801330772017b17c0013307f85c017b173801492140010349215801951798017b174801492150010395172001951838015020f40368ae821668018215700151062733080164675020f603bec3005107f0024921380101951728027b7682193801520912281c951738017b7682193801510910821828025108095020f803f776821b3001570b1f0282162001821528013308400701330702330904645a0a640151062733080164675020fa035dc300510782024921980101951738017b7682199801520912281c951798017b7682199801510910821838015108095020fc03967695171c017b1738018217487b1740013307285d017b1798014921a001014921b801951838017b18a8014921b0010195178001951898015020fe0382ad8216900157068f01821780017b175881178c0082189000821588017a17a0017b18980195189c00330980009517a40150200004a6c6009517380195189801502002048835821b4801570b180182194001951768017b1560645864956469645a502004040a6c821768015117023701821638015106273308016467502006046ec200510786014921680101951828027b8682196801520912281c951868017b86821968015109108218280251080950200804a77582164082195851092e330801649750200a0427c20082153851072f014921980101951838018219587b898219980152091728f2fb951898018215387b89821998015109e2fb821838015108dafb50200c04547528d0fb82177b177095176833000e0428e42c95176850200e04de2c3307330882104002821538028216300295114802320033074004013308a60028c8b20033074004013308a60028bbb2003309e052013307013308032880930033071206013308a200289fb2003307385d0128d5c2003309e0520133070133088000285a930033071206013308a2002879b20033071206013308a200286cb2007c177001781728023307e80701330af85a01330b505d013308089519280228c3b8003307201f013308a400283db2003307201f013308a4002830b200781798013307e80701330af85a01330b685d0133080895199801288bb8003307201f013308a4002805b2003307201f013308a40028f8b1003307201f013308a40028ebb1003307201f013308a40028deb1003307201f013308a40028d1b1003307201f013308a40028c4b1003307b1050133086128b8b1003307e6040133086528acb1009511b07b10487b15407b1638647582767b1708510655825718825908aa79117c989599017b590833070128120182551095171051051b3308016457502010044dc0005107140149111001951730017b7582191051090f82183051080950201204987382150849050182584051086c825748aa78669587107b5740951710330014049511c87b10307b15287b1620648664753307013308013300240128edbf0082181014070000000000000080aa782b82172057079800821618c86707d8670952098b007b567b56087b58107b57186469aa765dff284bff825620510619825838825728aa87147c789577017b5728330701284b330728478257305107253308017b175020160486bf0051075a4911300195182882177b8782193052091028199518307b8782193051090f82182851080950201804c472330749152001821048821540821638951150320033071010013308612856b0003307201f013308a4002849b0003307201f013308a400283cb0009511c07b10387b15307b1628827782761098693a52095f82750895171050201c043ea3006468976605c86507ac575451082c33071061017b17019557207b17087b1520951710951820821950201e04fdbc009566e08215085206e29517105020200433be00821038821530821628951140320033072011013308a20028bcaf003307b4150133086128b0af009511c07b10387b15307b1628827782751098593b52096582760884670752075c95171050202404aca2006458975504c85607ac675451082c33073061017b17019567107b17087b16209517109518208219502026046bbc009555f08216085205e295171050202804a1bd00821038821530821628951140320033072011013308a200282aaf003307b41501330861281eaf009511c07b10387b15307b1628827782751098593b52096582760884670752075c95171050202c041aa2006458975504c85607ac675451082c3307b060017b17019567107b17087b1620951710951820821950202e04d9bb009555f08216085205e2951710502030040fbd00821038821530821628951140320033072011013308a2002898ae003307b41501330861288cae009511c07b10387b15307b1628827782751014095555555555555505ac596b8276088467075207629517105020340481a1009757036458975505c97505c85607ac675451082c3307f060017b17019567187b17087b16209517109518208219502036043abb009555e88216085205e29517105020380470bc00821038821530821628951140320033072011013308a20028f9ad00330710100133086128edad009511c07b10387b15307b1628827682750895171050203c04f8a000330718d6750752075e6458975703975505c97505570550c85607d8670752074751082c3307f060017b17019567187b17087b1620951710951820821950203e04a2ba009555e88216085205e295171050204004d8bb00821038821530821628951140320033071010013308612862ad009511c07b10387b15307b1628827782761057065a8275089517105020440467a000c86507ac575451062c33079060017b17019557017b17087b15209517109518208219502046042bba009566ff8215085206e29517105020480461bb00821038821530821628951140320033072011013308a20028eaac003307b4150133086128deac009511b07b10487b15407b163882761098693952090d018277087b1710847707520700017b1833070133080150204a048fbb005107fa009765067b1608955604330801646750204c04746e5107ef007b16187b17208216089867204911285207ea007a163095173095181850204e04d958821710c85708ac78ea0051061795764095181850205004320f9555c064675205ef8219285709d80082151882182082177b1810330052049511a07b10587b15507b1648827a827b087b1910648595171864a864b933000228529f6476510528330801645750205404e6ba0051079e00491118019517307b758219188465ff00520914281d9517187b758219188465ff0051090f821830510809502056041d6e6457821048821540821638951150320033072011013308a20028b2ab003307201f013308a40028a5ab003309e85d013307016468286b8c003307681201330ab05d01330bd05d0133081695193028f7b1003307c400013308612872ab0033072011013308a2002865ab003307201f013308a4002858ab009511d07b10287b15207b161882751098593a52095598592052095c8276087a15109517107b185020580497576468975605c86807ac87596459821551091e9587207b1708330920645750205a046284009566e08218085206e8821028821520821618951130320033072011013308a20028e4aa003307fe1601330a505e01330be85e013308169519172844b1003307c4000133086128bfaa00951178ff7b1080007b15787b16708289087b18088286187b1918ac96cf007b1733071833080833051850205c0472b9005107c500330a7b1610821b18c9b606986604976703976805d65609c978055209271407f8ffffffffffff7fac571a510524330808645750205e04336c330a08821b1852071c3309805d0164a7645828118b00821710abb77d3306330708017b16207b1728821a0882a895193082aa104911307b18387b1b407b1a488218107b18507b19584911607b176895173895185850206004710d821730821828821920821a7ba7107ba8087ba982108000821578821670951188003200330771100133084728caa9003307201f013308a40028bda9003307b8100133086828b1a90033090564873308dc1201287c96009511c07b10387b15307b162882751098593b52096582760884670752075c95171050206604a19c006458975504c85607ac675451082c3307b060017b17019567107b17087b162095171095182082195020680460b6009555f08216085205e295171050206a0496b700821038821530821628951140320033072011013308a200281fa9003307b415013308612813a9009511c07b10387b15307b16288275101409427b09ed25b49700ac596a82760884670752076195171050206e040a9c009657d800c86707ac675a5105329655d8003307d060017b17019567d8007b17087b1620951710951820821950207004c5b500955528ff8216085205e095171050207204fab600821038821530821628951140320033072011013308a2002883a8003307c41b013308612877a8008377510711140700000000000000807b57281297882098892064576468502076040187821010821508821695111832008377510711140700000000000000807b5728139788209889206457646850207a04607cff821010821508821695111832008377510711140700000000000000807b5728129788209889206457646850207e04438d821010821508821695111832008377510711140700000000000000807b57281297882098892064576468502082047694821010821508821695111832009511e87b10107b15087b166486647564875020840448528377510711140700000000000000807b57281297882098892064576468502086045c83821010821508821695111832009511c07b10387b15307b16288278105108bd00827708957520140700000000000000807b1728108218109588ff9555b80051089b007b18108256e08257e85106277b170833080164675020880402b60051078a00491120019518187b8682192052091028199518207b8682192051090f82181851080950208a0441698256f88217aa76a6825751062a7b1708330801646750208c04bab50051074f491120019518188217087b86821920520912287aff9518207b8682192051096fff821818510868ff50208e04f468285eff82103882153082162895114032003307201f013308a4002888a6003307201f013308a400287ba6009511807b10787b15707b16688278107b183051080c0433098277087b17281408ffffffffffffff7f7b1820140800000000000000807b18183308840e017b1810140700000000000000807b17081407abaaaaaaaaaaaa0a7b172815502090046868821938959901821730aa79b4037b19389697d800821828c887077b17408275821720c85707887817821718d357079387330816ac78cd977702821810c887078177c887073207821708aa75b7821740827608510551033308016457502094049bb40051076604491160019517587b7582196052093d03288a8217408276088275105106db0198673c5207020497660433080864675020980462b4005207770128de038217408276088275105106b10198673b52078d03976605330801646750209c0438b4005207820128690382184082851882871051050f02957608280c9555ff9566185105f9018269f8826751092d7b17503308017b194864975020a004fbb3005107e402491160019518588219487b8982196052091028c69518607b898219605109bc8218585108b65020a204376728ad821840828708828510510758021408c4300cc3300cc300ae87dc029676500133080164675020a6049fb3005207e90028b7028217408276088275105106ee0033080164675020aa047fb3005207c900288a0282184082851882871051058701957608280c9555ff956618510571018269f8826751092d7b17503308017b194864975020ae0442b30051071e02491160019518588219487b8982196052091028c69518607b898219605109bc8218585108b65020b0047e6628ad82174082760882751051066798673c52075d0297660433080864675020b404efb20051073d023307082836821740827608821708aa76e0fd82174082751051063298673b52078c0297660533080164675020b804bab20051076c02330701017b17609517587b7682196052091228a7fd9517607b7682196051099cfd821858510895fd3300900428ef658218408285188287105105c600957608280c9555ff9566185105b0008269f8826751092d7b17503308017b194864975020bc0451b20051072001491160019518588219487b8982196052091028c69518607b898219605109bc8218585108b65020be048d6528ad82184082871001828908510997008218ae89b6016475979803979705c9870633080864675020c004f5b100520764288b01821840828710018289085109668218ae8954016475979803979705c9870633080864675020c204c5b100520734282901821840828710018289085109368218ae89b3006475979803979705c9870633080864675020c40495b10051078b00491160089518587b868219605209122885fc951860490882196051097afc82185852086dfc286ffc95176049078219605209ccfe2860fc9517607b75821960510955fc82185851084efc3300900428a8648210788215708216689511800032003307201f013308a4002841a2003307201f013308a4002834a2003307201f013308a4002827a2003307201f013308a400281aa2003307db1e01330845280ea2003307201f013308a4002801a2003307201f013308a40028f4a1003307db1e0133084528e8a1003307201f013308a40028dba1003307db1e0133084528cfa1003307201f013308a40028c2a1003307db1e0133084528b6a1003307201f013308a40028a9a1003307db1e01330845289da1003307201f013308a4002890a1003307db1e013308452884a1003307201f013308a4002877a1003307db1e01330845286ba1003307201f013308a400285ea1003307db1e013308452852a1003307201f013308a4002845a10084770151073b8257645951078200829708829518ac758003c975057b1608829720510776829728829638ac768203c976068297407b19107b1852076b2879825749063308017b6808491610645951070a02829a08829818aca849037b19108296105106db01647533080164675020c804b5af00510775034911380195182064577b868219385209c20128ca0133057b160882972052079033068297407b19107b18510714829a48ac7a1d03aa7a0a3307951838280ac8560795189000017b873307013308015020ca045caf005107ae028567ffe67507c867073308fee68707957701330808e4870557059f0233080133060164575020cc042b62510790026478821778877b15187b152064857b18283306017b16309517383309508218105020ce04f0b2009517385020d00471ee8477015107cd00821c18aac6355706b700c86507d857095209ad0078788217207b1718ae76f4019566017b16309517385020d2043bee8477015207ce289400821738510711821740821950ac79f501c9790701821958510911821960821a70ac9ad501c99a0901821a787b1810510a0e821b8000acabb401abab0cc87908951a880028093308951a900001859bffe6b707c897077ba83308fee68709959901951720330a01330b0164c83300d4049511f87b1033007c08285d44821528821810590651ff330710100133086128479f008215385105c500821740821850ac78c50182164851069a0033080164675020d60406ae005107e0014921900001951788007b768219900052098400288d009518387b8682193851090f8218205108095020d8044061821910018295205105d900829728829838ac78540182963051062533080164675020da04aead0051077b01491138019517207b7682193852091228a6009517387b7682193851099b00821820510894005020dc04ea60288a00951790007b7682199000510910821888005108095020de04cd60821558821a08510555821760821870ac78070182166851062733080164675020e0043dad00510724014921900001951788007b7682199000520912281c951790007b7682199000510910821888005108095020e2047660821a08018217308218288219207ba7107ba8087ba9018210a8008215a000821698009511b000320033076b0f013309985d0133082c28cd9d00330771100133084728e39d00330771100133084728d79d00330771100133084728cb9d003307201f013308a40028be9d003306016467645882191828847e330771100133084728a59d00330715160133084728999d003307711001330847288d9d00330715160133084728819d00330771100133084728759d00330715160133084728699d003307151601330847285d9d003307201f013308a40028509d003307201f013308a40028439d003307201f013308a40028369d003307201f013308a40028299d009511c07b10387b15307b1628827510570566647998572052076b8297087b17089597187b17107a151c95171c64867b195020e4045e49646782180864595020e6043f7682178177387a172095182033090464675020e804297633092064678218105020ea041b76821038821530821628951140320033072011013308a20028a79c003307c70101330a005201330b2052013308169519272807a3009511887b10707b15687b16608279827508827b108277188286828c087b17287b197b1b107b1608aa75b100828a10845707510a19015207680184a7077b1a1852076a0197c70397c805c97808c8a808958610140700000000000000807b1720017b1c3882587b15308255089517405020ec04c250821740821820aa874f01821950821848ac59135705b700c85809d8890a6459520aab00821c38330a18d6ac0a821518520a690197ca0397cb05c9ab0a570a5c01c85a0ad85a0a520a52017b67f07b68f87b6995cc01821530955510956618821728ab758b8217087b7c82161051063098673c5207ec0097660433080833050864675020ee0475aa005107e2007b15409517587b7682194052091028229517407b7682194051091882185851081282107082156882166095117828a95d8210708215688216609511783200330710100133086128449b0052075364c682588255089517405020f004d94f82174014080000000000000080aa875f821750ac57145705d200821748c87505d875075207c500330718d6760752078200976703976805c978085908960028713307e9140133086528e59a0064c682588255089517405020f2047d4f82174014080000000000000080ab872533074060012828a1003307db1e0133084528b09a003307201f013308a40028a39a00821750ac5712570548821748c87505d8750752073c330718d6760782191851070f3307b4150133086128769a00976703976805c978075707ebc89707d897075207e233074e150133086628559a00330710100133086128499a003307101001330861283d9a009511907b10687b15607b16588289187b18188288087b1910aa98a4007b178487075207d600958520140900000000000000807b190801825ce08258f8825be8955a98008217187b1a287b7a087b1830aa980f82570882587b17407b183801510c2b7b1b2033080164c664c75020f404aea80051077849115001951748821b207b7682195052091028199517507b7c82195051090f8218485108095020f604eb5b821a30821908ab9a259555b800821710821828ab7883649a821728203308ff140a0000000000000080281182194082183882177b79107b7808017b7a82106882156082165895117032003307201f013308a400284699003307e91401330865283a990082152814070000000000000080ab75087b67289f008217385707a7008218307b187b18187b17209517185020fa04b34383775207269788209889209517289518185020fc044571ff82172814080000000000000080ab8734140700000000000000807b6751053633080133060164575020fe04a7a7005107567b16289517407b75821928520921282a8218388219307b68107b69087b675205d09517287b7582192851090f82184051080950200005d75a821058821550821648951160320033075c16013308a200286e98003307201f013308a4002861980082152814070000000000000080ab75073307287482173857077d8218307b187b18187b1720481128951718951828330904502004050871647651072651052c330801645750200605f9a600510756491128019517407b7582192852091928228117287b17085205da9517287b7582192851090f82184051080950200805305a85670182180801821058821550821648951160320033075c16013308a20028c097003307201f013308a40028b39700951168ff7b1090007b1588007b16800082ab1098bc3b520ce10082ac0884ca07520ad70064867b1997b804c8c808acc8d60064754911204911407b1c607b18683309005e0195170895182033000a05951150ff7b10a8007b15a0007b1698007b19186485647664873300c604289be5821b189757209877209768208215109888208219979920989920645a0a0528774b8477ff00330807ab8779821608510624330801646750200c05eaa50051078000491178019517707b7682197852091028269517787b7682197851091c8218705108168210900082158800821680009511980028195982109000821588008216800095119800320033072011013308a20028af9600330710100133086128a3960078177833079b1301330a185e01330b385e0133084895197828009d003307201f013308a400287a96009511987b10607b15587b16505108c20064767b18109787209877207b19189798209888207b17207b182895172050200e05f04083777b180852077495172050201005df4083777b1852076495172050201205cf408377520756978820988920951730951820502014051e79ff82153014070000000000000080aa75338217408218387b67107b6808821818502016056f577b658217087a671882177a671c8210608215588216509511683200821818502018054b573307681401330a505e01330b705e0133082b95194f28259c0033075c16013308a200289f95009511987b10607b15587b16505108c20064767b18109787209877207b19189798209888207b17207b182895172050201a05154083777b180852077495172050201c05044083777b1852076495172050201e05f43f8377520756978820988920951730951820502020055c75ff82153014070000000000000080aa75338217408218387b67107b68088218185020220594567b658217087a671882177a671c82106082155882165095116832008218185020240570563307681401330a505e01330b705e0133082b95194f284a9b0033075c16013308a20028c4940051070b33050182180828348217107b1746111f95181f3309013305016467502028057e6d51070882180828137c171f330582180882197b8908788710017b8582103082152882162095113832009511d07b10287b15207b16188276641750202c058687005616dfa1027b161033059060016417951810330990600150202e0555a1009567017b1710641795181064595020300541a1009567027b171064179518106459502032052da1009567037b1710641795181064595020340519a1009567047b1710641795181064595020360505a1009567057b17106417951810645950203805f1a0009567067b17106417951810645950203a05dda0009567077b17106417951810645950203c05c9a0009567087b17106417951810645950203e05b5a0009567097b17106417951810645950204005a1a00095670a7b171064179518106459502042058da00095670b7b1710641795181064595020440579a00095670c7b1710641795181064595020460565a00095670d7b1710641795181064595020480551a00095670e7b17106417951810645950204a053da00095670f7b17106417951810645950204c0529a0009567107b17106417951810645950204e0515a0009567117b1710641795181064595020500501a0009567127b17106417951810645950205205ed9f009567137b17106417951810645950205405d99f009567147b17106417951810645950205605c59f009567157b17106417951810645950205805b19f009567167b17106417951810645950205a059d9f009567177b17106417951810645950205c05899f009567187b17106417951810645950205e05759f009567197b17106417951810645950206005619f0095671a7b171064179518106459502062054d9f0095671b7b17106417951810645950206405399f0095671c7b17106417951810645950206605259f0095671d7b17106417951810645950206805119f0095671e7b17106417951810645950206a05fd9e0095661f7b16106417951810645950206c05e99e00641750206e0529a00082102882152082161895113032003307b4150133086128b391009511c87b10307b152882756417330901330a50207205c783007b15189555087b15203305a85e0164179518183309a85e0150207405a49c006417951820645950207605969c00641750207805e79d0082103082152895113832009511c87b10307b152882756417330901330a50207c056d83007b15189555087b15203309c85e01641795181850207e054f9c003309a85e016417951820502080053e9c006417502082058f9d0082103082152895113832009511c87b1030827c648795c8087b182833080060017b18209518287b1818491110083308f117017b18083302e05f013308ec1701330a831701330905330b047b1250208605587e82103095113832009511e87b1010827964877b19083302c05f0133088b1701330ae91701330904330b03951c087b1250208a05807d82101095111832009511e87b1010827964877b190833022060013308f91701330aff1701330906330b05951c087b1250208e054b7d82101095111832008279829a140b00000000000000806487abba0e330887170133090428027d9511f07b10087b1933087f1701330b005f01330904641a502092057d820082100895111032008089348277849a10520a2c7484992052098b7428318b0082777c779777033309301801c879098299330a586001c8a707827a648764a828a37c9511c07b10387b15307b16288277827582760833097717016417330a0450209a05d48100ac565584570752075bc956061407f9ffffffffffff7fae764b9866031407abaaaaaaaaaaaaaaca76077b15187b17203309205f01641795181850209c05909a0050209e05e39b008210388215308216289511403200330715160133084728468f0033075c16013308a20028398f0033090264873308e7140128ca798089348277849a10520a88728499205209e772288687008089348277849a10520ad37084992052093271283f84008089348277849a10520a267384992052098573282b8a009511f07b10088279829a140b00000000000000806487abba1c7b193308db1701330b605f01330903641a3300aa05282381007b193308d91701330b405f01330902641a5020aa050e81008210089511103200808934849a10520abf7284992052091e7328c489009511d07b10287b15207b1618647564175020b0058f81005615dfa1027b15103306906001641795181033099060015020b2055e9b009557017b1710641795181064695020b4054a9b009557027b1710641795181064695020b605369b009557037b1710641795181064695020b805229b009557047b1710641795181064695020ba050e9b009557057b1710641795181064695020bc05fa9a009557067b1710641795181064695020be05e69a009557077b1710641795181064695020c005d29a009557087b1710641795181064695020c205be9a009557097b1710641795181064695020c405aa9a0095570a7b1710641795181064695020c605969a0095570b7b1710641795181064695020c805829a0095570c7b1710641795181064695020ca056e9a0095570d7b1710641795181064695020cc055a9a0095570e7b1710641795181064695020ce05469a0095570f7b1710641795181064695020d005329a009557107b1710641795181064695020d2051e9a009557117b1710641795181064695020d4050a9a009557127b1710641795181064695020d605f699009557137b1710641795181064695020d805e299009557147b1710641795181064695020da05ce99009557157b1710641795181064695020dc05ba99009557167b1710641795181064695020de05a699009557177b1710641795181064695020e0059299009557187b1710641795181064695020e2057e99009557197b1710641795181064695020e4056a990095571a7b1710641795181064695020e60556990095571b7b1710641795181064695020e80542990095571c7b1710641795181064695020ea052e990095571d7b1710641795181064695020ec051a990095571e7b1710641795181064695020ee0506990095551f7b1510641795181064695020f005f2980064175020f205329a0082102882152082161895113032003307b4150133086128bc8b0000330905648733087217012886787c779777033309301801c879098299330a586001c8a707827a648764a828667851070f14070100000000000080285d7c181407000000000000008051084f330901ab983e64675020fc050b3683775207d5978820988920641764685020fe05fa69ff821714080000000000000080aa87b58218108219087b58107b5908280d14070100000000000080017b5782102882152082161895113032009511c87b10307b15287b16206476827a140900000000000000806487ab9a1346111695181633090133000406283b64461117019518173309017b1708502000062b648265105705399857205207408267087b177a15189517188216086468502002061b3764678218645950200406fd63821030821528821620951138320033072011013308a20028898a003307c70101330a005201330b20520133081695191f28e990005207477c17510715330801ab873c14070000000000000080283a646750200806f73483775207239788209889206417646850200a06f178ff821714080000000000000080ab871e14070100000000000080017b5782102882152082161895113032008218108219087b58107b590828e49511e07b10187b151064756417330a0333000e069511e87b10107b15087b166485828b2882882082bb18647664873308de170133090333001a0a320b33087b1701330b805f016417330904645a502010069f92005020120657940082101882151095112032009511e87b101064827b17083307a05f013308e11701330ae71701330906330b02951c087b17642750201606687682101095111832009511b87b10407b15387b1630827a087b17827818aca85b01aaa8d40014092d64210b59c84216c9a808988803d6980898880195a620140700000000000000807b17082811821818019588ff9566b80051089d007b18188265e08267e85105277b1710330801645750201806da97005107e700491128019518207b8582192852091028199518287b8582192851090f82182051080950201a06194b8265f8821818821708aa75a582675105287b1710330801645750201c068e97005107a800491128019518207b858219285209122877ff9518287b8582192851096cff821820510865ff50201e06ca4a285bff8218828710828551073414084316b290852c6401ae877b9676b800330808646750202006359700510774491128089517207b768219285209102822951728490782192851091882182051081282104082153882163095114828694a82104082153882163095114832003307201f013308a400280388003307201f013308a40028f68700330715160133084728ea87003307db1e0133084528de87003307201f013308a40028d187009511c07b10387b15307b16288275087b17827718ac57ee00aa576fc957071408abaaaaaaaaaaaaaad68706986604955508280b9566ff95551851064e8259f8825751092d7b17103308017b19086497502022065e960051079f00491120019518188219087b8982192052091028c79518207b898219205109bd8218185108b7502024069a4928ae821882871082855107391408abaaaaaaaaaaaa0aae8773977803977705c98706330808646750202606019600510767491120089517187b768219205209102822951720490782192051091882181851081282103882153082162895114028354982103882153082162895114032003307201f013308a40028cf8600330715160133084728c386003307db1e0133084528b786003307201f013308a40028aa86009511d87b10207b15187b1610827808827918ac8960827610827551062b98673b52075e9766053308016467502028065b95005107584911080164177b7682190852091028219517087b76821908510917821851081282102082151882161095112828914882102082151882161095112832003307151601330847282c86003307db1e01330845282086003307201f013308a400281386009511d87b10207b15187b1610827808827918ac89688278108275510833140787f21aca6b28af01ae785f96869800330808646750202a06bc94005107584911080864177b7682190852091028219517084907821908510917821851081282102082151882161095112828f24782102082151882161095112832003307151601330847288d85003307db1e01330845288185003307201f013308a40028748500951178ff7b1080007b15787b16708279087b17827718ac974005aa97ea03140813da4b682fa1bd84330ac99707987703ca87077b17201408ffffffffffffff7f7b1838140800000000000000807b18303308e412017b1818140700000000000000807b17101407abaaaaaaaaaaaa0a7b17087b1928281850202c064547821a4095aa01821928821720aa7a7b037b1a4096a7d800c897077b17488276821738c86707887817821730d367079387561716d0977702821818c887078177c887073207821710aa76b982174882750851061d033308016467502030067a93005207110128b0048217488275088276105105e70298573c52073d049755043308086457502034065093005207b5022819048217488275088276105105bd0298573b5207c8039755053308016457502038062693005207920128a4038217488275188276105105f601956608280c9555ff9566185105e0018269f8826751092d7b17583308017b1950649750203c06e9920051071f03491168019518608219507b8982196852091028c69518687b898219685109bc8218605108b650203e06254628ad821848828708828610510729021408c4300cc3300cc300ae871703967550013308016457502042068d92005207f90028f202821748827608827510510610023308016467502046066d92005107c902491168019517607b768219685209fc01287ffe82174882751882761051055d01956608280c9555ff956618510547018269f8826751092d7b17583308017b1950649750204a0620920051074902491168019518608219507b8982196852091028c69518687b898219685109bc8218605108b650204c065c4528ad8217488275088276105105630198573c52078702975504330808645750205006cc910052073101286302821748827508821710aa75e2fd82174882761051052f0198573b5207da029755053308016457502054069891005107bb0233070128fa008217488275188276105105c400956608280c9555ff9566185105ae008269f8826751092d7b17583308017b195064975020580658910051077401491168019518608219507b8982196852091028c69518687b898219685109bc8218605108b650205a06944428ad8217488276100182770851079700821808ae870902977803977705c98705330808645750205c06fd900052076228e00182174882761001827708510767821808ae87a801977803977705c98705330808645750205e06ce9000520733287f0182174882761001827708510738821808ae870801977803977705c987053308086457502060069f90005107e200330708017b17689517607b7582196852091528affc3305019517687b758219685109a1fc821860520894fc2896fc9517687b7682196851098bfc821860510884fc33002c0628bb4382188287108285510736140885f612da4b682f01ae874a019676d8003308086467502062062b900051074201491168089517607b768219685209102824951768490782196851091a8218605108148210800082157882167095118800285c43821080008215788216709511880032003307201f013308a40028f480003307201f013308a40028e780003307201f013308a40028da80003307201f013308a40028cd80003307db1e0133084528c180003307201f013308a40028b480003307201f013308a40028a780003307db1e01330845289b80003307201f013308a400288e80003307db1e01330845288280003307201f013308a400287580003307db1e01330845286980003307201f013308a400285c80003307db1e01330845285080003307201f013308a400284380003307db1e01330845283780003307201f013308a400282a80003307db1e01330845281e80003307151601330847281280003307db1e01330845280680003307201f013308a40028f97f3307201f013308a40028ed7f3307db1e0133084528e27f3307201f013308a40028d67f827728b94d827728b44d8277282a5282772825527c77977903330a501a01977702330b881a01c8b7078177c8a9098299c87b0a648764a828756c510711140701000000000000807b57284d7c1708510756140601000000000000807817079517077b170849111001461127951708951827330901502070063b585207197c17279578ff330905ae980d9566ff7b5678570828057b56018210388215308216289511403200646750207206d429837752078f978820988920951708646850207406c25dff82170814080000000000000080aa876dff8218188219107b58107b59087b5728b4821b28570b73821a2033081b1a0195173033090b9516307b1a502078063737821830511802607b18108215188217387b170851051c330801330601645750207a06928d0051075d7b1630951640017b6582193051090f82184051080950207c06df4082171082180882105882155082164895116032003307c918013308a20028707e7c17387817403307131a01330a686101330b88610133080895194028cb84003307201f013308a40028457e9511b07b10487b15407b163864867b171095870c7b170833058c0033070133080150207e06018d0051077b570584003308017b18645750208006ea3f5107768168087b15187b17204911287a183095171895183033090450208206425782677b1730951718951830330908502084062e5795171833098000821808502086061e57821728821820821918821a107ba7107ba8087ba982104882154082163895115032003307201f013308a40028967d490101330950610182176458285b5e83775207387b1848646750208a0621288377520728978820988920951770646850208c060f5cff821870140700000000000000807b1840ab7822140700000000000000807b57018210d8018215d0018216c8019511e00132008217787b1738821780007b17304921880049218000491178491170951870330920646750208e06e45552071f028217880082188000821978821a707b17687b18607b19587b1a504921c0014921b8014921b0014921a8019518a801330920646750209006a7555207e2018217c0018218b8018219b001821aa8017b1728017b1820017b1918017b1a10014921c0014921b8014921b0014921a8019518a801330920646750209206645552079f018217c0018218b8018219b001821aa8017b1748017b1840017b1938017b1a30014921c0014921b8014921b0014921a8019518a801330920646750209406215552075c018217c0018218b8018219b001821aa8017b1768017b1860017b1958017b1a50014921c0014921b8014921b0014921a8019518a801330920646750209606de54520719018217c0018218b8018219b001821aa8017b1788017b1880017b1978017b1a70014821a8019518a801330904646750209806a7545207e2008117a8017b170895179001646833009a069511e87b10107b15087b16648664756487330078042842268217900114080000000000000080aa87a7008218a00182179001821998017b18b8017b17a80198873a7b19b0015207e00151084e7b18978705c87908ac98dd01957be0140a000000000000008064977b19187b1a1001510b4b9578207b18203309207b1b2850209c06778d00821b28821918837895bbe08217205708dc28104911109517a80133009e06287a219517a80150209e0673213308330901821a102809821aa80182180114070000000000000080ab7a3c140700000000000000807b578216405106d10033080133050164675020a006a8890051072a017b15709517a8017b768219705209ba002870fd951788007b18951810017b19183309207b1a105020a206648d009517a800951830013309205020a406528d009517c800951850013309205020a606408d009517e800951870013309205020a8062e8d008217107b17708217187b177882177b1780008217087a1708014921c0014921b8014921b0014921a8019518a80133092064675020aa06045351073c140700000000000000807b579517705020ac067c20821640520637ff9517707b768219705109befc8218a8015108b6fc5020ae063b3c28acfc9557d8009518a8013309205020b006a88c009557b8009518503309205020b206978c009557189518703309a0005020b406868c008217407b578217387b57088217307b57108217487a57f800285dfc3307201f013308a400288e793307c918013308a2002882793307681801330861287779951120ff7b10d8007b15d0007b16c800648664754921b8004921b0004921a8004921a0009518a00033092064675020b60623525207d1008217b8008218b0008219a800821aa0007b17387b18307b19287b1a204921b8004921b0004921a8004921a0009518a00033092064675020b806e451520792008217b8008218b0008219a800821aa0007b17587b18507b19487b1a404921b8004921b0004921a8004921a0009518a00033092064675020ba06a5515207538217b8008218b0008219a800821aa0007b17787b18707b19687b1a6064675020bc064923837752072a9788209889209517a00064685020be063657ff8218a000140700000000000000807b1818ab7822140700000000000000807b57018210d8008215d0008216c8009511e00032008217a8007b17108217b0007b17084921b8004921b0004921a8004921a0009518a00033092064675020c006065152074d8217b8008218b0008219a800821aa0007b1798007b1890007b1988007b1a800064673300c2069511d87b10207b15187b161064754611089518083309013306013300a60828b95051075314070000000000000080017b5782161851062033080133050164675020c4069c86005107e9007b15a0009517c00028079517a000017b768219a000510939ff8218c000510831ff5020c606dc392827ff7b189517a00064683300c8069511c07b10387b15307b162864866475461108951808330901646733006e062838508218a0001407000000000000008014090100000000000080aa9875ff8217b0008218a8008219a0007b57287b58207b59189557309518203309205020ca06ec89009557509518403309205020cc06dc89009557709518603309205020ce06cc890095579000951880003309205020d006ba89008217187b578217107b57088217087b571082177b57b000286cfe3307201f013308a40028c3769511a87b10507b15487b16406486647546112095182033090164675020d206814f51072c140718000000000000807b57280f140718000000000000807b570182105082154882164095115832007c1720330817ac78d19777023308a41a01c887078177c887073207645764683300d606951170fe7b1088017b1580017b167801648664754921100149210801492100014921f8009518f800330920646733006c0728fb4e289b140710000000000000807b57288d49113849113049112849112095182033092064675020dc06d44e520756ff9557089518203309205020de06b2880014070e000000000000807b572851ff48112095182033090464675020e206a04e520722ff8118207b1810140718000000000000807b171848112095182033090464675020e406784e5207548117207b170848112095182033090464675020e6065e4e52073a8117207b1795172064683300e8069511e87b10107b15087b1664866475648733007c0428fd1f82172014080000000000000080ab8711058217187b5728bcfe645764683300ec069511b87b10407b15387b1630648a6476140518000000000000804811109518103309047b1a0864a73300480728e24d2882fe645764683300f0069511b87b10407b15387b16306486647547111895181833090264673300680728b54d2855fe95172064683300f4069511e87b10107b15087b1664866475648733008004285c1f82182014070000000000000080aa7817fe821830821928821a207b58187b59107b5a08140709000000000000807b572802fe645764683300f806951148ff7b10b0007b15a8007b16a000648a6476140518000000000000804811209518203309047b1a1864a733003c0728244d28c4fd14070f000000000000807b5728b5fd48112095182033090464675020fe06044d520786fd811720140803000000000000807b587a5708288bfd64576468330002079511987b10607b15587b1650648a6476140518000000000000804911484911404911384911309518303309207b1a0864a733005c0728a84c2848fd64576468330006079511907b10687b15607b1658648a6476140518000000000000804911504911484911404911389518383309207b1a1064a73300540728654c2805fd6457646833000a079511b87b10407b15387b163064866475140711000000000000807b1708481110951810330904646733006207282b4c28cbfc491120951820330908646750200e071a4c52079cfc821720140814000000000000807b587b570828a1fc95172064685020120768cb82182014070000000000000080aa7879fc821830821928821a207b58187b59107b5a08140705000000000000807b572864fc9517206468330016079511c87b10307b15287b162064867b170849111095181033090864673300260528954b821720520717fc8217287c1830140915000000000000807b597b57087858102816fc37070000036e77977720987920951720646850201a07f95bff82162014070000000000000080aa76e0fb8217287b17188217307b171037070000036e7701821a10821918977720987720ac7a1096785001c89808647aac9837027b56087b59107b5a1814070d000000000000807b5728a4fb6457646833001e079511987b10607b15587b1650648a6476140518000000000000804911484911404911384911309518303309207b1a0864a733004e0728c14a2861fb9517206468330022079511e87b10107b15087b166486647564873300740428681c82182014070000000000000080aa7823fb821830821928821a207b58187b59107b5a08140712000000000000807b57280efb646750202607311c83775207e6fa978820988920951720646850202807514dff82182014070000000000000080aa78c3fa821830821928821a207b58187b59107b5a0814070a000000000000807b5728bcfa481120951820330904646750202c070b4a52078dfa8117207b1718491138491130491128491120951820330920646750202e07e749520769fa95570895182033092050203007c58300140708000000000000807b578217187a5728285efa9517206468330034079511d07b10287b15207b1618648664754601641833090164673300060628924982182014090100000000000080aa9818fa821830821928821a207b58187b59107b5a08140716000000000000807b572803fa951720646850203807cac882182014070000000000000080aa78dbf9821830821928821a207b58187b59107b5a08140713000000000000807b5728c6f9140717000000000000807b5728b7f98217308218288219207b57187b58107b59088217189577f37b578217107a57208217087a572482177a57282889f9330710100133086128ff6f5107087b65289c007b151081152049112095182033090882171850203e07be4851070a8217107b6728797b15088217207b17491120951820330908821718502040079a486465520729821620951720330980003308502042070a82009518203309800082171850204407724851070a8217107b57282d95570895182033098000502046074982008217109577ea7b5782177b5788007b5690008217087a579800018210b0008215a8008216a0009511b80032005107077b6528467b1581151049112849112049111849111095181033092082170850204a07044851070982177b67281d95670895181033092050204c07dd810082179577ec7b677a65280182104082153882163095114832005107077b6528557b15821748821840821938821a307b17287b18207b19187b1a10491130951830330908821708502050079f4751070982177b6728208215309567089518103309205020520775810082179577ee7b677b65280182106082155882165095116832005107087b652882007b1508821750821848821940821a387b17307b18287b19207b1a18491138951838330908821510645750205607334751070a8217087b6728488217387b174911389518383309086457502058071347646551070a8217087b57282682163895570895181833092050205a07e680008217089577e97b5782177b57287b56300182106882156082165895117032005107077b6528557b15821748821840821938821a307b17287b18207b19187b1a1049113095183033090882170850205e07a24651070982177b6728208215309567089518103309205020600778800082179577ef7b677b65280182106082155882165095116832005207258117107b174911284911204911184911109518103309206467502064074b4651070d8217089577077b57281d955708951810330920502066072080008217087b5782177a5728018210408215388216309511483200510712140718000000000000807b572884007e17187b17103a07100003977720987920951718646850206a075043ff82161814070000000000000080ab7611140718000000000000807b5728488217207b17088217287b173a0710000301821a821908977720987720ac7a0e977805c89808647aac982d7b56087b59107b5a1814070c000000000000807b578217107957200182104082153882163095114832003307101001330861288a6c5207b700821710018218080182190001821af8007b17707b18687b19607b1a584921f8009518f800330908646750206e073645520784008217f8007b17184921f8009518f80033090864675020700718455207668217f8007b17104921f8009518f800330908646750207207fb445207498217f8007b17084921f8009518f800330908646750207407de4452072c8217f8007b179517f800330980003308502076074c7e9518f80033098000646750207807b544510730140718000000000000807b57280f140718000000000000807b57018210880182158001821678019511900132009517789518f8003309800050207a07657e9517f800646833007c079511d07b10287b15207b1618648664754601641833090164673300fa052847448218f80014090100000000000080aa989882170801821800018219f8007b17307b18287b192095173895185833092050207e07067e9557589518783309800050208007f67d8217208218288219307b577b58087b5910821738821840821948821a507b57187b58207b59287b5a308217187b57388217107b57408217087b574882177b5750282eff46111095181033090164a764a5502084072a449568183309206457502086071c448267387b171095181033090864575020880708448267407b1710951810330908645750208a07f4438267487b1710951810330908645750208c07e0438267507b1710951810330908645750208e07cc4395685833098000645750209007bd4364676458502092074cdf28d20433071028bb049566084611100e95181033090164a764a550209807934333092064576468821030821528821620951138287a434611100b95181033090164a764a550209c0769438167207a1710951810330904645750209e0755438167247a171095181033090464575020a00741438167287a17109518103309047b1564575020a2072b4382681898873b520793048267108479075209890482195020a407473dff282d043307042854034611100c95181033090164a764a55020aa07f1427f67207917109518103309027b1564575020ac07db42826a1898a83a52082b0482681097a505c85809ac8972048216510ae1039589207b190833092064675020ae07ad429555e08218085205e928c3034611100995181033090164a564a75020b2078d4282681898873b520701048267108479075209f703645982103082152882162095113828003cff4611100295181033090164a764a55020b6075342816798007a171095181033090464575020b8073e42826788007b171095181033090864575020ba072942826790007b171095181033090864575020bc07144295680833098000645733000a0828014233070f280f034611100395181033090164a764a55020c207ea418167087a1710951810330904645733000a0828d241330707284f014611100195181033090164a764a55020c807bb4195680833092064575020ca07ad418267287b171095181033090864575020cc0799418267302838019567087b17084611101195181033090164a764a55020d00779418167287a171095181033090464575020d2076541330920645782180833000a082853414611101495181033090164a764a55020d607424182670828e1004611100595181033090164a564a75020da07284182681814075555555555555505ac87a102826610846707520797029887205107f3013307681201330ab05d01330bd05d0133081695191f280d6e4611101595181033090164a764a55020de07da408267087b171095181033090864575020e007c6407c6710781710951810330901645733000a0828ae409566084611100d95181033090164a764a55020e4079a4064676458821030821528821620951138282b113307060178171095181033090164a764a55020e807704095680833092064575020ea076240826728017b1710951810330908645733000a082849404611101295181033090164a564a75020ee073840826918570997019897205207e3018267107b17087a1910951710645864965020f00726136457821808646933000a082803404611100a95181033090164a564a75020f407f23f82681814075555555555555505ac87770182661084670752076d019887205107bd003307681201330ab05d01330bd05d0133081695191f28d76c3307080178171095181033090164a764a55020f807a13f8167287a171095181033090464575020fa078d3f956808330920645733000a08287b3f4611101695181033090164a77b1a5020fe076a3f82670897553fab57910046111001951810330901821733000a08284a3f4611101395181033090164a564a750200208393f82681814075555555555555505ac87ca008266108467075207c0009887205207e8007a18109517107b1808645850200408181264678218086459502006086a56ff28173307170178171095181033090164a750200a08e13e82103082152882162095113832009566084611109518103309018215645750200c08bd3e6467645882103082152882162095113828fcb93307f521013308a2002844653307251c013308a2002838653307251c013308a200282c653307251c013308a2002820653307251c013308a2002814653307251c013308a2002808653307251c013308a20028fc64330794210133086128f1643307c70101330a005201330b20520133081695191f28526b3307681201330ab05d01330bd05d0133081695191f283a6b951118ff7b10e00082798297140affffffffffffff7fc8a70b88bb17140a0000000000000080d3a70793b7977702330a641b01c8a7078177c87a0a6487320a959818959a38959b40959c489592509593587b19687b187033084862017b18787b1a800033086862017b1888007b1b90007b1898007b1ca0007b18a8007b12b0007b18b8007b13c00033088862017b18c8009518687b18d0003308a862017b18d8003302073308c21d01330ad8610133090d330b07951c707b1250201208be548210e0009511e800320033088d1e013309058210e0009511e80028be509599087b1970330268640133087e1e01330aa61d01330905330b04281b04959c20959824959a289599087b19703309e863017b19609519707b1958491150083309591d017b19483302e862017b12407b1a38491130093309611e017b19287b12207b18184911100633085b1e017b180833084f1e01330a541e01330905330b077b123300220828d452959c289599087b197033084863017b18209518707b1818491110093308401d017b18083302e862013308fe1d01330a031e01330905330b067b123300400828b750959c209599087b197033082864017b18209518707b1818491110053308701e017b1808330208640133086a1e01330aaa1d013309062863029599087b19703302c8630133083e1e01330a441e01330906330b05281903959c980095988800959a90009599087b197033090863017b19609519707b1958491150043309a21d017b194833096862017b19407b1a3849113009330ae71d017b1a287b19207b1818491110063308e11d017b18083302e862013308691d01330ad61d01330908330b0b7b1250202208ce518210e0009511e80032003308831e0133090a8210e0009511e800280f4f9599087b197033022863013308f01d01330af71d01330907330b07286c02959c089598287b18703308c862017b18209518707b1818491110033308281e017b1808330288630133082b1e01330aa61d01330906286001959c089598289599307b19703309c862017b19409519707b19384911300c330a8d1d017b1a28330a6862017b1a207b18184911100c3308811d017b180833024862013308cf1d01330a401d01330907330b097b1250202c08f74f8210e0009511e8003200959c289599087b197033086864017b18209518707b1818491110083308791d017b18083302e862013308921e01330a991e01330907330b0a7b123300400828d74e9599087b19703302c862013308611d01330aa31e01330908330b052871019599087b197033026863013308091e01330a141e0133090b330b0d2853019599087b19703308a81e01330ba86401330913951a70330044082850539599087b197033024864013308751e01330aae1d01330909330b04281801959c089598287b18703308c862017b18209518707b1818491110033308281e017b180833028863013308211e01330aa61d01330907330b047b1233004008281f4e330b047b123300400828134e9599087b197033028864013308711d01330ab21d01330908330b0428ad009599087b197033026863013308491e01330ab21d01330906330b04288f00959c289599087b19703308a863017b18209518707b1818491110043308a61d017b18083302e862013308311e01330a371e01330906330b077b12502040089a4d8210e0009511e80032009599087b19703308bb1e01330bc86401330917951a705020440846528210e0009511e80032009599087b197033026863013308511d01330ab21d01330908330b0401951c707b1250204808a04c8210e0009511e80032003308d21e013309098210e0009511e80028604c808934849a10520a14428499205209764228e455808934849a10520ad2428499205209314328d057808934849a10520a8a438499205209e943288f5a330905648733083b1d0128174c5107d3009665d8007b15089555045706d1003308017b1864575020560800215107c3007b15207b17289867204911305207bf007a163c95173c95182050205808680b821718821808c87808ac78bb005106629665d800019576d80095182033005a089511c87b10307b15287b1620647682773305ff1409ffffffffffffff7fc8970a88aa1714090000000000000080d3970793a79777023309041b01c897078177c89707648a3207955528ff64675205a9821730821828821920821a107ba7107ba8087ba982105082154882164095115832003307201f013308a400285f5e4901013309e864018217645828243f3307c71c01330aa06101330bc0610133081695193c28b1643307c40001330861282d5e9511a07b10587b15507b164882ab08649c64856476510b2682a782a8107b1b087b1510ab5b4fac8c8300510816645964ca7b1c50205c0868202816510c182805510c1364c764587b1c50205e08bf1f821c2805645701887801db77057b65087b6c107b688210588215508216489511603200491118330b286501951808951910951a1833079511e864ac7b18087b19103309885e01951808951a107b1b330b885e013300f40528eb5d33070065017b17184911200149113849112808491130330810650195171828005d9511f07b10087b15827c6485330901330a08330b1064c8502060084a0214090100000000000080ab970d82100882159511103200645928fe3d14090100000000000080ab970d82100882159511103200645928e23d14090100000000000080ab970d82100882159511103200645928c63d14090100000000000080ab970d82100882159511103200645928aa3d9511f07b10087b15827c6485330901330a01330b2064c850206808bd0114090100000000000080ab970d82100882159511103200645928713d14090100000000000080ab970d82100882159511103200645928553d14090100000000000080ab970d82100882159511103200645928393d9511f07b10087b15827c6485330901330a08330bd80064c850206e084b0114090100000000000080ab970d82100882159511103200645928ff3c9511807b10787b15707b1668827564b6330bffdb650bc98b0bae9bc2007b1c5106cb007b15207b1728c889097b1930ac89bb00c86a079577ff9aa5d2750564577b1a1864a850207008c36a5107a600821930d695083307821a205208ca00ca950914080000000000000080821b18c9b80cac9c9700821528510a3b7b1910d6a60752078c008257087b1708ca6a06646764b864b550207208746a51077e9517608218087b1850645b7b155882152882191028083306951758017b76951738951a5064b8502074083bfd8217385207318217407b57088217307b570182107882157082166895118000320033078219280e3c3307201f013308a400282e5b821740821848821928f73b821928f23b3307db1e0133084528135b3307201f013308a40028075b821928d63b9511887b10707b15687b166051090801510b09c89805ae8516330701645882107082156882166095117832007b170882777b17285707f0007b1b20c8ab0b95bbff9aa6d26b077b17187b1a1064a85020760891695107d8003307821228972901821620886a0104330c0493ac01956aff330b08dbac0be45b0ae49a0a821818d6a809caa80552099714090000000000000080821810c98909ac59847b1a510238d62607520795008217088277087b1718ca62077b17286486502078082769510785009517588218187b18487b1650646882192828083309951750017b79951730951a48645950207a08f0fb82173051070c8217388215402825ff8217388218087b870882177b8714070100000000000080280bff3307fc2001330940650133082028bc59330758650128536a3307201f013308a40028ca593307db1e0133084528bf593307201f013308a40028b35914090100000000000080ab970a821095110832003309706501286b3a9511d87b10207b15187b1610827682750851062a98673b520751976605330801646750207e08526851074b4911080164177b7682190852091028219517087b76821908510917821851081282102082151882161095112828891b82102082151882161095112832003307db1e013308452824593307201f013308a400281859827728ca0182772889009511887b10706482957c80009578a0009579c0007b176833070066017b17609517687b1758491150083307bc22017b17483307e065017b17407b1938491130033307b922017b17283307c065017b17207b18184911100c3307ad22017b17083307a065013308972201330aa6220133090f330b077b176427502086080b488210709511783200951170ff7b1088007b1580007b16788279105109f5007b1998993a5209150182750833076a097b15687b17107b177033072066017b17389517687b17488287208288284911400149115849112801491150019519387b17207b181850208a08373d520792003a071000030183785318026c97772098772095582082169569ff9575ff33073066017b1708017b193051099b007b18688217107b17708217087b1738491140014911589517687b17484911280149115001951938821720648682181850208c08d13c52072c9568209555ff8219309599ff5205b5821718827a183308c72201330901821720b4a0028e087b1728018217288210880082158000821678951190003200828928828720829c183308c42201330902821088008215800082167895119000320c82176478287d573307f521013308a200284c57951170ff7b1088007b1580007b1678827a10510a0001140962188661188661007b1aae9a19018275087b1568330784087b17107b177033072066017b17389517687b17488287208288284911400149115849112801491150019519387b17207b181850209208f43b5207960037070000036e770183785318026e9777209877209558500182169569ff9575ff33073066017b1708017b193051099c007b18688217107b17708217087b1738491140014911589517687b174849112801491150019519388217206486821818502094088b3b52072d956850019555ff8219309599ff5205b4821718827a183308c72201330901821720b4a00296087b1728018217288210880082158000821678951190003200828928828720829c183308c42201330902821088008215800082167895119000320c821764782836563307f521013308a2002805569511d87b10207b15187b1610827910140a6218866118866100aea97982760896975001c86707ac677851095b648596975001017b1708956750017b1795688000330920645750209808142f9568a000330920645750209a08052f9568c00033099000645750209c08f52e330980006457646850209e08e72e8217089577b0fe82165207b282102082151882161095112832003307f521013308a2002867553307942101330861285c559511d87b10207b15187b161064754611089518083309013306015020a0081b2e5207b1007c18085108a4005228ff002449110895180833090864575020a208fa2d52076b8218089887208e762885006c885708073306284184874033060151073984872033060251073084871033060351072784870833060451071e84870433060551071584870233060651070c8487013306075207557b18491108951808645764695020a408922d51070833060128228567079b77ff8219088218e07807976603cf6708c89808ac98199887208e760164678210208215188216109511283200330770660128b264330758660128d75a51070628ae007c18085108245228ff002649110895180833090864575020a808282d52076d821808330628870064862882006c885708073306284184874033060151073984872033060251073084871033060351072784870833060451071e84870433060551071584870233060651070c8487013306075207527b18491108951808645764695020aa08be2c510708330601281f8567079b77ff8219088218e07807976603cf6708c89808ac981633060164678210208215188216109511283200330770660128e163330758660128065a9511a87b10507b15487b16408075648c510524532580003398570e330201510731330398571551073198571c8872019a2804282946113d95183d33090164c73300b00828ab2c33033308330201280e3303013308012806330802019a8a0884a90184ab02959901510b0d84ab0e979902511b022a84ab04520b105325800017330788650128906384aa0c979904511a040d561901ed0097990801330a0001330b8000c99a097b1c28acb566848701d2370b3306017b1b20520b20848b02937a01510b0f848606330b0297aa10aab60b5202b70097aa20017b177b12107b18087b1518cba507c8970778173e95183e33090164c75020ac08fb2b3306000182172051072c3309018217288215182855c8590978193f95183f33090133060164c75020ae08cf2b82172833092837821908849702821893860152070b821728821b102812849a06976610821728821b10511a020f520b3c976620821518280682151801cd65087b18309518305020b008842b82105082154882164095115832003307886601287262330788650128896233078865012881625107693307657814070000000000000080aa78649555ff9a87d25707c867096599510938c88707ac876251082f8218e66809821808520806520945d8780cc9870ac9780bdbca0bac9b36821020821518821610951128287864330782102082151882161095112832003307ee24013308a400288f513307a0660128236233077f23013308a600287b5133072524013308612870515207b1007c18085108a4005228ff002449110895180833090864575020b6082e2a52076b8218089887208e762885006c885708073306284184874033060151073984872033060251073084871033060351072784870833060451071e84870433060551071584870233060651070c8487013306075207557b18491108951808645764695020b808c62951070833060128228567079b77ff8219088218e07807976603cf6708c89808ac98199887208e7601646782102082151882161095112832003307d0660128e6603307b86601280b579511d87b10207b15187b161064754611089518083309013306015020ba085e2951070628ae007c18085108245228ff002649110895180833090864575020bc083c2952076d821808330628870064862882006c885708073306284184874033060151073984872033060251073084871033060351072784870833060451071e84870433060551071584870233060651070c8487013306075207527b18491108951808645764695020be08d228510708330601281f8567079b77ff8219088218e07807976603cf6708c89808ac9816330601646782102082151882161095112832003307d0660128f55f3307b86601281a569511a87b10507b15487b16407b170882781014076218866118866100ae7802017b1896855001955603951728646833095020c0087512821728821a305207ee00ac6af7008217388218088286087b1a107b1718c85608491120ac68e9008218510878956750017b1708956880009517103309205020c2088f289568a0009517103309205020c4087f289568c000951710330990005020c6086e289517103309800064685020c8085f289555b0fe8216085205b28217205707a3008217188215100a070164596475520919951728490782182852082d283964a50a076459647501330801649664975020ca08a45d510760491128019517107b7682182851081282171051070c5020cc08865d51075a645782105082154882164095115832003307c72c013308a20028844e8218383309e8660164a7284b2f3307862401330868286c4e330725240133086128614e33071c2f013308a40028554e33079225013308a20028494e3307232c013308a400283d4e9511a07b10587b15507b16487b171082791098973a7b18085207cb007b19979505955603951730646833095020ce0809118218308217385208b800ac67bf008218408219108299087b17187b1820c8590a491128ac9ab100649b821951092c64b8019586209517183309205020d00826279555e064685205ec8217285707940082161882182028056476018217087e770a06016475646751062233080164765020d2087b5c510777491130019518187b86821830520810281c9518307b8782183051081282171851070c5020d408505c510758645782105882155082164895116032003307c72c013308a200284e4d8218403309e8660128172e330786240133086828384d3307252401330861282d4d33079225013308a20028214d33071c2f013308a40028154d3307232c013308a40028094d51070933050128cd00821770821868821960821a587b17507b18487b19407b1a3864675020d80847fc3305015207a7007b182864675020da0835fc520798007b182064675020dc0826fc520789007b181864675020de0817fc52077a7b181064675020e00809fc52076c7b180864673300e2089511d87b10207b15187b161064754611089518083309013306013300b408285125837752073d8216309567087b189518383309205020e4082c5f33058217287b67288217207b67308217187b67388217107b67408217087b674882177a6750018217307b758210880082158000821678951190003200821882170852084c98780354280002518218103309330a081001c8980bac8b29959901460baba9f53309f8efae983b7b577b5808330708107b57108210208215189511283200330725240133086128cf4b821810330900670128982c3307bb2b0133086828b94b33075a2b0133086128ae4b9511b85107159578097b17551807169a87069511482877b4330707951148286eb43307620a64187b18387b174033071867017b1708491110014911289518387b181849112001330828670195170828fa4a9511987b10607b15587b16506486647564173300e8089511d87b10207b1518647533080810641733093300e60828270e82191057098700821708330a083308646b330c0a12015517f71682171082180882197b57107b58087b5928525217ff6a8216140800000000000000807b5851062433080133050164675020ea08c1595107687b15189517487b76821818520810281c9517187b7682181851081282174851070c5020ec08975951074a82106082155882165095116832003307f728013308a60028974a33074067017b17184911200149113849112808491130330850670195171828134a33071c2f013308a400286a4a3307232c013308a400285e4a5517f70b33087b570828179578093309077b17ae981d8d870678570133080101785882105882155082164895116032003307620a64187b18387b174033071867017b1708491110014911289518387b1818491120013308286701951708289b4982109511083200821095110832009511e87b1010491108037b1664768177330902ae97108210108215088216951118320064855217021a390718000301816608be6707ac6743ae571e330728d9390718000301816608be6707ac6735ae571933070128c2390718000301be7608ac682933070428b1390718000301be7608ac682033070328a0330780670128af593307b0670128a7593307986701289f593307c867012897595107199578093309077b17ae98199a8706821048951150320033070782104895115032003307620a64187b18387b174033071867017b1708491110014911289518387b181849112001330828670195170828a1489511b07b10480a0f015107199578093309077b17ae98199a8706821048951150320033070782104895115032003307620a64187b18387b174033071867017b1708491110014911289518387b181849112001330828670195170828444852070b82104895115032009578097b17551807228d87067817083307c42901330ae06701330b00680133084495190828ee4e3307620a64187b18387b174033071867017b1708491110014911289518387b181849112001330828670195170828e2479511b07b10489777209877200a1b015107199578093309077b17ae98199a8706821048951150320033070782104895115032003307620a64187b18387b174033071867017b1708491110014911289518387b1818491120013308286701951708287f479511f87b10649a64893300f208951178ff7b1080007b15787b16706475491110ff3307ff7b191864987b1a2064a9330a330b330c0a0228ea06821095110832009511a07b10587b15507b164864756487649864a964ba0a03015617f60e3308017b587b570828233308ffaa871b9578093309077b17ae98218d87067857083307027b57280549050182105882155082164895116032003307620a64187b18387b174033071867017b1708491110014911289518387b181849112001330828670195170828b94664783309f7330701ac98115218ff163307821048951150320082104895115032009587097b18551707228d78067818083307c42901330ae06701330b186801330844951908284d4d3307620a64187b18387b174033071867017b1708491110014911289518387b18184911200133082867019517082841469511987b10607b1558647564173300f408951160ff7b1098007b1590007b168800491108ff7b1733074c6476951718330901646833000209286309821751071c95180833095064575020f608265982106082155895116832003307082a019511b07b174911081f64177b17403307580a7b17483307684d017b1710491118014911309518407b182049112801951710330830680128aa45987820520811977720d4870782104895115032005317f7249578093309077b17ae98399788089a870006330801d48707821048951150320033074067017b1708491110014911284911180849112033086867019517082851453307620a64187b18387b174033071867017b1708491110014911289518387b181849112001330828670195170828214552070b82104895115032009578097b17551807228d87067817083307272a01330ae06701330b48680133083395190828cb4b3307620a64187b18387b174033071867017b1708491110014911289518387b181849112001330828670195170828bf447a15049517043300fa089511a07b10587b15507b1648647595170833087433093300160928f507821080008215789511880032009511b07b10489777209877200a0b015107199578093309077b17ae98199a8706821048951150320033070782104895115032003307620a64187b18387b174033071867017b1708491110014911289518387b18184911200133082867019517082828445107199578093309077b17ae98199a8706821048951150320033070782104895115032003307620a64187b18387b174033071867017b1708491110014911289518387b181849112001330828670195170828d44351071a9578093309077b1708ae98199a8706821050951158320033070782105095115832003307620a9518087b18407b174833071867017b1710491118014911309518407b1820491128013308286701951710287e4352070b82104895115032009578097b17551807228d87067817083307272a01330ae06701330b60680133083395190828284a3307620a64187b18387b174033071867017b1708491110014911289518387b1818491120013308286701951708281c439511807b10787b15707b16687b17183306f77b180864877b191064983309330a330b0a010164755517f73d95172833090164585020000943068217288218307b185207468216388217088218106469330a645b0a0182171882187b787b76087b751028165215ff30140700000000000000808218187b87018210788215708216689511800032008218383309786801821728bf239557097b1520551707228d78067818283307c42901330ae06701330b906801330844951928283c493307620a9518207b18587b176033071867017b1728491130014911489518587b1838491140013308286701951728282f428217188218207b181052072e0182152882170864580a04015517f765570629017b15707b167895171895187095151833000409951170ff7b1088007b1580007b167864867b173049117049116849116049115895185833092064673300d608280c1b82171851074a8216108218490852065e7b5682181852087728ab005217ffd4008217490782161051068100330801330501646750200609da505107df007b15189517707b7682181852086b2878951820821695670833095050200809a25464683307018216107b875106a8330801330501646750200a099a505107ab007b1518951580007b5682181851083a8217800051073350200c097a5052072a3307232c013308a4002888419517187b7682181851081382177051070d50200e0954505107a3008210980082159000821688009511a00032008218283309a8680182171028212233075528013308a2002841419578097b1780005518073a8d87067817183307c42901330ae06701330bc0680133084495191828914733071c2f013308a400280c4133071c2f013308a4002800413307620a951880007b18707b177833071867017b1718491120014911389518707b1828491130013308286701951718286b403307232c013308a40028c24064765516f744510652951730330901646850201009a8038217308218387b180852077c821a407b1a821710821818821920330b646c0a028217087b5782177b57087b561028205216ff2e140700000000000000807b57280e49053307017b570849151001821080008215788216709511880032009567097b1628551707308d78067818303307c42901330ae06701330bf06801330844951930289f468218403309d8680182170828ec203307620a9518287b18607b176833071867017b1730491138014911509518607b184049114801330828670195173028843f7c77977903330aa82a01977702330be02a01c8b7078177c8a9098299c87b0a648764a8289a2c33090b64873308692d01288d2c82180882171052089e00541773a5008218187b170881577b18104911189555047a173895170895183833090450201809dc18951708330970645850201a09ce18821718570777821710821508330833090a0a01520773510525330801330601645750201c09304e510786007b16089517387b75821808520810281d9517087b7582180851081382173851070d50201e09054e510797008210588215508216489511603200821818330920690128d91f3307bb2b0133086828fa3e3307c72c013308a20028ee3e9578097b175518072e8d87067817083307d62d01330a386901330b58690133083395190828404533071c2f013308a40028bb3e3307620a64187b18387b174033071867017b1708491110014911289518387b181849112001330828670195170828283e3307232c013308a400287f3e9511e87b10107b1508978820988520330804645750202009484d51071c3308046457502022093a4d51072682101082150895111832003307302e01330a706901330b90690133082b95190728aa443307232c013308a40028253e9511e87b10107b15087b166486647550202409f34c5107453307657814070000000000000080aa78409566ff9a87d26707c875056559510914c88707ae871133075a2b0133086128db3d330701821010821508821695111832003307232c013308a40028bf3d330708690128534e9511f87b1064876498502026098b4c51070a821095110832003307232c013308a40028923d9511d87b10207b15187b16107b1a08649564867b176487649850202809564c51073882186459646a821b088210208215188216109511289511d87b10207b15187b161064b67b1a64957b180864b764983300b208281b4c3307232c013308a400282f3d7c77977903330ac82e01977702330b002f01c8b7078177c8a9098299c87b0a648764a828e2299511c07b10387b15307b16287b191064867b1718491108330b013305013307017b152033080150202c09c04b5107f700d66507520773ca650514070000000000000080821820c98707ac575d5105657b16645782182050202e09904b8218105108625107cd003307657b14080000000000000080821a18aa8bd0009ab8821908d29808c858096599510979c88b06acb6c900510b6f64673308645950203009ce4e821a18284a821a18491a0833090128643309821a18491a088217207ba710285451077a3307657714080000000000000080821a18aa877a9a78821908d29808c85809659951091cc88706ac767795a80895a7105106143309821c7b8c7b76281495a80895a710018219207b897b75330901017ba9821038821530821628951140320033071c2f013308a40028da3b3307232c013308a40028ce3b3307232c013308a40028c23b3307a0660128564c3307086901284e4c330725240133086128a73b33075a2b01330861289c3b9511a07b10587b15507b164882ab08649c64856476510b2682a782a8107b1b087b1510ab5b4fac8c8300510816645964ca7b1c50203209d7fd2816510c182805510c1364c764587b1c502034092efd821c2805645701887801db77057b65087b6c107b688210588215508216489511603200491118330bd06901951808951910951a1833079511e864ac7b18087b19103309706a01951808951a107b1b330b706a0133006009285a3b3307a869017b171849112001491138491128084911303308b86901951718286f3a14090100000000000080ab970d821008821595111032006459288a1b14090100000000000080ab970a821095110832003309186a01286e1b9511887b10707b15687b1660330bffaab89c007b171882777b17205707e10064857b1a10c8a9079577ff9a96d2760664677b1928649850203a0941495107c8003307955801821b2097b901d8950adba908330904e4980ad6a608caa60552088f0014090000000000000080821828c98909ac597b7b1a08510b38821910d6b907520790008217188277087b17ca9b06646750203c09e6485107840095175882187b18488218287b1850280c3307283f3306951750017b76951730951a48645950203e093ffe82173051070b821738821540281b8217388218187b87088217087b871407010000000000008001645882107082156882166095117832003307e8690128134a33073431013308a400288a393307c02f01330845287f3933073431013308a400287339951158ff7b10a0007b1598007b169000827c1098cb397b1a107b19087b1820520b9001827508845807520886017b171897c606c86507ac5785017b1c510c9900821720827a10570a8b018277087b172833099555107b1a30280e9599019566c095554051066b825757076f01aba7ee8257f88218287b193864a9502040093a4b821a3082193883775207d28217ae796201330a006a01951740821818502042092402821640821548510634330801646750204409a047510744014921880001951780007b7682198800520920282a8215188216282a33068215182823951788007b76821988005109108218800051080950204609ccfa8215188256100195175833092082181050204809374b8219208297829808829910825a7b17407b18487b1950aba61d645733004a099511f87b108278330908330a4033003809289dfd98673a52076297670657075c825808c88707d8870852085084780752086c821870821968821a607b78307b79287b7a20821858821950821a48821b407b78187b79107b7a087b7b8218087a78389567017b57108210a00082159800821690009511a800320033076b300133086128c4373307d831013308a20028b83733076b300133086128ad37330705300133086628a23782571059070f3307d831013308a2002890373307d831013308a2002884373307cc300133086828793733073431013308a400286d378089348277849a10520a961b8499205209f51b289b328089348277849a10520a801b8499205209df1b2885329511d07b10287b15207b16188276641750205209512a5516e06b7b16103305306a0164179518103309306a015020540922449567087b171064179518106459502056090f449567107b17106417951810645950205809fc439566187b16106417951810645950205a09e943641750205c092a4582102882152082161895113032003307de330133086128b53600009511a07b10587b15507b1648828610ae69c600647598973a5207b2009797065707ab00828a08c8a707d8a70a520a9e00847a07520ae6007b18827838827a30827b287b18408278207b1a387b1b30827a187b1828827810827b087b1a20827a7b18187b1b107b1a085517c0bc009578408599ffc8690997990650206209f246821982989567ffac78a9007b9710821708821810821918821a207b577b58087b59107b5a18821728821830821938821a407b57207b58287b59307b5a3882105882155082164895116032003307a4340133086128de356497646864a99511a07b173307620a7b180864187b18407b17489518087b18507b17583307c06d017b1710491118034911309517407b172049112802951710649828373533073f3401330865288f353307a4340133086128843533070535013309906a0133082c285235951148ff7b10b0007b15a8007b16a0007b19307b17491138491140014911484911684911604911589517504911505617dfe6021409157c4a7fb979379e1407b9e5e41c6d4758bfc88909989a1ed3a909ca790a98ab1b1409eb113113bb49d094d3ba0a140b2af894fe72f36e3cca9a0a98a51fd3a505c88b0b98ba1ed3ba0aca7a0a98ab1bd3ba0a140b3f74df7d2c6da6daca9a0a98a61fd3a606c88b0b98ba1ed3ba0aca7a0a98ab1bd3ba0aca9a0a98ab1fd3ab0b140a54f029fde5e6dd787b15507b16587b1b287b1b60c8a808988a1ed3a808ca780798781bd38707ca970798781fd387077b17207b17683308b0350195175033092050206409bd46837751071f027c17307b17105107da01330249111801951c90003307c06a017b170801951770821a28821b2001c85b089e8829c85808976911d35a0ad36b0bd3a606d3b505d39a0a98882078789577019ebb13abc7da7b1b207b1a287c1789007c1888007c198a007c1a8b00977708d4870797991097aa18d4a909d497077c188d007c198c007c1a8e007c1b8f00978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17687c1781007c1880007c1982007c1a8300977708d4870797991097aa18d4a909d497077c1885007c1984007c1a86007c1b8700978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17607c17797c18787c197a7c1a7b977708d4870797991097aa18d4a909d497077c187d7c197c7c1a7e7c1b7f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17587c17717c18707c19727c1a73977708d4870797991097aa18d4a909d497077c18757c19747c1a767c1b77978808d4980897aa1097bb18d4ba0a821938d4a808978820d487077b17507b1230ab9232951738821808330066099511f07b10087b15827b6485330901330a2064b8330036092831f88212308217407b171801972705821818c88707ac874a9518503309205020680976458218309588017b18488217106482951c9000ab7841fe821748821840821938821a7ba7107ba8087ba98210b0008215a8008216a0009511b80032003307a434013308612860323307a434013308612855324911703309b03501330ba86a01330701951850951a709511e864ac7b18087b19103309506a01951808951a107b1b330b506a0133005e092882329511b8fe7b1040017b1538017b1630015617df0e047d79330b1fad9b075209a7037d7a015a1a1f08520a9c037d7b0233061fadb607520b8f037d7c035a1c1f08520c84037d720433061fad2607520277037d74055a141f0852046c037d730633051fad350752035f037d76075a161f08520654037d75087b15c0005a151f0c8215c000520541037d75097b15b8005a151f0c8215b80052052e037d750a7b15b0005a151f0c8215b00052051b037d750b7b15a8005a151f0c8215a800520508037d750c7b15a0005a151f0c8215a0005205f5027d750d7b1598005a151f0c821598005205e2027d750e7b1590005a151f0c821590005205cf027d750f7b1588005a151f0c821588005205bc027d75107b1580005a151f0c821580005205a9027d75117b15785a151f0b821578520598027d75127b15705a151f0b821570520587027d75137b15685a151f0b821568520576027d75147b15605a151f0b821560520565027d75157b15585a151f0b821558520554027d75167b15505a151f0b821550520543027d75177b15485a151f0b821548520532027d75187b15405a151f0b821540520521027d75197b15385a151f0b821538520510027d751a7b15305a151f0b8215305205ff017d751b7b15285a151f0b8215285205ee017d751c7b15205a151f0b8215205205dd017d751d7b15185a151f0b8215185205cc017d751e7b15105a151f0b8215105205bb017d751f7b15085a151f0b8215085205aa017b1888a801889a01d4a80888ca0188bb018e99d4ba0a85bb03db890bd4a808884901882a01d4a90985aa05db8b0ad49808886901883b01d4b90985bb07db8a0bd498088219c000889901848a01859c09dbab0c821ab80088aa01d4a909d498088219b000889901848a01859b0bdbac0b821aa80088aa01d4a909d498088219a000889901848a01859c0ddbab0c821a980088aa01d4a909d4980882199000889901848a01859b0fdbac0b821a880088aa01d4a909d4980882198000889901848a01859c11dbab0c821a7888aa01d4a909d49808821970889901848a01859b13dbac0b821a6888aa01d4a909d49808821960889901848a01859c15dbab0c821a5888aa01d4a909d49808821940889901821a4888aa01821b5088bb01d4ba0ad48a0a84880185bb17db8c0b84a801859c19db8b0c821838888801d49808d4a808821920889a01821928889901821b3088bb01d4b909d4980684880185b91bdb8c0984680185ab1ddb890b8218088e8895881f33091e821c10dbc809821818888801d4a808d46808848801db8b0964789517c800648550206c09bd2f8217c80051076d82186457017b17c8004921d000089517c8007b172001330780097b1728013307f86a017b17f0004921f80002951720017b1700018287208288284921080101492110019519f00050206e091f13821040018215380182163001951148013200330767360133086128f22d8217d0008218d8007b17e0007b18e8009517e0007b17200133077c097b1728013307d86a017b17f0004921f80002492110019519200182188287208288287b19000149210801019519f00033006e0928ad129511807b10787b15707b166856277fffde00648564763307800033081f6469015107137d9a9599019577ffada8f5510af2282e3309800001c876087c8851080b957701ab97f5280a647955278000ab006417646850207209a02e821751073e7b164911080864177b1758330780097b17603307f86a017b1728491130029517587b173882572082582849114001491148951928330074092813128217088218107b17187b18209517187b175833077c097b17603307d86a017b1728491130024911489519588257208258287b19384911400195192850207409d511821078821570821668951180003200330767360133086128ab2c3307c8360133086828a02c9511807b10787b15707b166856276fffde00648564763307900033081f6469015107137d9a9599019577ffada8f5510af2282e3309900001c876087c8851080b957701ab97f5280a647955279000ab006417646850207809a02d821751073e7b164911080864177b1758330780097b17603307f86a017b1728491130029517587b17388257208258284911400149114895192833007a092813118217088218107b17187b18209517187b175833077c097b17603307d86a017b1728491130024911489519588257208258287b19384911400195192850207a09d510821078821570821668951180003200330767360133086128ab2b3307c8360133086828a02b8279827a08648b649764a864b928ef1e827728550d951128ff7b10d0007b15c8007b16c00082750882797b181055250304ab00c85907ac970b025105eb018217108278207b18388277287b173033077e097b1728140720000000080000007b17203307a037017b1718017b19409517407b17788217287b1780004921880002492198004921a000024921a8008217207b17b0004621b800038217187b174849115001951888007b1868491170019518787b185849116001649695194882173882183050208209e80f520761019555ff9569015205982850017b15085629fffd71018217108278207b18288277287b17203306000233077e097b1738140720000000080000007b17183307a037017b17307b196495017b15409517407b17788217387b1780004921880002492198004921a000024921a8008217187b17b0004621b800038217307b174849115001951888007b1868491170019518787b185849116001951948821728821820502084093f0f5207b8009566ff95550152069a330897370133090382171050208609ed1633080152079b008217088218c88707957500fe5625fffdb50033060002015105787b15409517407b17788217387b1780004921880002492198004921a000024921a800140720000000080000007b17b0004621b800038217307b174849115001951788007b1768821810828720828828491170019519787b195849116001951948502088099a0e3308015207149566ff95550152068b330828063308010164878210d0008215c8008216c0009511d8003200330736370133086128542933073637013308612849293307363701330861283e299511a07b10587b15507b164882ab08649c64856476510b2682a782a8107b1b087b1510ab5b4fac8c8300510816645964ca7b1c50208a0979eb2816510c182805510c1364c764587b1c50208c09d0ea821c2805645701887801db77057b65087b6c107b688210588215508216489511603200491118330b406b01951808951910951a1833079511e864ac7b18087b19103309a06b01951808951a107b1b330ba06b013300980928fc283307186b017b171849112001491138491128084911303308286b019517182811289511887b10707b15687b16605109ee0064b501c88909ac89da007b19187b172882777b17205707e50033060164677b161064a850208e091e375107d9003307821c2097c80188590104330b04939b019559ff330a08db9b0a821918e49a09e4890bd6b608cab6095208b700140a0000000000000080821810c98a0aac9aa3007b1b18510c387b1908d6c5075207ab008217288277087b17ca5c056457648650209009b33651079d0095175882187b18487b1650646882190828083305951750017b75951730951a48502092096efe8217305207578217388218287b87088217187b87821070821568821660951178320033073309886b01284e083307d139013309586b013308202848273307706b0128df373307f537013308a40028562764983309886b012820088217388218403309886b012812083307b037013308452833273307f537013308a4002827279511d87b10207b15187b1610827a086496ac9a367b1a087b178275d88507c95809c9850adb790aac6a3164876458646950209409c139821a08c96a07c8650582187b857b870801d86a0782102082151882161095112832003307f139013308a60028c3268089348277849a10520aec0a84992052094b0b28f121009511d87b10207b15187b161064955709860064866479c85807d887075207788298108297c987077b1908ac574657085c829a08c88a07d8a70a520a507b18d87608c96709c9760adb890aac5a556468645950209a0925398217c875058217087b75108210208215188216109511283200330a01330b016497645950209c09befd8219088298105908aa3307973a013308612818263307973a01330861280d263307f139013308a6002801269511d87b10207b15187b1610827682750851062133080164675020a009c5345107404911080164177b7682190852091028219517087b76821908510917821851081282102082151882161095112828fce782102082151882161095112832003307da3e013308a40028962533090564873308593b012862129511b834084400037b175208333307620a64187b18387b17403307086c017b1708491110024911289517387b1718491120013309286c0195170828f6243307620a64187b18387b17403307086c017b1708491110024911289517387b1718491120013308406c0195170828b9249511b87b10407b15387b1630648682880898893c5209de009789045709d700826cc8c909d8c9095209cb007b170851082d330595c90801829a645bc8a505acb5ac009588ff9599105208ef82681851081056150f7782c8085208703305013307013308015020a4099533510774570590005105153308017b1864575020a6097ce6520709287c330701017b15107b17184911203308d86b0195171064695020a8098909520769821720821818821910821a087ba7107ba8087ba98210408215388216309511483200895797550194753307013308015020aa0924335207923307da3e013308a4002832243307c06b012869343307f83a01330861281f244901013309586c018217645828e4043307f13c01330a706c01330b906c0133085695192f28712a9511f87b105020ae0904053307821095110832009511d87b10207b15187b1610552880003d8276108279ab961764757b183308a86c015020b2092b0282186457015706a900827a08c86a09d8a90a520a9c0078989566017b76102880008b890b48110c520915951a0d9889068699c00078190c33090228528b8910520922951a0e98890c8699e00078190c97893498993a9599800078190d330903282d951a0f9889128699f00078190c97892e98993a9599800078190d97893498993a9599800078190e3309040184883f8688800078a895180c5020b4093504330782102082151882161095112832003307f83a01330861280323648a3308c06c0164a92804089511d87b10207b15187b1610827682750851062133080164675020ba09bb315107404911080164177b7682190852091028219517087b76821908510917821851081282102082151882161095112828f2e482102082151882161095112832003307da3e013308a400288c229511f87b105020be0998033307821095110832009511d87b10207b15187b1610552880003d8276108279ab961764757b183308f06c015020c209bf0082186457015706a900827a08c86a09d8a90a520a9c0078989566017b76102880008b890b48110c520915951a0d9889068699c00078190c33090228528b8910520922951a0e98890c8699e00078190c97893498993a9599800078190d330903282d951a0f9889128699f00078190c97892e98993a9599800078190d97893498993a9599800078190e3309040184883f8688800078a895180c5020c409c902330782102082151882161095112832003307bc3d013308612897219511f07b10087b15827c6485330901330a01330b0164c85020c6093d14090100000000000080ab970d82100882159511103200645928330214090100000000000080ab970a821095110832003309186d012817029511887b10707b15687b166051090301c89805ae8516330701645882107082156882166095117832007b170882777b17285707ee0049112001330b013306ff3307017b17187b171064a85020ca09d62f5107d7003307821228972901821620886a0104330c0493ac01956aff330b08dbac0be45b0ae49a0a821818d6a809caa80552099814090000000000000080821810c98909ac59857b1a510238d62607520794008217088277087b1718ca62077b172864865020cc096c2f510784009517588218187b18487b1650646882192828083309951750017b79951730951a4864595020ce096982173051070c8217388215402827ff8217388218087b870882177b8714070100000000000080280dff33070840013309306d013308202802203307486d012899303307da3e013308a4002810203307953e013308452805203307da3e013308a40028f91f9511a07b10587b15507b164882ab08649c64856476510b2682a782a8107b1b087b1510ab5b4fac8c8300510816645964ca7b1c5020d00934e22816510c182805510c1364c764587b1c5020d2098be1821c2805645701887801db77057b65087b6c107b688210588215508216489511603200491118330b606d01951808951910951a1833079511e864ac7b18087b19103309a06d01951808951a107b1b330ba06d013300d60928b71f3307786d017b171849112001491138491128084911303308886d0195171828cc1e52072464979511d064783307086d017b174911080149112049111008491118641728a81e64872880f98089348277849a10520a2f0384992052098e0328341a009511d87b10207b15187b161064955709910064866479c85807d88707520783008298108297c987077b1908ac5746570866829a08c88a07d8a70a520a5a7b18d87608c96709c9760adb890aac5a5f646864595020d80967318217c875058217087b75108210208215188216109511283200330a01330b01649764593300da099511f87b103300c8092824fd8219088298105908a033078d410133086128501e33078d410133086128451e3307e740013308a60028391e951178ff7b108000641a551a805164827c77330c951b80000184780f88890a868a30958857db9a0878b8ff95bbff98770495cc015207e59ac7800056278000982c33098f4201330a0264275020dc099b0582108000951188003200330759430133086128d31d951178ff7b108000641a551a805164827c77330c951b80000184780f88890a868a30958837db9a0878b8ff95bbff98770495cc015207e59ac7800056278000322c33098f4201330a0264275020de093505821080009511880032003307594301330861286d1d951178ff7b108000641a551a805464827e77330c951b80000184780f88890a868a30958857db9a0878b8ff95bbff97773098773495cc015207e29ac7800056278000c92b33098f4201330a0264275020e009cc0482108000951188003200330759430133086128041d951178ff7b108000641a551a805464827e77330c951b80000184780f88890a868a30958837db9a0878b8ff95bbff97773098773495cc015207e29ac7800056278000602b33098f4201330a0264275020e2096304821080009511880032003307594301330861289b1c951178ff7b108000641a551a805164828177330c951b80000184780f88890a868a30958857db9a0878b8ff95bbff8b770495cc015207e59ac7800056278000fa2a33098f4201330a0264275020e609fd0382108000951188003200330759430133086128351c951178ff7b108000641a551a805164828177330c951b80000184780f88890a868a30958837db9a0878b8ff95bbff8b770495cc015207e59ac7800056278000942a33098f4201330a0264275020e809970382108000951188003200330759430133086128cf1b951178ff7b108000641a551a805164828277330c951b80000184780f88890a868a30958857db9a0878b8ff95bbff98770495cc015207e59ac78000562780002e2a33098f4201330a0264275020ea09310382108000951188003200330759430133086128691b951178ff7b108000641a551a805164828277330c951b80000184780f88890a868a30958837db9a0878b8ff95bbff98770495cc015207e59ac7800056278000c82933098f4201330a0264275020ec09cb0282108000951188003200330759430133086128031b828920828828647a649764a901951178ff7b1080007b15787b167049116020330a03781a688296204911304911407b17507b18587b190851063b01829728330838d6870852085602977803977a06c98a0a570a4902c8a608d8680852083f025107a7017b1a288298088295829a107b1a108299187b19189577ff9777039877039577017b17955508958801956618019588ff7b18205108bf018259510918821a588217508258f882aa18b4a002f00952078d018267107b17607c69188268f8826778196851082852180119821818ae87ac01977704821810c8870782785108073308280e8277083308012806330801018269e87b18307b1738330702aa791f8267f052190125821818ae878101977704821810c8870782785108073308280e8277083308012806330801018269087b18407b1748821718ae792801979804821710c8780882875107060e828908951830b49002f2095207e7009555108217289577c89566387b1728821820520724ff288a0082971898783c52082d0197780457082601821908829510c885097b1920d859095209130151077a82190882970882969588f09888049588017b18956608957801019588ff7b18285108b6008269510917821a588217508268f882aa18b4a002f40952076982575107740d825908951830b49002f609520755955510956610821720821828ab75bd8219088218829708ac781a283d3308829708560710283333088219088297085407298217088277978804c88709821a58821750829882990882aa18b4a002f809510708330701280533070182108000821578821670951188003200330729460133086128a41833073244013309086e0133082b287218330729460133086128891833075d44013309206e0133082028571833075d44013309206e0133082028471833078f4501330861285e1833078f45013308612853189511987b10607b15587b165064727b1c307b1b280180263484670152075a49113800001164c5846704510743570a4502c8a907d8970752073b027b1a187b1920551a2048510a1f0164a9330a821820017d878977c0857701c8a707aca70e029599ff958801647a5209e9282f330c822752073d286195c501510504024911382b8467045107e928a6649764a87b12405020fa09161b82124001c85707ac57cd006475821a18821c20822751072a822908ae95248466085206557c2738c959097b1a187b1c205a1701ac005207bd007b191028bc008225208226286457646882193864ab64ca5020fc09a7013308015207cd00826c186457821828821930821060821558821650951168320c7b19108127307b17087c28387b18822720822628481230307b1240461238017b1748646882193864ab64ca5020fe09560152077b821710c95705955501019555ff5105dd00826920330830821748b49002000a5107eb28566457ae573bff3307506e012821275217021695980151080c019897019888017b181028086497491110018228207b18488226288128307b1840957501019555ff510528826920821748821840b49002020a5107ec330801016487821060821558821650951168320082154864576468821938821a20821b185020040ab1003308015207d7826a186457821828821930b4a002060a3308015207c18217109a777b17383305ff01821738c857075117ff45826920821748821840b49002080a9555015107e52839826a18821748821828821930b4a0020a0a33080152077eff33088217408219087a79308219787938286aff821510821710d87508285eff821710d875082855ff330728700128212633078f450133086128d7153307386e01280e263307686e012806269511d87b10207b15187b161064a6647c5139000011286485828a2064c764987b1b087b1cb4a0020c0a821c6458821b08647933070152092051061a828a1864c7646864b9821020821518821610951128320a33070182102082151882161095112832009511a87b10507b15487b16406476827a827710847701510a08520709286a5107b6017b1a205709ec01c89807d8870a520ae2017b19107b16188265184911387b18087b18287b17309555013306000011019517289555ff51051050200e0a71088388ab68ef28115020100a6408838852380000118c00821910821808821c20821618510c520157099601c89807d8870752078c018265087b19107b18085519203f51090c01330a017d878977c0857701c8a707aca756019599ff958801647a5209e9ae57277c69383308330a01c97505ad9a4f51096164584901285d648764985020120ad617ac57df826828826720828c1882180882191028e600821910821808821c205107b200ae97b200649ac878097d9b591bc0ad00330b64a928ab0052190215955701510701019858019877017b1728057b15018267207b17208265288167307b1718958601019566ff51061a825920821720821818b49002140a5107ec330701289700825a18821720821808821910b4a002160a647833070152087e82179a777b17103306ff01821710c867073308ffaa8758825920821720821818b49002180a9566015107e3284a3307560516ff2836ff648b280f649aab975cff648b64a9ac7a6bdbb709dbbb08821618520cb6fe826a2882672082ac1801821050821548821640951158320c82168217d8760728088217d87607018210508215488216409511583200330728700128922333078f450133086128481333078f4501330861283d133307686e0128742333078a4601330866282a13827a2882772082ac18320c7b65786708461609821010821508821695111832009511b87b10407b15387b163064768275288217487b17188267208255187b1c107b1b087b1ab450021c0a7b16207817284611299517208218821908821a10821b1850201e0ab61b7c19297c1828d4890751094284880152083c8217207c783484880452081a827828827720828a183308894d013309023300200a320a827828827720828a183308884d01330901b4a002200a84770182104082153882163095114832009511987b10607b15587b165064758217707b17388217787b1730825728821680007b1628821688007b16208216687b16188276188257207b1c107b1b087b1ab46002220a7b15407817484611499517408218821908821a10821b185020240af91a951740821838821930821a28821b205020260ae41a7c19497c1848d4890751094284880152083c8217407c783484880452081a827828827720828a183308894d013309023300280a320a827828827720828a183308884d01330901b4a002280a8477018210608215588216509511683200951178ff7b1080007b15787b167064768217b0007b17588217b8007b17508267288215c0007b15488215c8007b1540821590007b1538827518821798007b17308217a0007b17288217a8007b1720821788007b17188267207b1c107b1b087b1ab450022a0a7b16607817684611699517608218821908821a10821b1850202c0a061a951760821838821930821a28821b2050202e0af119951760821858821950821a48821b405020300adc197c19697c1868d4890751094284880152083c8217607c783484880452081a827828827720828a183308894d013309023300320a320a827828827720828a183308884d01330901b4a002320a84770182108000821578821670951188003200951160ff7b1098007b1590007b16880064758217e8007b17708217f0007b17688257288216f8007b1660821600017b16588216c8007b16508273188217d0007b17488217d8007b17408217e0007b17388217a8007b17308217b0007b17288217b8007b17208217c0007b17188217a0007b17108257207b1c087b1b64a6b43002340a7b1578781780004621810095177864688219821a08821b105020360ade18951778821830821928821a20821b185020380ac918951778821850821948821a40821b3850203a0ab418951778821870821968821a60821b5850203c0a9f187c1981007c188000d4890751094284880152083c8217787c783484880452081a827828827720828a183308894d0133090233003e0a320a827828827720828a183308884d01330901b4a0023e0a8477018210980082159000821688009511a00032009511907b10687b15607b16588215707b1c107b1a18491120075215071601330607827a28647582772082aa18b4a002400a7b152878173098683c4611315208e7009768045708e000821a18c88a09d8a9095209d300821910c89808ac980b0151069e0033058217189577087b17083307986e017b170157059800821818c85807d8870852088b00821810c8580aac8a8c008278821708c857078279951728821b5020420a97179566ff9555105206ca7c17317c1830847901d4870751094284880152083c8217287c783484880452081a827828827720828a183308894d013309023300440a320a827828827720828a183308884d01330901b4a002440a847701821068821560821658951170320033078f450133086128310e33078f450133086128260e3307f74701330861281b0e491128330b806e01951820951970951a2833079511e864ac7b18087b19103309086f01951808951a107b1b330b086f013300660a284b0e33078f450133086128d90d9511e07b10187b15107b16086485828b2882882082bb187b1a64766487649864a9b4b002460a82188888017b6508786710490678681182101882151082160895112032009511c07b10387b15307b16286475827c2882772082cc187b1b087b1a6496b4c002480a8868017b1518781720491110781821951710821882190850204a0a7e188218107c19208e87d49707510859849901520953521801367c17218215185107307c5734847704520727825828825720828a1833080c4e01330901b4a0024c0a6478330701510809281c82151801825828825720828a1833080b4e01330901b4a0024e0a84770182103882153082162895114032009511e87b10107b15087b166485828828825a20828b18647633080e4e0133090164a7b4b002500a7b6578670846160982101082150882169511183200648a6478649764a92838f79511f87b15827508827aaca5af00aa5a607ca895ab016c8c7b7b590c71aa5bc50084831f7cac0195ab027b7b84cc3f5328e0004daa5bae007ca20295ab0397c406842c3fd4c4027b7b5328f0003baa5b940095ab047b7b7ca80397333d983c2b97220684883fd4c808d48208281f3308000011649782159511083200973806d4c808280997330cd4320801858c00d80033020000efc82c0c543cff07ef2cacb531827910c9ab0ac89a0aac9a307b7a10649782159511083200330748450133084728d50b3307f0450133083928ca0b330748450133084728bf0b3307b86e0128f61b3307f6440133085228ac0b951178ff7b1080006482808834827784891052091b84882052085482773308016429821080009511880028cb0664195519808f008277330c951b80000184780f88890a868a30958857db9a0878b8ff95bbff98770495cc015207e59ac7800056278000e71933098f4201283f64195519805b8277330c951b80000184780f88890a868a30958837db9a0878b8ff95bbff98770495cc015207e59ac7800056278000a91933098f420101330a0264275020540aabf282108000951188003200330759430133086128e30a330759430133086128d80a8279088277829c18320c827a827908648764a82861f57c776489330801019511f07b100864925417634d96792998990c969b64bfb707977739987b383307914201c8b707acb7be00951c05511cffc200951b06d8b70cc97b0ac9b70bdbca0b541b01b9007c7a017c77330b781a07781706280b330b02647955170a33951705c8b70cac7c7586993078c9c8b709ac795f85bc03c87b0b3309016427330a50205a0ae8f182100895111032009777399879383307914201c89707ac976b951a05511aff70951906d8970ac9790bc99709dbab09330b01ae9b657c79017c77781907781706951705957901ae79a7330796470133086128e109330796470133086128d609330796470133086128cb09330796470133086128c0093307f046013308a60028b409330796470133086128a9093307964701330861289e093307f046013308a6002892097e776489330801019511e87b10107b1508987a046492542a7002890096a94703989313963a1027bfa70b97b730987732967a7b1498a7108479fe073304914201c8490cac9c4b01951904d89c07c9c905c99c09db7509541901430198aa1196a764bf7b0b7cc70197bb317cc998bb30781705c84b07781904acb72d01951906d8970ac9790bc99709dbab09330b01ae9b22017c79017c77781907781706285c330b0564735417635198790296997b14989311963a64bfa707977731987a303307914201c8a707aca7f600951b03551bfdfa00951a06d8a70bc97a0cc9a70adbbc0a541a01fc007c7a017c77781a07781706330b030155130a4095bbff951703c8b70aac7a8100d87a0786393078a98477015207679abc05951703c87b0b3309016427330a50205c0a1bf082101082150895111832009739319899303307914201c89707ac978f0095bbfe951303c8b309ac399900d89704c9790ac9970cdb4a0c541c0194007c7a017c7cd83907789a01789c84770151079f3307964701330861280f083307964701330861280408330796470133086128f9073307f046013308a60028ed07330796470133086128e2073307f046013308a60028d607330796470133086128cb07330796470133086128c007330796470133086128b5073307f046013308a60028a9073307964701330861289e073307f046013308a60028920781776489330801019511d87b10207b15187b161064928b7904330c0a532971024a019773201409000000005917b7d1d6930a98a02d96091027bf970c97c93098993296997b14989b1084b5fe073304914201c8450bac5bf6019516065516fafa0195150cd85b06c9b50ac95b05db6a05541501f101989911969964bf9c0c7cb90197cc317cba98cc3078190dc84c09781a0cacc9db01951a0ed8a90bc99a0cc9a90adbbc0a541a01d3017c9a017c99781a0f78190e330c06534700e1f505ab009607b98d0698772096771027bf7007977920140a000000007b140000d6a909989a1284abfeff00c84b0aacba6101951b06561bfd6501951b08d8ba0cc9ab05c9ba0bdbc50b531b025c01989913969964bf97077ca9019777317caa987730781909c87404781a08ac74460195170ad87409c9470ac97407db9a07330c02acc73b01140700000000893be6557c4a017c4bd67309989939781a0b781b0a55190a6228a000640701541763940097793098993296997b14989911969a64bfa707977731987a303307914201c8a707aca7250195ccfe951b06c8cb0aacba2301d8a703c97a05c9a70bdb350b330501aeb51b017c7b017c7778ab0178a753190a469799013307914201c89707ac97bb0095cbfe951306c8b309ac39c400d8970cc97905c9970adbc50a541a01bf007c7a017c7cd83907789a01789c281e647955190ac295cbff951706c8b70aac7a8600d87a0786993078a9018477015207279abc0a951706c87b0b3309016427330a5020600a13ed821020821518821610951128320033079647013308612847053307964701330861283c0533079647013308612831053307f046013308a6002825053307964701330861281a053307f046013308a600280e053307964701330861280305330796470133086128f804330796470133086128ed043307f046013308a60028e104330796470133086128d604330796470133086128cb043307f046013308a60028bf0482776489330801019511d07b10287b15207b1618649298790433051453297102c30214094b598638d6c56d34d6970c98c40b96491027bf970b97b930989932969a7b1498a910849cfe073303914201c83c09acc977039515045515f07b03951c14d8c905c99c06c9c90cdb560c541c01720398aa1196aa64bfab0b7c9a0197bb317c9c98bb30781a15c83b09781c14acb95c03951b04551bee6003951a16d8a90bc99a0cc9a90adbbc0a541a0157037c9a017c99781a17781916330510534700e1f505160214092d431cebe2361a00d6940998990296991027c9940b96ba7b1498a912849cfeff00c83c09acc9de02951c04561cf3e202951c10d8c906c99c05c9c90cdb650c330602ac6cd60298aa1396aa64bfab0b7c9a0197bb317c9c98bb30781a11c83b09781c10acb9c102951a04561af1c502951a12d8a90bc99a0cc9a90adbbc0a531a02bc02140afdce61841177ccab7c9b017c9cd6a70998991a781b13781c12140a0010a5d4e800000033050caca76a01140acc10c7bab88d0600d6a90a96aa1027c9a90b96ba7b1498a912849cfeff00c83c09acc93002951c04561cf73402951c0cd8c906c99c05c9c90cdb650c330602ac6c280298aa1396aa64bfab0b7c9a0197bb317c9c98bb30781a0dc83b09781c0cacb91302951a04561af51702951a0ed8a90bc99a0cc9a90adbbc0a531a020e02140a3322d45b02332f237c9b017c9cd6a709989925781b0f781c0e140a0000c16ff2862300330508aca75101969abbb88d0698aa2896aa1027bfa90b97b920140a000000007b140000d6a90a98a912849cfeff00c83c09acc97a01951c04561cfb7e01951c08d8c906c99c05c9c90cdb650c330602ac6c720198aa1396aa64bfab0b7c9a0197bb317c9998bb30781a09c83b0a781908acba5d019519045619f9610195190ad89a0bc9a90cc99a09dbbc0953190258011409577813b12f65a5397cab017caad69707987733781b0b781a0a3305045617631828a80064475614630f289f00649701541763990097793098993296997b14989911969a64bfa707977731987a303307914201c8a707aca708019555fe951c04c85c0aacca0601d8a703c97a04c9a70cdb340c541c0117017c7b017c7778ab0178a753190a4d9799013307914201c89707ac97e300955bfe951304c8b309ac39f700d8970cc97905c9970adbc50a541a01f1007c7a017c7cd83907789a01789c2825649756176371ff647955190abb955bff951704c8b70aac7aa700d87a0786993078a9018477015207279abc14951704c87b0b3309016427330a5020640ac2e88210288215208216189511303200330796470133086128f600330796470133086128eb00330796470133086128e0003307f046013308a60028d400330796470133086128c900330796470133086128be003307f046013308a60028b200330796470133086128a7003307964701330861289c00330796470133086128910033079647013308612886003307f046013308a600287a330796470133086128703307f046013308a60028659511e87b177b180847111001009511b8827a28827b207b1a287b1b20827a18827b10827c0882777b1a187b1b107b1c087b1764177b17307b1938461140461141009511c07b17307b18389517307b17491108014911204911100849111864176498289f9511c07b17307b18389517307b1749110801491120491110084911183309d06e01641728879511a07b177b18089517087b17403307620a7b174864187b18507b17583307e86e017b1710491118024911309518407b1820491128029517103308406601283aff00951118ff7b10e0007b15d8007b16d0007b187b19087b1a107b1b185107145217011e3307b549017b1720330702281b3307b349017b1720330702280e3307b749017b17203307070182c88215e8007b17285208359517207b17603307580a7b176864177b17703307560a7b17789518107b1880007b1788003307686f017b17a0003307032850951730330930c8910664c85020680ad5119517207b17603307580a7b17687b16703307ee097b177864177b1780003307560a7b1788009518107b1890007b1798003307286f017b17a000330704017b17a8004921c0009518607b18b0007b17b8009517a00064582849fe9511d87b10207b15187b16107b17510985013303330a0fe4a90b95bbf1958a0784aaf8c98a079a9c330ab84a017b1a083300f82809953301ae935b01c8380a7daa570a5bbf370a84aa07520aebaeb33357039b01c8380ad88a0652069101ae0a820182a682aa0814058080808080808080d46a0ad25a0a520a09953310acb3d3ae9397009a36c8380a017da5570588009566ff95aa01ab6cf428fa0084a6ff00821a08c86a0a7ca55115044d51150327521502f30095350151053e01ae95e300330201c858067d663304015816bfc10028f100953501ae95c900c885057c553302015126e0003b5226ed004c6c5a581a9f5c28b900953501ae95a700c885057c555126f000286c555226f4005558158f5c2892009a6301ac9325ff2878845ae000512aa00029288500955a70ff531a303c2872956a1fff6c54531a0c0d846afe00522aee006a5914c065953502ae9552c8580a7daa3306bfafa634285d956a0fff561a02425915c03e953602ae9633c886067d6a5a1abf3c953503ae9524330201c8580a7daa3306bfada634955301ac93adfe330a82177b78087b791028333302282033020133040128183304012813330201330402280b33040228063304030182177b7308787210787411330a01017b7a82102082151882161095112832003307b84b0133086128c1fc3307b84b0133086128b6fc330710700128ed0c9511c87b10307b15287b16206479957c0784ccf8c97c07ac7832c8790aac9aba02c978075707be02140bf8ffffffffffff7fd2b705c85c0baccbb60298720351020aab9c3d3306285857088302c88907d8970752077902510823330a017d978977c0857701c8a707aca70f029588ff959901647a5208e928d101330728cc013308c9c90c017d9b89bbc085b601c88606ac86f70195cc019599016468520ce9847707c85a0bc87b08acb851025107aa013308017db98999c0859901c88909ac89c1019577ff95bb0164985207e9c89607ac678c01140801010101010101011409ff00ff00ff00ff007b1910140b0000000001000100140901000100010001007b19083306df01510242013309c000e69204974303c8a303aca37801c9420b98b93c520979018449fc00979903c89a0cacac7701984902979905c89a00aca075017b1b187b125512040833022872330201aca61c0182a98595ff985507989906d45909d28909c82909ac29fc0082a508855bff98bb07985506d45b0bd28b0bc8b905ac95e30082a910859bff98bb07989906d4b909d28909c85909ac59ca0082ab1885b5ff98550798bb06d45b0bd28b0bc8b902ac92b10095aa20ab0a96821b10d2b209982a08d2ba0ac8a909821a08caa909989930c87909ac79e400844503643a6497821218510520ff975503c8c505acc51401330a3307c000821be67b078477039777030182cb85b6ff98660798bb06d46b0bd28b0bc8ab0bacabaa009577f895cc0864ba5207e0821810d28b0798bb08d2b808c88707821808ca8707987730c89707ac97cb0082103082152882162095113832006467ae677cfe3307986f0128890a3307f86f0128810a33075848013308612837fa3307287001286e0a330728700128660a3307287001285e0a3307b84b013308612814fa33079b4c013308a2002808fa3307b84b0133086128fdf93307f7470133086128f2f93307b06f0128290a3307e06f0128210a3307b84b0133086128d7f93307fa500133086128ccf933075b51013308a20028c0f93307fa500133086128b5f93307b84b0133086128aaf93307f74701330861289ff93307c86f0128d609647933073d4d0133082b2868f99511907b177b18087b19107b1a1864177b17503307580a7b17589517107b17603307560a7b176833074070017b1720491128024911409518507b18304911380295172064b828dff89511907b10687b15607b1658649b330233064911188279107b192882797b19108277087b17087b18509588ff7b183833077a4d017b17330c0a7b1b402837821738c857077c779577f6887701018218287887821708827a18c92509821710821830b4a0026c0a821220821b40330c0a52070c018217188477015207fe00821750c827077b17307b1248ae6b28ac2b1101aab2e70057021101821750821830d87807491118017b122064b55107a30028f700646528076465ac6bd5c95b09821a50c85a0a551910203308510936c88a077c77aac735958801ab89f533076498330901285133070a64a850206e0a5807330c0a821248821b40330901aa971028353307330901282e3309010195880151088000c88506ac5681009567ffaeb79a821738c867077c77abc78f7b1620ae260f288c0084770164b6510755ff570275821750821830d878078215205207668217287c7751071c821708827a183309048217108218b4a002700a821248520710ab25d0fe330728d9fe330728063307010182106882156082165895117032003307c0700128ec073307d8700128e4073307a8700128fb073307054f013308612892f73307054f013308612887f73307664f01330866287cf79511d87b10207b15187b16108276107c69827b827508510938825c18330a7a4d0133090464b77b180833087a4d017b1bb4c002740a821b82180851071433070182102082151882161095112832009587f68877017867825c2064b7821020821518821610951128320c951168ff7b1090007b1588007b168000647c7c77087b1b087b1a10330a01330501520783016492648382c68067347cc9098478047b1c1852089000826828330a7e4d01826720828b183308814d01da9a0885990364257b13b4b002760a82186459821c18330501330a0152073a01826a2882672082aa18b4a002780a330a01821c1833050152071f01826828826720828a183308784d01330902b4a0027a0a330a01821c183305015207fc008217088279188217106468b490027c0a330a01821c18647528e000520932826828826720828a183308834d013309037b126435b4a0027e0a64538212330501821c18330a015207b3008167340182682082692833050178153f7b18207b192895183f7b18308168307c6938826a826b08826c108266187a17747a18707819787b1a407b1b487b1c507b16589517207b176033076070017b1768951720643864295020800a85fc52074b3308784d019517203309025020820a71fc520737821708827918951840821710b49002840a520723821868821760828a183308864d01330902b4a002860a6475821c18330a012809821c18330a010178c508461c090164c782109000821588008216800095119800320064787c79097c77085109688479015209579511f07b10087b15648582877c783484880452081a827828827720828a183308894d013309023300880a320a827828827720828a183308884d01330901b4a002880a6458821008821595111078870884770132004618080133070128446bff847701283d6bff951168ff7b1090007b1588007b1680007c7a10827c330501520a1d01648b827608806834848a047b17187b1c10520a527b197b1b0888c201330b814d018269283308084e01826720829318dbcb08330b02c92b09b430028a0a821c1064788217185208d40082178279188217086468b490028c0a821c10647582171828b900520c337b197b1b08826828826720828a183308094e01330902b4a0028e0a6478821718821c1052089000816834821b08821901826720826a2833050178153f7b17207b1a2895173f7b17308167307c6338826a826208826c108266187a18747a17707813787b1a407b12487b1c507b16589517207b176082991833076070017b176895184064b7b49002900a520723821868821760828a183308864d01330902b4a002920a6475821718821c102809821718821c100195c8017875105108177b78821090008215880082168000951198003200330790700128c2039511e87b10107b15087b1682787c75105108708459013305015209635218013a7c78115108338278087c8934849904520927828928828a20829b1833080c4e01330901330501647664a7b4b002940a64786467520829827808828928828a20829b1833080b4e01330901647664a7b4b002960a6475646778751028067875100184570182101082150882169511183200951170ff7b1088007b1580007b16787c7a08330b01330501520a1901648c82768068347c7a09848b047b1710520b4c510a32826828826720828b183308814d017b190833090264c5b4b002980a645c330b0182190864788217103305015208d40082991864c76468b490029a0a330b01647582171028bc00520a36826828826720828a1833080d4e017b19083309013305017b1cb4a0029c0a821c8219086478821710330b0152088b0081683401826720826a283305017815377b17187b1a209517377b17288167307c6a38826b8263088262108266187a186c7a1768781a707b1b387b13407b12487b16509517187b175882991833076070017b176095183864c7b490029e0a520723821860821758828a183308864d01330902b4a002a00a6475821710330b012809821710330b01017875084617090182108800821580008216789511900032009511f07b10087b1564757c780833070152081b8257827828827720828a1833080f4e01330901b4a002a20a78570882100882159511103200648a330860700164a92873d69511a07b17491108800064177b17403307620a7b17489518087b18507b17583307f070017b1710491118024911309518407b1820491128029517103308f06d0128c0f06689330a01aba91814090000000000000080c98909d879078577013200330732009511e87b10107b15087b16958a0784aaf8ab8a07330a28225109fb00330bc98a0ae69a0a9aac6485017c56aa76b40095bbff955501abbcf4531910db009592f0aca26f1403000101010101010114008080808080808080140c0101010101010101cac70c95840801570a9500c8a805d8850652068b0095a60857068f00c88606d88606520685008255c8a4068266d3c505d3c606c9630bd46b0bc95306d46505d25b0bd20b0bab0b0995aa10aea2bc570a6bc8a80bd88b08520862aca96ac9a90857086baa9a173308c99a09017cbcaa7c179588ff95bb01ab89f5330728139ab8330701280cc98a08aca84f33070101821010821508821695111832003307fa500133086128faef3307fa500133086128efef330750710128453307fa500133086128ddef3307687101283333075b51013308a20028caef3307807101019511d0647833071071017b17491108014911204911100849111864172842ef9511d0647833072071017b17491108014911204911100849111864172823ef9511d0647833073071017b17491108014911204911100849111864172804ef9511d0647833074071017b174911080149112049111008491118641728e5ee9511e07b10187b15107b1608c9870aae9a8200c8970a330b10c89804acb9130184a20784a3f89a2baea31ac898059555ff017c5c95a6ff78acff9555ff646aac63f3c8b404c92902842af88449077b1ac9a30a5109b700ae3acd0097490384903851002701844cf882c58d9984993895ccf80182c6cf9505d00608d45808953bf87b38f895ccf864b36465acbae82896005319106f8d7a84a207c82705ae571a642c647a648b017cb678a695aa0195ccff95bb01520cf3c82804c929028423f8844807c8350a51088500aea594009748038480385100b4008446f8826b8d88848c38956808018286d00b0bcfc609d4b9097b59955508958808646baca5eb2860647ac8970bacb7652872ae3a1ac828089588f8018289953bf87b39f89588f864b3acbaf382189a88842907c8840401c99a08aea848954cff017cc995abff78a9ff95ccff64baacb8f32831aea51364480182897b59955508958808aca5f6842907c83408c89a0baeba137c8b78ab95aa019599ff9588015209f38210188215108216089511203200005109167c7a7c8babba139599ff9588019577015209f033073200c9ba073200531910558d7a84a207c8270baeb713642c647a0178a895ccff95aa01520cf8c92909849af8c8ba0aaeab22978238140c0001010101000000d6c202972c20d42c0c017bbc95bb08acabfb849907c89a0bacba0d2815647ac8970baeb70e78a89599ff95aa015209f832009511e87b10107b15087b165319106c8d7a84a207c82706ae671a642c647a648b017cb578a595aa0195ccff95bb01520cf3c82804c929028423f8844807c8360a51085caea66c9748038480385100718445f8825b8d88848c38955808018285d00b0bcfc509d4b9097b69956608958808645baca6eb283a647ac8970baeb7137c8b78ab95aa019599ff9588015209f382101082150882169511183200aea61364480182897b69956608958808aca6f6842907c83408c89a0bacbac728d40021244952099224490a922c498224499214224244920a4a0a12292925499024858810114982244952902415242948b2240992a410119208112122494a4a2a4852b22405495250922429849044482449494905494a96a420490a4a92248510920889142222a224912824a44c22912425888888248854921111499224091289249104492222229220914892480a22a448a9a49424809424298a24410114c022220a899008111111258944212165128924294144442441a4928c8848922449904824892448121111910489449244521021454a25a52401a424495124090aa0001611514884448888882811222a248084a42492442492a404492605258942248944225210918888484144444422229224a992149148441249a4248988489224912489242988104512454404492545490248912489484a9292a42042a4485124121453924852101115298a44820016115148844488081121112222222a894421012424258924914892242828924c5248922489882449aa244524912492442222929244921414499292a42429889014298a2441a9a44c028892a420428a94920490922445912428800258444421111221222442449224490049225121134952099248228924099224490920254a25936449a2921424524952894450022844840821112222224a22a290909448129124891224651291824422529048440a4424a2404481888840440a442422029124915449141189882491882890482412892449124925a52401a4244949028848810851a42812091440012c22a29008899008111111258944210124a44c2249122590049248251989409224922a892222119124121181249104924422419200225220428a944a4a4902484992a2481228800258444421111221112222224a225148482a91249248524aa0a44c92542049920249922490482489249192244922a9a49424809424294922299048492949020550008b88282442222442444444492251480009299348924409248124524946229024094422224924220249220924894482240144a44084142995949204909224459124500005b0888842222442224444445412498484a42489249224492481649288884881444444444444440a444444444444818802111181880211052222029108442210118144049224915449141149442489442049922492244a1240522a9924a5240124522021458a224920802c22a290088910111151128942425289249144925202256592a402499214489224814422492489942449124925a52401a4244949124981444a4a491228800258444421111221112222224a12894242ca24124952021111910422956444449224490289449248024922222292402291249114889022a5925292005292a42892040aa00016115148844488489224912492244b929424900a2449921422420849844424094492a44022494a024992a440922549204992248588904488081191241049920289242909244992024996248124499214224212212244244992940492542049922481a440922425492142444345082189248194242581a4249094942485889044121222090991848448920a2009202512520a2012291021228944522042441289a4408488244529899424052292925292044a22808814881091440a448848529492484952202229292549a0241249814809201289a440645222498a521229490a442425a524099444009124291021224900912429901091248048921488a4a49424114d4912489242448808112111122111122122244222244244488488244992244902514aa054920492a402a94c92a4022999248588868a1091948a1091425400252492424881a41212524a455225859022520829909454510829a210522029a20248024809299524491212212222222244224442485220a29088484208499224214984842442920809212922494812212184a48824214984841092248490242292902442421221498484109202118544441242489224248990104292242149848424424208498188928424111242488a484208499210429224842422092124491292444808212922452421842449082149929024424208498188921092404449082199021185444452092915924412424821492421844c0a24852491544a4292482421498484109202494908214981a42421498484244242480a24454424222212910811222244049144486a44522009b14824108924912449a24444440411402248111151001101241212a48820924412898840491211449248922492242222112449200911292211442412498908241111818844224444880844121192a24981244491a24824109124451252524492244992244992249124499224499224492449922449922449122949922449922449222222222222101111519114494284081111441251154d5220095144222212214244848820928890d42405921045249244004992442411119188881021224404914484a426299084282291448448818824291089a4888848222222428488081141241121a9490a24218a48449224499224499224912449922449922449244992244992244912294992244992489224228188884444448810112122882422243549814844244992244992244912499224499224499244922449922449922491922449922449922422221111214244848820928890d42405921045a41021424404914484a426291089244992244992244922499224499224499248922449922449922452922449922449129144445284081122228824222435522012114044922449224922251289888888081122424420928890449302498822459148209224922a224444881011220291445435299084285214890422494422499224499224499248922449922449922492244992244992248994244992244992241191141111214244848820928890d44881484444224488881011441211921a299084282211111111111821221221224244104944486aa440122245249224499224499224892449922449922449224992244992244992484992244992244924111149119188888844881011212288242224355220095144224992244992244992489224499224499224922449922449922489942449922449924412518b48462222220a4444444444848888081141241121a991024988221211818844881011212288242224355220095144aa919088884444840811112202914444444484081121221049444445818824059210458a2291404452200951245144442091242242241089281291141111111111112244448808228908498d1448421491482422a2888808221211111111112142448488209288548d1448421491282211492422222242848810114412912a1a2990842822898844881011212288242224355220095144a2482490049224904488888804228908910211891488449148202202482205921045248a4402119124454444448810112122882422243552201245228108448890084488884849112142848808228908498d148884081122228824a2aa91029148914820232222111111112142448488209288548d144842149128120944248a44021191941411112244888820928890d448812444881011412411558d1448421491281209444492a440924491482022109110890422248a481489044224128890281209444404222222428488081141241121a991024988221245228188489214884491482009244920921011110944121122052212299084285214890422222210119124259228120944444422224a49245222121149442222428488081181884444240a4488a4408428521489041249204244914411118144914820921449242552204214298a440225012449522009512451444444111129222211242289222222812422228124444411091122444410494455930249882289222212452281242251241288081149122989884844a4121189482222101111112244448808228908491a2990842822114522818862229144121111114912234992244992244992489224499224499224922449922449922489942449922449922411111191482042842411111129822410492042444424101111220522240522238a14452281882429922412452281a4408428521489044688884488881011444484a42605224491a2482410111188884844444444881011212288880849d1a4408428521489041211111121422449892492a454492411a2888888111111111111810811418488a4242991281021a2485124122846241249810851a42812096412911488104512454444441449248184888810422242444884881011224244880811212244848808212422448808112122420889081121224484889008212422448488101122448810112242444408211121224484881011114248448808498488101122424488081121222442448808112189101122228490881011224222444484101211222242088908111142484448848810111142484488081121222442448808891011224484448808112122448488101122424488081121224484881011224244880811211122424488084984881011224244880811212242088988082124224484881011224244880811212244844408211121224484081121224444082111212244848810112242444884881011115194241111111151450a4400898844442222a20811222244441011218946920211b14824109188484404104002480490909048484850041111412422221091242944884422928888888820222182484444128948214444202221824844441289c82291482211091144222292484404128924499224499224492249922449922449924892244992244992245292244992249124492442444404111149a2910211a28844001189a424119144442444108988482211299288a44822522205224491a24824502212918824a94a4444444488101121221011119288260522449144111181240248244981882822512412882852128948244244848820222249d1a440842852148904914444444444224124222289441489441211891489040949221111214244848808222224d1485220221689041140442222494a928808221240440a44882245914890249214888822459148902481884891a248248888480a44882245914850444414224244880811218444844448844448844448844488081121222442222442224484881011122111224208890811212244848810112242224484448808112122a91424494a5244444424222249925452444445414422229288489222121112124042229122222522220229922449244925894a444491249192880849021109a2489220220a4414882442a2244c1288248892242122498890148814494422094412442422121111214244848820222910218a144522414444486a52204214298a4482884488101121228888444444440422828888540511222910218a14452241921448844891448a88085212495011118588101192081121422242448888082124224484880821242244848810112242448808899048924a929424495220224b92204d9224912495442480249244498a24494a522529904825a52409ca24292442448848929224819224344952204902495288088924294992044a92d02449812409244921222492a424491228494293240592249024858890489212409224909224a1499202491248924244482449492049499224a1499202491248924222494a924092d0244981240924492122249224892451128824298824492409924812a42492124952522a29291091122925920425492122448414218424422244848824499224292849254981244952880821241112114992444902519224490248529024542465929224499224419292228942224484440a912449922450928426490a244920490a11219124092049128824111a495220920492a410111225804a2a484a25804a2a90944a00955490944a009554909492540525804a2a484a49924802a8242249920211292925499052920289a42429910892248588109124499404900012124002a82049249294242949524a92488188942849249202912491480a4492444a2225498188a4a494244149228088148824891488248994444a9202114949294982922491148894249224920291494952221149122989942405229292529204251345495220492645495220492645495220222549894452222991882489424488081121222442448808891011122122244244488488900811211122422244241549244912494a95a44822895220229548494a4a22a5c49240244992445210555292248104892492288924419224894c92c8241149544c529224458a2411128924122910218a484a922013492429109192a444224814890449924852204214298a44824c923422a210122111122111225a494884444884444884444884881011224244922425499254500a4a92829420490a1121842492a424252591442491240920539220892491144912492289a42449529248522092925292244992a490482541024812492289245148541224808408891091244992242492244948844448244992448904904922499292492a90484a4a49122489245502282959921422422201a4245292a0244920012480921448525292949424419214224244024825294992a02405122929494a9220992485881011119144129544491292888848154992a4a4124992021129292549444422220a112191104212212249a22449920425092a094a920412409224414949921284109208114912254992242849504950922490009224094a4a9294208490448848524a9282a4949225494952819050422029059252202905925220290592522029059252202905925220290592522029059252202905925220290592522029059252202905925220290592522029059252202905925220290592522029050a24492191a45220492821500a144852924a8124a104420914485252494812212184a42029a98410921424259510429282a40aa08424298414484a894454420aa9922495500249124012409250028124292442440a498944a444225222114905500a214514420a2425918824a9404828219094024929909402492990940249299094024929909402492990940249299094024929909402492990940249299094024929909402492990940249299094024929909402492990940249299094020592a49098424a48215512400590a4a0241528802449012c494a920aa02489449220495292822a489242440821899424800a4a52810248004b9292a424954852492a240aa11408242929218424152425494a4400499200522622499202112929254990242529109192a4442248290124522091925292244992a4101122422224424492a44412409292a42429109194949224a8940092240512292925499224490a1121111221224992a42449814425a5a4244992a490088910912449520248a440a2925252922449524884448888485222022849024800090920012405491289a4244991a494249102912491480a449244222910491229899424052292925292042589002252209224522022259224521229490a442425a524094a1289a44024492491480a449224521229490a442425a524094a26124952204926892429902493449214884c4ad2944824251291520288488188949492249248245188081121224484448808112111224222448444880889101112212224422224422444421222a5945242424aa924804a02481249122449526a9254509214480091249524242948244992024996244192248548124248224492a424910412a92049922441491224122449529214220d95928292a440024802684444492422490a2222499288884841444444444444440a22222222222222521011111111111191828888888888484144a24852158900222222129192480025932481a42425221194140d200194480a44a244a248128848202281880492a4484444440a1240091229111124228148024904529224122221129288888a888814444444924444440a22222249222222058988489282924481082009a0111145221111918244444444144952924412c092a44044148b880852144952498a4400014444244912481248128848202549911049924a5290002a802649491252aa8888a88888489102a824490a2249208092a420920092a420499282a41449aa220920522a92a402489222a948924a522429925445124024491240a98888a80092a4480228490a220194542449059024498aa422492a802449522415495201244991242988045092820410499204505224494952249104509250920209209224344912299204502a92a4024892244592224955240144922401948222498104104992045092144492244941240904509214495295220920922409a0a420014492240194004a9224494952129294244982a49292242849144824482a89405211891111a54a9224095225099432494a952449922441aa24099432494a4992244992142495921494549204924a2649a99224499224489524815226494949921424a9248154264909a0242149810490002a49614992942401944c9242121111491291828844a42091881424129182441105110509a0026844448908a24892aa14890012114992044904494a92244992242949524152902405495290240549529028a8204922a920952489a482242948928224a92089240a2489a48224a92089245124294892482a482289922422a9209214449282485290289244524192144922a920294892822489a4822429488944524112490509209248841092445241921424499144524125493249054941262912490589449254508a4452410288241221842499a4822429488a44524112911489a48204104924921494026592822429492aa82449888448888448888448888424849024849024a20248024809299524491212212222222244224442485210514844921042922449481221218990244242488a2409492224849014491292444808214942084922220949222424119244480821298828242249082149929024424208499284241112920809212405112509492224849014494208499210429224842492841092240949222484901449912484902409212449129244480821298828092109224a4208c914441412912412492291241249211149a482489248122489c44824492580248094905251922429490a91864a082149489224554a924815945205d52429494a48524a4252888444222149aa24050124a51240522a0124a51240522a4995a42080a45402484a2580a454922a898200925249924a12459244524a412425124012491249aa20494a52595290246992a812224929210991922491a425492991a4240551924892244912409292489214449294ca92822449920a20212421211292100920a59024294992824425a5a4244992a4908448294911919024424212212189901042524152442451229124244922114982088b24252113499248129194204992242421139128494844a24a88444412011449242149128948124468914449c8449224924424254824491292908948942424225125449224012491a498285210298828880a1225490a9190244949124422242948524a25499224499224494a2a484a5292242d490a09499290a42029a55492244992244992a4a482a42425495a92141292249590a42449498a544aca2449124908492264449224499224892c4992a420922425490a5249495292449232499024858484a402484a4a92922449922429490a91901009492442928224a55492244992244992a4a482a4242549d292a49090242949822409490a92524a25499224499224494a2a484a5292a4254921214992041045524112912449a244220922092211240a224924ada4a4aaa92091922448495288249490842444422444922449a2241524114992242a4d12a424929a5a49412225a52409529214220909494842244442244992244941220591824841529014495292444a5212942449929429225149428a494aa44492149284842424498a2449a2242492444892a48a2445451249d5484a2a0aa02405494a4a491224490a9190482424211222514a4a922549511212492224a552125592948a30492a4c921426294c52484848489292a424454948248990244d529294a428098924119224a548420849a2242492444892a4494a9292142521912442922a2292222955a594245535524a9292a49a2445494824899094244a4a4a9184109244494824899024a98888885252a42405490a294a242492444292242992924892422291904449482489902429451242481225219124429244929414894449d2242549498a929048122149529294a42809892411922449929214494222498424492992104292280989241192244952aa5a2405494952954a8a00d224514249220921248924219124429224921a91248988484992a448924a4942259282484a4a49505292824894448284484a922022224a484224114908214988844844121249222421522429489294a44a25450055929144492421842409492409892411921212524a25852411929224499014244935490a222929254910495242421222299210429210899290481221099124490a92822429849024449254412a80a4a4242169520a919054414a219224a92a48952449922a1509911212524a25495292240922490049520992a8001225298954414a5249522a80a42425495255329525492112222112121292902449aa9424912a28a50aaa4952929490a49484a410098944421240522a01a414922489942849290551922449228024491249298894a42a4b0a92a42429802549212112921089884892441249a248222929498a2429484a119220490a22449152a5281209924982949424452a89244992244992244992922422a2908448484292844888842444522291944824492a88841282a41424a5202905054952484e9244894492489424499224250949125422495292244992242924558a94240989249112929084904444a44492242180009224012480244992049024499224012449922449929004451245844c32499224294912119124494444922449222292248988489224499224499224499224499224499224499224499248aaa44892499204495244929222220a49481242929484a41089880825918824918824918824918844442422221111898848444422221211918888444424914822914422894412892412492492482491482291442289441289241249244a9294244992244912491249224922492449248924892491244992244992244992244992244992244992244992a4244992a42888d40811112142441222828888421211111111224494448848920415694a92624a524105a52891904892482249924442224912099244210949920415694a92624a524105a52891904892482249924442224912099244210949a94a8988480912912489049090491221422442129148a224412449822449440248484d9208112211928844124982481292209112414c9244881001444822922492489024a92c22a29084242449922a2549a40a4aa9826a9294242524292521294442229190244934919448298892489224492280249224925241a4a4549604499294241512421212228524212109912449492549aa20496592a410498944264951495292244949920a4a92242515240949484224494a5290a8a4949424499214222924a1444948248990444948248990244949244a22292b912489a44990482a489924a4204992942495922021121292864a0821490a2a254948a910d448122549924452924492484a2249248964a204254921299424294941a29252529224495288a4a0529284940a418d24519224492425492489a42492449248264a50921492a44a520820299500520a4992485a929412912429889244922449920092944492a42092a45496149224492a8084908484484842244952a5248954412955504d9292a48424a5242485484824129294429148292991c824292a899424495292a48292244949458a240949484224a2a8629224491241484144218928aa98244992441052105148228a2a264992248920a420a2904414554c9224491241484144218928aa98244992441052105148228a2a2649922411841444149288a28a49922449042105118524a2a86292244912414841442149aa1149244992449248124944529224494926519224449294442249a9a44c922412494a2565924492220991244924894492249294a464122549428a24244992924a2a494a929290d448a29084908424842484242421499292498292482289a82c4924a54a8948aa049928294992242192a452412225495292944422a5822499481252111289249549924c92846449522a4990a4242429994492909424215192922449121292909024a9a0540a9592a44a929a24254925499148224992244c129482024912914492889624929294245505499224494429542291549324992409499224945432499284a42255549244924952a532490a094948424292a492a49424254992429294244892244992844449424292a424294912892449524892920449922049922449121225090949922292229144229144229148924292942448920449922049922449121225090989248a8828124924124924128944229148522111a9244892044992204912442449922409899284844444942449222425214922912492889284469248a2a41424499224499224244a12129224852424214948524a4252888424494a52552849499292a424a99024490992922492244912124aca2421214952924a5248a124295525a548a9449212224952889424490a95929221244892a452484212929090449492a4149142c5244992248250513149922489204c41442109494aa552991449928444429224a49252244949920a929290249224494a928424242109498884242421529924059148128990489284244949124992444a4a9148928444429224a424234924254952419292904812491252521292908424444212222109494842242421529924254102482249244222214912922425492449424a121012490049482424494292a424892449228094249186249224219124922491522424924492849454912492264952419214929084244442122221094948422424210991ca2429090248224924422221491292242549242449424a121040128984444292249124294922214912024849124022804422219190244924494a924848928400529204900812490049482424491249929224129224218094248914694822491212492249424a9190481249125252852249244d92a4822429242109498884242421129290842424210a294429314992a424952493a448a44a922291502949a424249244424644942411924252481649122991448448490a92482245442242644422a2244951922485a4444a92441251024892445924492445844424252491124291440a8548a30889288a0891a4a4884852244949a2922a2925a52c499624852424214952922412012491a42289b424919494b224919424112d492425114000092080644492482292481229aa5192249124492249924492248924499224914a24519464499244529224491249521112129290909090844848421212129290844848421292904292929448120989249192245592922409252525539224248924922411912452a44a92282949a51424492a6522912425498a24495292a49044a94c924242424212929024492a494809859224494929498a882849125592444212920a952492842491242424892485a4244948854a1299244992942449922421a920210992242149424249c9441111952449aa244942a22421a194a44824222252a224494a42429224a4444921252949424249244d9224494a9224499284a4902421a1a4649222220a4952499248922449482a549224a442a54c528a8824894a922449480a2525910a29499290a4504a2293244992922449922421292449482829998848945492928484a4542829944892904812092909204995a4241525b548129200020820194924922825499224499224294b922a2599944212925048422112a65024525228122929148994148a444a92249244529225294952229248254b92a4922849b59224491291442a59929252494996a45432c99294d24a9224952449d25492a45294a496244912409225492a2949498a92542b499224499254b22425a5924a92a49456922429", - "00eb00ec000100004afa7e53164226ab91221245fc7e2d48c4a8ea897e404b": "0100000000", - "09000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "ff000000000000000000000000000000000000000000000000000000000000": "bc804dccb6bae35cd48cc1e11e426e7ba218727e2ba7dd43e8163f4f5d06333fffffffffffffffff0a000000000000000a00000000000000c97102000000000004000000", + "01000000000000000000000000000000000000000000000000000000000000": "08a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad808a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8", "0a000000000000000000000000000000000000000000000000000000000000": "0000", - "0f000000000000000000000000000000000000000000000000000000000000": "000000000000000000000000", - "ff000000000000000000000000000000000000000000000000000000000000": "7d952fd5905548e08a9a7d8fca521139f7e2573a2207d6a6d0e706a13070487bffffffffffffffff0a000000000000000a00000000000000487702000000000004000000", + "07000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "00e800c0000100009365e44fe301c284baad2fb8fbf048d1deb729ca23e5c3": "0100000000", + "00fe00ff00ff00ff804dccb6bae35cd48cc1e11e426e7ba218727e2ba7dd43": "5000156a616d2d626f6f7473747261702d7365727669636506302e312e32330a4170616368652d322e30012550617269747920546563686e6f6c6f67696573203c61646d696e407061726974792e696f3e28330070270000000020000200ffff1c00ffff1400ffff1800ffff0c00ffff2200ffff3c00ffff1a00ffff4600ffff1000ffff4200ffff0800ffff0e00ffff2a00ffff0600ffff1600ffff0400ffff1e00ffff3e00ffff4c00ffff2000ffff2600ffff4800ffff5000ffffe200ffffec00ffffec00ffffec00ffffec00ffffe400ffffec00ffffec00ffffec00ffffda00ffffde00ffffda00ffffda00ffffda00ffffec00ffffec00ffffec00ffffec00ffffdc00ffffe800ffffec00ffffec00ffffdc00ffff6a01ffffb001ffff9c01ffffa201ffff8601ffffc801ffffdc01ffffa801fffff001ffff9601ffffea01ffff8401ffff8e01ffffd601ffff7e01ffffa001ffff7c01ffffba01ffffe401ffff0202ffffc201ffffce01fffffa01ffff0802ffffce01ffffde01ffffd601ffffd801ffffd001ffff2a02ffff6a02ffffa602ffff8e02ffff5e02ffff7002ffffb202ffffba02ffffde02ffffd402ffffd402ffffc802ffff5202ffff7a02ffff6402ffff4a02ffffb803ffff5002ffff4602ffff3402ffffc002ffffd402ffffbc02ffff9a02ffffa603ffffd603ffffcc03ffffd203ffffbc03ffffe003fffff603ffffd403ffff0004ffffc403fffffe03ffffb403ffffbe03ffffe403ffffb003ffffd003ffffae03ffffda03fffffa03ffff0604ffffde03ffffe203ffff0404ffff0a04ffff0c05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff1605ffff1005ffff0e05ffff0e05ffff1405ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff1805ffff0e05ffff0e05ffff0e05ffff0e05ffff1205ffffb004ffffb004ffffb004ffffae04ffffac04ffffae04ffffac04ffffb004ffffb004ffffb004ffffb004ffffb004ffffac04ffffac04ffffb004ffffac04ffffb004ffffac04ffff7604ffff7604ffff7604ffff7404ffff7004ffff7404ffff7004ffff7604ffff7604ffff7604ffff7604ffff7604ffff7004ffff7004ffff7604ffff7004ffff7604ffff7004ffff3204ffff3204ffff3204ffff3004ffff2a04ffff3004ffff2a04ffff3204ffff3204ffff3204ffff3204ffff3204ffff2a04ffff2a04ffff3204ffff2a04ffff3204ffff2a04ffff2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7665632f737065635f66726f6d5f697465725f6e65737465642e72734661696c757265206f6e6c7920696e2063617365206f6620626164206d656d6f7279206f722062616420736572766963652049443b20626f74682061726520676f6f643b207165642f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f686f73745f63616c6c732e727363616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c75652f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f6d656d2e72732829436f6d7061637420656e636f646573206c656e677468696e666f6d656d6f4974657268617368636f72656b65797369746572626f6f7472656164536f6d65206f6620203c3d207472756573656564202d3e204e6f6e654974656d202020206461746144726f704f6b4572724f706171756556616c4b65797365746564323535313962616e646572736e61746368626c73636f64655f6c656e496d706f727465646e625f6974656d736175746f5f6163636d657461646174616578706f7274656420286279746573204578706f727465645472616e7366657262616c616e63653f696d706f72746564526573756c743a207472616e736665724c6f6f6b65645570707265696d61676550726f7669646564636f64655f686173686d696e5f6974656d5f6761736d696e5f6d656d6f5f676173656e646f776d656e74726567697374726174696f6e437265617465536572766963655570677261646564657374696e6174696f6e616d6f756e746761735f6c696d69745a6f6d62696679656a6563746f72456a65637474617267657444656c6574654974656d7373746f726167655f6974656d73536f6c696369746c656e466f726765744c6f6f6b7570736572766963656561676572496d706f72746974656d734578706f7274426c6573736d616e6167657241737369676e717565756544657369676e6174655969656c64436865636b706f696e7450616e696350726f76696465736572766963655f6964636f756e7452616e646f6d53746f72616765526566696e6552616e646f6d53746f72616765416363756d756c61746542656e63686d61726b4d61705b5d2f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d74797065732f7372632f66697865645f7665632e7273436c6f6e65646974556e72657175657374457870756e67654e6f74596574556e72657175657374737563636573735f61667465724e6f74596574457870756e6765556e70726f766964656473696e6365556e72657175657374656470726f76696465645f73696e6365756e7265717565737465645f73696e63655265726571756573746564756e7265717565737465645f617472657265717565737465645f73696e63656b6579496e7075744f7574707574426f6f747374726170205365727669636520526566696e652c206852657475726e696e672000000029207768656e20736c6963696e672060207769746820636f64655f68617368204372656174656420736572766963652020696e746f20616363756d756c61746572616e676520656e6420696e646578204578706f7274656420736c6963653a2001000000000000000000000000000000736572766963655f726567697374727944657369676e61746564206b65797320303132333435363738396162636465664561676572206c6f6f6b75702c20676f742073657276696365732f626f6f7473747261702d736572766963652f7372632f6c69622e7273426f6f747374726170205365727669636520416363756d756c6174652c2068204020244465636f64656420696e737472756374696f6e3a20436f64652070726f766973696f6e20726573756c74656420696e20417474656d74696e67207472616e736665722c206761733d5472616e73666572206f66202077697468202067617320726573756c74656420696e204661696c656420746f20637265617465215570677261646564214761732072656d61696e696e673a20417474656d7074696e67207472616e736665723a20205a6f6d62696679696e6720736572766963652e20456a6563746f723a20235a6f6d6269666965644661696c656420746f207a6f6d62696679202d20696e76616c696420636f64655f686173683f4661696c656420746f207a6f6d62696679202d206c6167676172647320696e2073746f726167652f6c6f6f6b75703f456a656374696e6720736572766963652023206974656d732064656c65746564207375636365737366756c6c792c20206b657973206e6f7420666f756e6444656c6574696e67206974656d3a204661696c656420746f2072656d6f7665206974656d3a20536f6c69636974656420206f66206c656e67746820726571756573746564517565727920726573756c743a20202866693a20756e7265717565737465644c6f6f6b75702c20676f74206c6f6f6b65645f7570426c6573736564207365727669636573206d3a20232c20613a20232e20763a20232e2061613a2041737369676e696e6720636f72652020746f2071756575652041737369676e6564214661696c656420746f2061737369676e215969656c646564206861736820436865636b706f696e7465642150616e696320696e737472756374696f6e2065786563757465642150726f766964656420707265696d61676520746f20496d706f72746564206461746120696d706f72742d4578706f7274656420206974656d73636f756e745f72616e646f6d5f73746f726167656974656d2d2d62616c616e6365206c6f77496e737472756374696f6e206e6f742068616e646c65643a207472616e736665727265647472616e736665722d636f756e745265636569766564207472616e736665722066726f6d202077697468206d656d6f20000b000000000000000c000000000000000b00000000000000070000000000000006000000000000000e000000000000000d000000000000006361706163697479206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72736d656d6f727920616c6c6f636174696f6e206f6620206279746573206661696c65642f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f737472696e672e72736120666f726d617474696e6720747261697420696d706c656d656e746174696f6e2072657475726e656420616e206572726f72207768656e2074686520756e6465726c79696e672073747265616d20646964206e6f742f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f666d742e7273292e2e3a5b63616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c756570616e69636b6564206174203a0a696e646578206f7574206f6620626f756e64733a20746865206c656e20697320206275742074686520696e646578206973203a20207b202c20207b0a2c0a7d207d28280a2c0a5d2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6e756d2e727330303031303230333034303530363037303830393130313131323133313431353136313731383139323032313232323332343235323632373238323933303331333233333334333533363337333833393430343134323433343434353436343734383439353035313532353335343535353635373538353936303631363236333634363536363637363836393730373137323733373437353736373737383739383038313832383338343835383638373838383939303931393239333934393539363937393839392f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6d6f642e727366616c736572616e676520737461727420696e64657820206f7574206f662072616e676520666f7220736c696365206f66206c656e67746820736c69636520696e64657820737461727473206174202062757420656e6473206174202f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f6d6f642e7273010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002020202020202020202020202020202020202020202020202020202020203030303030303030303030303030303040404040400000000000000000000005b2e2e2e5d626567696e203c3d20656e642028606279746520696e64657820206973206e6f742061206368617220626f756e646172793b20697420697320696e736964652029206f662060206973206f7574206f6620626f756e6473206f6620602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f756e69636f64652f7072696e7461626c652e7273000601010301040205070702080809020a050b020e04100111021205131c140115021702190d1c051d081f0124016a046b02af03b102bc02cf02d102d40cd509d602d702da01e005e102e704e802ee20f004f802fa04fb010c273b3e4e4f8f9e9e9f7b8b9396a2b2ba86b1060709363d3e56f3d0d1041418363756577faaaeafbd35e01287898e9e040d0e11122931343a4546494a4e4f64658a8c8d8fb6c1c3c4c6cbd65cb6b71b1c07080a0b141736393aa8a9d8d909379091a8070a3b3e66698f92116f5fbfeeef5a62f4fcff53549a9b2e2f2728559da0a1a3a4a7a8adbabcc4060b0c151d3a3f4551a6a7cccda007191a22253e3fe7ecefffc5c604202325262833383a484a4c50535556585a5c5e606365666b73787d7f8aa4aaafb0c0d0aeaf6e6fddde935e227b0503042d036603012f2e80821d03310f1c0424091e052b0544040e2a80aa06240424042808340b4e03340c813709160a08183b45390363080930160521031b05014038044b052f040a070907402027040c0936033a051a07040c07504937330d33072e080a0626031d080280d0521003372c082a161a261c1417094e042409440d19070a0648082709750b423e2a063b050a06510601051003050b5908021d621e48080a80a65e22450b0a060d133a060a06141c2c041780b93c64530c48090a46451b4808530d49070a80b6220e0a06460a1d03474937030e080a0639070a813619073b031d55010f320d839b66750b80c48a4c630d843010160a8f9b0582479ab93a86c68239072a045c06260a460a28051381b03a80c65b654b0439071140050b020e97f80884d6290aa2e781330f011d060e0408818c89046b050d030907108f6080fa0681b44c4709743c80f60a73087015467a140c140c570919808781470385420f1584501f060680d52b053e2101702d031a040281401f113a050181d02a80d62b040181e080f7294c040a04028311444c3d80c23c06010455051b3402810e2c04640c560a80ae381d0d2c040907020e06809a83d80411030d0377045f060c04010f0c0438080a0628082c04023e81540c1d030a0538071c06090780fa840600010305050606020706080709110a1c0b190c1a0d100e0c0f0410031212130916011704180119031a071b011c021f1620032b032d0b2e01300431023201a704a902aa04ab08fa02fb05fd02fe03ff09ad78798b8da23057588b8c901cdd0e0f4b4cfbfc2e2f3f5c5d5fe2848d8e9192a9b1babbc5c6c9cadee4e5ff00041112293134373a3b3d494a5d848e92a9b1b4babbc6cacecfe4e500040d0e11122931343a3b4546494a5e646584919b9dc9cecf0d11293a3b4549575b5c5e5f64658d91a9b4babbc5c9dfe4e5f00d11454964658084b2bcbebfd5d7f0f183858ba4a6bebfc5c7cfdadb4898bdcdc6cecf494e4f57595e5f898e8fb1b6b7bfc1c6c7d71116175b5cf6f7feff806d71dedf0e1f6e6f1c1d5f7d7eaeaf4dbbbc16171e1f46474e4f585a5c5e7e7fb5c5d4d5dcf0f1f572738f747596262e2fa7afb7bfc7cfd7df9a00409798308f1fcecfd2d4ceff4e4f5a5b07080f10272feeef6e6f373d3f42459091536775c8c9d0d1d8d9e7feff00205f2282df048244081b04061181ac0e80ab051f08811c03190801042f043404070301070607110a500f1207550703041c0a090308030703020303030c0405030b06010e15054e071b0757070206170c500443032d03010411060f0c3a041d255f206d046a2580c80582b0031a0682fd03590716091809140c140c6a060a061a0659072b05460a2c040c040103310b2c041a060b0380ac060a062f3180f4083c030f033e0538082b0582ff1118082f112d03210f210f808c04829a160b158894052f053b07020e180980be22740c80d61a81100580e109f29e033709815c1480b80880dd153b030a06380846080c06740b1e035a0459098083181c0a16094c04808a06aba40c170431a10481da26070c050580a61081f50701202a064c04808d0480be031b030f0d2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f756e69636f64652f756e69636f64655f646174612e7273617474656d707420746f20646976696465206279207a65726f617474656d707420746f2063616c63756c617465207468652072656d61696e646572207769746820612064697669736f72206f66207a65726f0000000003000083042000910560005d13a0001217201f0c20601fef2c202b2a30a02b6fa6602c02a8e02c1efbe02d00fe20369eff6036fd01e136010a2137240de137ab0e61392f18e139301ce14af31ee14e4034a1521e61e153f06a61544f6fe1549dbc615500cf615665d1a15600da215700e0a158aee2215aece4e15bd0e8615c2000ee5cf0017f5d00700007002d0101010201020101480b30151001650702060202010423011e1b5b0b3a09090118040109010301052b033b092a180120370101010408040103070a021d013a0101010204080109010a021a010202390104020402020303011e0203010b0239010405010204011402160601013a0101020104080107030a021e013b0101010c01090128010301370101030503010407020b021d013a01020201010303010407020b021c02390201010204080109010a021d0148010401020301010801510102070c08620102090b0749021b0101010101370e01050102050b0124090166040106010202021902040310040d01020206010f01000300041c031d021e02400201070801020b09012d030101750222017603040209010603db0202013a010107010101010208060a0201301f3104300a040326090c02200402063801010203010105380802029803010d0107040106010302c6400001c32100038d016020000669020004010a200250020001030104011902050197021a120d012608190b01012c03300102040202020124014306020202020c0108012f01330101030202050201012a020801ee010201040100010010101000020001e201950500030102050428030401a5020004410500024f04460b31047b01360f290102020a033104020207013d03240501083e010c0234090101080402015f030204060102019d01030815023902010101010c0109010e07030543010206010102010103040301010e025508020301011701510102060101020101020102eb010204060201021b025508020101026a0101010208650101010204010500090102f5010a040401900402020401200a280602040801090602032e0d010200070106010152160207010201027a06030101020107010148020301010100020b023405050317010001060f000c030300053b0700013f0451010b020002002e021700050306080802071e0494030037043208010e011605010f000701110207010201056401a00700013d040004fe0200076d07006080f00073657276696365732f626f6f7473747261702d736572766963652f636f6d6d6f6e2f7372632f6c69622e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e72732f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e72734572726f722f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6d706163742e72736372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f686f73745f63616c6c732e7273686f73742063616c6c2072657475726e7320636f727265637420747970653b207165646974656d206d75737420626520617661696c61626c653b2071656443616e6e6f74206661696c2065786365707420666f72206d656d6f7279206163636573733b2077652070726f76696465206120676f6f6420616464726573733b2071656443757272656e742073657276696365206d7573742065786973743b207165644661696c757265206f6e6c7920696e2063617365206f6620626164206d656d6f72793b20697420697320676f6f643b2071656450616e69632068616e646c65722063616c6c65642150616e6963206d6573736167653a2050726f746f636f6c20706172616d65746572732061726520696e76616c69646372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f737461727475702e7273756e6b6e6f776e206572726f7220636f64653a206372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f726573756c742e72734f7572206f776e2041504920696d706c2068617320726573756c74656420696e20737563636573732076616c7565207768696368206973206f7574206f662072616e67652e61737369676e64657369676e6174654f75744f66426f756e6473496e646578556e6b6e6f776e53746f7261676546756c6c426164436f72654e6f436173684761734c696d6974546f6f4c6f77416374696f6e496e76616c69646372617465732f6a616d2d74797065732f7372632f73696d706c652e727360726563656e745f626c6f636b5f636f756e7460206d6179206265206e6f206c6172676572207468616e2060426c6f636b496e6465783a3a4d415860202832353529607472616e736665725f6d656d6f5f73697a6560206d75737420626520657175616c20746f7220604d454d4f5f4c454e602028313238296076616c5f636f756e7420213d20636f72655f636f756e74202a2056414c535f5045525f434f524560202833296076616c5f636f756e746020646f6573206e6f7420646976696465206279206056414c535f5045525f434f524560202833296065706f63685f7461696c5f737461727460206d757374206265206c657373207468616e206065706f63685f706572696f646060726f746174696f6e5f706572696f646020646f6573206e6f742064697669646520696e746f206065706f63685f706572696f64606062617369635f70696563655f6c656e6020646f6573206e6f742064697669646520696e746f20605345474d454e545f4c454e602028342c313034296062617369635f70696563655f6c656e60206973206e6f74206576656e2e2e2e223078000b000000000000000c000000000000000b00000000000000070000000000000006000000000000000e000000000000000d0000000000000068030100000000008b000000000000003400000005000000000000000000000001000000000000000100000000000000e8010000000000003b040100000000005b00000000000000f5030000040000000000000000000000000000000000000001000000000000007206000000000000c10401000000000054000000000000002000000009000000701e0100000000005e00000000000000540400002b000000000000000000000020000000000000000100000000000000ba070000000000000000000000000000080000000000000008000000000000003601000000000000000000000000000080000000000000000100000000000000c007000000000000000000000000000008000000000000000800000000000000dc00000000000000e10e010000000000010000000000000071070100000000005500000000000000a600000020000000520f0100000000000200000000000000d31e01000000000060000000000000001401000036000000d31e0100000000006000000000000000150100001000000078080100000000001a000000000000009208010000000000010000000000000093080100000000000a00000000000000d008010000000000100000000000000040090100000000001200000000000000520901000000000025000000000000002600000019000000520901000000000025000000000000003a00000019000000520901000000000025000000000000003100000019000000f008010000000000100000000000000065050100000000000400000000000000520901000000000025000000000000002900000019000000520901000000000025000000000000003c0000001a00000052090100000000002500000000000000220000003400000052090100000000002500000000000000370000003400000077090100000000001e0000000000000095090100000000000300000000000000980901000000000002000000000000009a090100000000001500000000000000c008010000000000100000000000000092080100000000000100000000000000af090100000000001b00000000000000ca090100000000001800000000000000e2090100000000000c00000000000000ee090100000000000600000000000000f40901000000000011000000000000005209010000000000250000000000000072000000460000005209010000000000250000000000000060000000310000001f0a0100000000000f000000000000002e0a0100000000001500000000000000430a0100000000000100000000000000430a0100000000000100000000000000430a0100000000000100000000000000ff050100000000000800000000000000440a0100000000001e00000000000000c00a0100000000001200000000000000b008010000000000100000000000000001000000000000000000000000000000d20a0100000000001d00000000000000ef0a0100000000000f00000000000000fe0a0100000000000f000000000000000d0b0100000000001700000000000000240b0100000000000a000000000000002e0b0100000000000b0000000000000052090100000000002500000000000000bf0000003e00000052090100000000002500000000000000bd0000003600000052090100000000002500000000000000c20000003c000000420b0100000000000e00000000000000500b0100000000000600000000000000dd0e010000000000010000000000000052090100000000002500000000000000ca0000004000000052090100000000002500000000000000c900000035000000610b0100000000000c0000000000000052090100000000002500000000000000d000000042000000760b01000000000015000000000000008b0b0100000000000600000000000000910b0100000000000600000000000000970b01000000000006000000000000009d0b0100000000000f00000000000000ac0b0100000000000a0000000000000020090100000000001000000000000000d00b0100000000000d00000000000000ea0b0100000000001b0000000000000052090100000000002500000000000000fa00000019000000050c01000000000015000000000000004d0f010000000000020000000000000052090100000000002500000000000000d7000000420000001a0c0100000000000e00000000000000280c010000000000070000000000000052090100000000002500000000000000b50000002200000052090100000000002500000000000000b2000000510000002f0c0100000000000900000000000000380c010000000000060000000000000052090100000000002500000000000000ba0000004300000052090100000000002500000000000000080100002200000052090100000000002500000000000000040100004b000000520c0100000000000500000000000000570c010000000000010000000000000052090100000000002500000000000000110100001e000000630c0100000000001900000000000000520901000000000025000000000000004c0000005000000052090100000000002500000000000000850000004f00000052090100000000002500000000000000ff00000049000000950c010000000000170000000000000055050100000000000400000000000000ac0c0100000000000b0000000000000007060100000000000800000000000000520901000000000025000000000000002d0100000e00000052090100000000002500000000000000220100002f00000020210100000000002b210100000000003721010000000000422101000000000049210100000000004f210100000000005d2101000000000074060000000000001800000000000000080000000000000076060000000000007c060000000000008406000000000000f00c0100000000001100000000000000780d01000000000015000000000000008d0d0100000000000d00000000000000010d0100000000007700000000000000b60100000d0000009a0d0100000000007800000000000000ea010000170000000000000000000000000000000000000001000000000000007206000000000000680e01000000000075000000000000007e0200000e0000009a0d01000000000078000000000000008d0500001b00000001000000000000000000000000000000e00e0100000000000100000000000000e00e01000000000001000000000000001b0f01000000000020000000000000003b0f0100000000001200000000000000010000000000000000000000000000004d0f0100000000000200000000000000000000000000000018000000000000000800000000000000ce06000000000000d406000000000000d806000000000000620f01000000000078000000000000005b00000017000000a2100100000000007800000000000000a709000026000000a2100100000000007800000000000000b00900001a0000001f11010000000000120000000000000031110100000000002200000000000000e0080100000000001000000000000000311101000000000022000000000000005311010000000000160000000000000069110100000000000d00000000000000f3120100000000000e0000000000000059050100000000000400000000000000a00801000000000010000000000000000113010000000000010000000000000002130100000000000b000000000000000d130100000000002600000000000000d7050100000000000800000000000000331301000000000006000000000000000113010000000000010000000000000002130100000000000b00000000000000391301000000000016000000000000000113010000000000010000000000000076110100000000007800000000000000f20000002c0000004f1301000000000082000000000000001a000000360000004f1301000000000082000000000000000a0000002b0000007a1901000000000085000000000000004e000000280000007a1901000000000085000000000000005a00000016000000ff190100000000001900000000000000181a0100000000003900000000000000cb1d0100000000002c00000000000000cf00000017000000cb1d0100000000002c00000000000000ed00000013000000f71d01000000000079000000000000002b02000011000000000000000000000008010000000000000800000000000000800700000000000092070000000000009407000000000000d31e01000000000060000000000000002d0100003d000000701e0100000000005e00000000000000920400001e000000701e0100000000005e000000000000006c0400000f000000701e0100000000005e000000000000009e04000010000000701e0100000000005e00000000000000f70000000f000000331f0100000000002700000000000000c90000001c0000000000000000000000000000000000000001000000000000007206000000000000331f0100000000002700000000000000d10000002e000000331f0100000000002700000000000000db0000002e000000331f0100000000002700000000000000150100002b000000331f010000000000270000000000000021010000290000000000000000000000010000000000000001000000000000003a07000000000000331f0100000000002700000000000000e20200000a000000331f0100000000002700000000000000480300000a000000331f01000000000027000000000000006603000020000000331f0100000000002700000000000000a00300000a000000331f0100000000002700000000000000ae0300000e000000331f0100000000002700000000000000150400000a000000331f01000000000027000000000000002804000018000000331f0100000000002700000000000000260400000e000000331f01000000000027000000000000003e04000018000000331f01000000000027000000000000003c0400000e000000331f01000000000027000000000000005c0400000a000000331f01000000000027000000000000006a0400001c000000331f0100000000002700000000000000650400000e0000000000000000000000100000000000000008000000000000009a0700000000000071200100000000002400000000000000040000001400000095200100000000001400000000000000a92001000000000023000000000000002100000012000000cc200100000000004500000000000000a9200100000000002300000000000000560000000d000000a9200100000000002300000000000000820000000d0000006a210100000000001e000000000000009a0100000c0000006a210100000000001e000000000000009d0100000c0000002423010000000000010000000000000024230100000000000100000000000000252301000000000002000000000000002123010000000000030000000000000020210100000000002b210100000000003721010000000000422101000000000049210100000000004f210100000000005d2101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000ff030000000000005000000000000000004b000008000000f065010083e603c1c033f600000d01001c01003e0100c301000c02002302003a0200610200970200ae0200bd0200d30200ea02000103001803007e0300890300bf0300ca0300ef03008d0400d804007105008005008f0500a10500b30500d005000e0600250600300600660600710600a70600fc06000707003d07004807007e0700a607002c0800430800e508003e0a007a0a00a20a00980b00690c005d0d009a0d00060f00270f00840f006112006c1200db1300ef13000416003e1800191a00dd1b007b1c00911c00691d00bc1d00e01d00051e00261e00711e007f1e00851e00a81e00d11e00df1e001e1f00421f00531f00581f005e1f00931f00af1f00f41f00182000342000742000b32000c12000f020002121005021006321008b2100ba2100f121002022003322005622007b22008b2200ca2200ee2200ff2200042300252300d62300fa2300ab2400cf2400d424002025003c25007c2500a02500ae2500da2500112600402600532600822600b32600e22600f526002227005027007f2700922700e22700652800742800992800ba28000529001329001829004529007d2900bd2900e12900f22900212a00522a00812a00942a00a22a00cd2a00ee2a00062b00512b00622b00722c00fa2c001e2d00cd2d00f12d00a02e000c2f00152f002f2f005f2f00612f00772f00a52f00a72f00d52f00f22f000030002830004730005b30006f3000833000973000ab3000bf3000d33000e73000fb30000f31002331003731004b31005f31007331008731009b3100af3100c33100d73100eb3100ff31001332002732003b32004f32006332007732008b32009f3200b33200d432000633002e33005e3300753300ef33000034000a3400223400c134004c3500ef35008a3600323700c337003e3800733800813800ae3800e13800113900243900513900823900b13900c43900103a002c3a006c3a00903a00bb3a00bb3b00293d00cd3d00073e006b3e00933e00063f00293f00b73f00794000fd4100824200aa4200d04200fc42001843005d43008343009f4300df43001e44002c44005a4400764400bb4400e444000045004045007f45008d45000446003e46001947003847005b47006f4700824700e54700a94800e74800514a00a94a00cf4a00144b00914b00bb4b00d44b00ed4b00064c001f4c00384c004f4c008d4c00a14c00c04c00e54c001f4d00824d00a44d00c74d00e14d001b4e003d4e00604e007a4e00b44e00d34e00f84e00324f00a54f00a84f00bc4f00db4f000050003a50005c50007f5000995000d35000fb50001e51003751005151008b5100ad5100d05100ea5100245200435200685200a25200c15200e652002053003f5300605300a85300b45300d35300f85300325400545400775400915400cb5400ea54000f55004955006855008d5500c75500ef55001256002b56004556007f56009e5600bf5600075700135700325700575700915700735900885900b05900cf5900195a002a5a00855a00e65a000f5b00565b00b15b00155c003d5c00845c00a95c00ca5c00155d00235d00405d005c5d00805d00a15d00d95d00f55d00195e003a5e006f5e00b85e00c65e00255f00465f005f5f00b55f00d75f003560005a60007060008960000261005064007964009a6400ac6400bc6400d56400f46400496500af6500d665004066006866007c6600b366008167009d6d00f06d00906e00c07000f370004371008e7100067200387200ed72003e7300807300a57300c873003674005874007d7400be7400de7400ea74001d7500737600b47600cf7600e57600fa76007377009b7700a57700ed77008c7800af7800d07800e37800e67800077900167900227900397900557900b47a00397b00617b009e7b00ef7b00157c00217c00ad7d00d57d00067e00777f00977f009e7f00d88000f380000f8100ad8100dd81003b82006782006a8200a08200f682002283004d83007e8300b18300d48300f883001c84002784007484009c8400a78400e98400028500198500248500848500d985001586002886002b8600628600b88600e086006c8700908700cb8700688900908900aa8900b78900378b00668b00798b00c68b00ee8b00f88b005b8c00c58c001f8d008a8d00a48d00298e003c8e004f8e00988e00c08e00ca8e00d18e000390005090007890009b90000591005e9100779100f291000d92002a9200c49200ec92000993005793006d9300df9300529400729400c49400e49400f094005695007295007e9500ac95002196004996006c9600869600a79600e896005c9700789700849700bf9700cd9700ea9700059800339800859800a59800b19800799900a19900d09900319a000d9c00179c008a9c00b59c00cd9c00d79c00f49c00199d00a19d00c99d00db9d00319e00599e001b9f003b9f00479f005d9f007d9f00a89f00bb9f00cb9f001ca00049a00007a1003ea10061a100cda1004ba20072a2009ea200d8a20004a30056a30073a3008fa300c0a300e0a3000fa40029a40028a50009a700cca70017a8004daa006faa0092aa00b3aa00c5aa0003ab001bab0092ab00aaab00c0ab00deab0049ac0065ac0082ac0099ac0007ad0023ad00daad00f0ad003ab200bbb80024bc0042bc0054bc006cbc00e0bc0007bd00ebbd000bbe006fbe0070bf0013c10056c100a8c200bcc200e1c200f6c200bcc300e2c3000cc4001cc4003ac4007fc40093c400dcc400eec40004c50047c50062c50016c6003bc6006bc600aec600eec60015c70036c7005dc7009bc70019c8005dc800adc900c2c900c5c900e7c9001fca0026ca00f1ca0096cb00b7cb00c8cb00e1cb004dcc007dcc0087cc00bdcc007acd009bcd00adcd00aece00b9ce00dbce002ccf0044cf0089cf00f0cf0060d0006ad00098d000b1d00036d1008ad100d9d100ffd10024d20044d20062d2007cd2009fd200b2d200f8d2004cd30077d3007ad30093d3000dd40032d40077d400a8d400abd40000d50026d50056d5006dd50083d5009fd500b7d500cbd500dfd500f3d50007d6001bd6002fd60043d60057d6006bd6007fd60096d600add600c4d600dbd600efd60003d70017d7002bd70042d70058d70074d7008cd700a0d700b4d700cbd700dfd700f5d70011d80029d8003dd80054d80068d8007fd80093d800a7d800bbd800cfd800e3d800f7d8000bd90021d9003fd90059d9006dd90081d90095d900a9d900e6d90001da0010da003fda0002db000bdc0024dc0034dc0044dc007edc008fdc00a0dc00eedc000cdd001cdd002cdd0045dd0076dd002bde005adf00c7e000dde00013e10077e10090e100a7e100b6e100bee100fce1001be2002ee20052e2005ce20070e20080e20095e200c4e200e4e20043e30054e3007be300b8e300eee300fce30027e40037e4004be400c4e40018e500cce6001ee7004fe70087e7009ee700b1e700bee700e9e7000fe80016e8003de800dee80003e9000ae90073e90099e900e5e9002cea00a8ea00bdeb007dec00ccec00eeec0030ed005ded0073ed009ded00b6ed000eee0023f1003df1006bf100e3f10062f200f0f20048f30058f30097f300b1f300d7f4008ef500ddf700fcf7001ff80039f80061f800cef800e1f800f2f8000bf90037f900c2f90015fb0044fb0079fb00adfb0099fc00f3fc000bfd0034fd009ffd00b8fd0030fe004dfe0079fe00e4fe00fdfe00a8ff00c6ff00dbff00f0ff000500015200018900019d00013302013902014702014d0201530201a00301400401a00601c60601e00601f706010607012107013607014d07016607017808019009019e0901f209018f0a011b0b01770b019a0b01e50b01400c014f0c01570c01640c01900c01e30c01e90c01ee0c01070d011e0d012e0d01480d017e0d01850d018c0d01c50d01cc0d019d0e01350f016d0f01c80f01e71001a11101ca12017a1301e31301f31301d41401e41401ae1501bf1501f21601021701121701221701321701421701691701c91701331801971801cf1801e018010e1901661901801901d41901e61901511a01121b018a1b01d11c01aa1e01d71e01b11f013b20015420017320012b21013e21017f2101242201c32201e622016823018e2301e22301f82301042401b727010b28016f2801be28013f29014e29019d29011e2a01c32b01280abc000028f0db000028cadc00828908330a08330b01aca992009511d87b10207b15187b161082837c307c32017c36027c3c037c3a047b1a087c35057c34067c3a077b1a953a087b8a959af87b8a08510a49330b972208d4020a97661097cc18d46c0cd4ca0a975508821c08d45c0c9744108216976618d44606d46c0c97cc20d4ca0a7c3c089599f79533097b837b89087b7a08787c10018210208215188216109511287b7b3200490701281e03951168fb7b1090047b1588047b168004828908647551097f07648482877c7a959bff9578017b4b08330b177b48acab670797aa02330b000001c8ba0a81aac8ba0a7b14d000320a645764488210900482158804821680049511980428b10e140710000000000000807b57284507645c140a0000000000000080330b2114051800000000000080aeb958067bc5282307330805ac8902077c72017c78027b18507c76037c7c04959afb9578057b483308047b4a08ac8ae1067c7a057b1a407c7a067b1a287c7a077b1a387c7a087b1a30959af7957b097b4b7b4a08531a04ba067c78097b18687c780a7b1888007c780b7b18607c780c7b18209599f395770d7b477b490864477b12187b16107b1c0850100a330e01821ad000837752077a067b15b00083874921c8019519d0013302087b12d0014921d8015107d508330c9788209888201407f8ffffffffffff7f7b174882146882106082168800287c06645764488210900482158804821680049511980428d719645764488210900482158804821680049511980428480a9517300364483300129511d0fe7b1028017b1520017b16180164857b1764873300e00128930d018218300314070000000000000080aa78e2058218400382193803821a30037b58187b59107b5a08140709000000000000807b5728ca05645764488210900482158804821680049511980428841714070f000000000000807b5728a405330a0514081800000000000080aea9e4047b58288e0564576448821090048215880482168004951198042853116457644882109004821588048216800495119804281c13645764488210900482158804821680049511980428d008330809ac8928057c78027c7a017c7b037c7c04978808d4a80897bb1097cc18d4cb0bd4b8087c7a067c7b057c7c077c760897aa08d4ba0a97cc10976618d46c0cd4ca0a97aa20d4a8089599f79577097b477b4908140714000000000000807b577b580828e3049517300364485010241a378218300314070000000000000080aa78ba048218400382193803821a30037b58187b59107b5a08140705000000000000807b5728a20495173003644850102849fc8217300352077504821738037c184003140915000000000000807b597b570878581028720437075827034921c8014921d001014921d8015107f5037b19a80033066e783302204921f000017b15b00028118215b0008218b800c938085108cb01821ac801330730e67803c96a077b18b8007b13c800ae3772c83605ac656707330c5001d6c5083307520865079659500157095d07510a1896a750018218f0007b1830037b17400333070128053307017b1738039517e001330801951a300350102c352f8217e00152070a078217e8017b17f0007b17d0017b15c8018214d0003302208213c80001966550019a377b17c00033000133070128457b18f000c858079518e0013309500150102e1b2c019566017b16d8018213c800821be000d83b0795bb018218c000c8b808955550018214d00033022064b05118011aff7b15e8007b16f800824908ac29c60082459598e09556207b467b4808ac28b5009598c0955b407b4b7b4808330a9000aca8a200959a30ff9558d0007b487b4a08330c8000acca8d009597b0fe955950017b497b470895173003330980007b10e0007b1bd800501030822b013309209517b0036458501032732b013309209517d0036468501034642b01330990009517f0038218d800501036522b019517e0019518300333095001501038402b018217c8018216f8008218f0008215e800ab7600ff9517c80150103a9a318218d00128effe847701520754028215b0008216f8008218b800c9380852083dfe8217c801140800000000000000808219a800821af000ab87f5012838026457644882109004821588048216800495119804282d12951730036448501040e13f8218300314090100000000000080aa9813028218400382193803821a30037b58187b59107b5a08140712000000000000807b5728fb0195173003644850104432348218300314070000000000000080aa78d2018218400382193803821a30037b58187b59107b5a0814070a000000000000807b5728ba01330805ac89990164537c72017c76027c7b037c7c049595fb9578057b48330a207b4508aca51e01959adb9575257b457b4a08510a0f017c75259599da9577267b477b490851059203521501f9004921f8000128890395173003644850104a9e408218300314090100000000000080aa983c018218400382193803821a30037b58187b59107b5a08140716000000000000807b5728240195173003644850104e5b338218300314070000000000000080aa78fb008218400382193803821a30037b58187b59107b5a08140713000000000000807b5728e300140717000000000000807b5728d4009599df9577217b477b490864c695c7083309205010524d29019555f67b6528b3007c7a027c7b017c7c037d760497aa08d4ba0a97cc10976618d46c0cd4ca0a9599fb9577057b477b4908140803000000000000807b587a5a082878140718000000000000807b37286a3306330a010137085827036e88e6860814090d000000000000807b597b57087b5a107b5818955720951800013309b800501054c7280128328217c8018215b00051070d8217f000501056e42501140718000000000000807b57280f140718000000000000807b57018210900482158804821680049511980432008477015207f50182185882179800c9780851085602821bc80133070004e67809c9cb077b18587b199800ae9772c89c05acc5200398583c330752083303975904821a48ac9a29037b1cf800510b1397bb047b12e0017b1bf00133070828053307017b17e80195170001330808951ae001501058dd2a821700015207e202821208017b12d0017b15c801821ad000821468821060821cf800821998000197c5049a977b1790003303013307012893009766088217f000d467078218e8009788108219e000979918d49808d487078218a000978808d4b8088219b800979910821ba80097bb18d4b909d498088219c000979908d4c909821bd80097bb10821cc80097cc18d4cb0bd4b909979920d49808c852097a977b9808821cf80095cc017b1cd80182179800d8730795330182189000c8380895551082168800511801cefe82a808330903ae89bcfe7b1cf80082a97c9b7b1bf0007c96017c9b027b1be8007c9b037b1be000958bfc959c047bac7bab08541b07797c9b047c97057b17a0007c97067b17b8007c97077b17a8007c9c087c97097b17c0007c970a7b17d8007c970b7b17c8008217c80195990c7ba99588f47ba8088218f800ab78f7fe9517c8017b1380007b1c787b1b7050105a232a821b70821c7882138000821060821468821ad0008212d00128c8fe84770182168800821cf800510713fe8217c801510790008217d00150105cad23012882004921f80001976608d4260797bb1097cc18d4cb0bd47b05643695370c33092050105e512601140708000000000000807b677a65088217f80078672c28a6fd82980882997b18c0017b19b801821468821060821c2082168800283a9519d00182988217c8018299087b18b801140800000000000000807b19c001821c20ab87168215b000140718000000000000807b572852fd821850978808821918d49808821910979910821a0897aa18d4a909d49808821928979908821a40d4a909821a3897aa10821b3097bb18d4ba0ad4a909976a08d44a0a970b1097cc188216c0018215b801d4cb0bd4ba0a821cb0007bc6187bc510140b0b000000000000807bcb7bc7087ac8207ac9247aca2895c72c951830033309ac005010605b250128c6fc8217e8018218f0013309a82f01282cda0033073309a82f012821da003309a82f012818da0033073309a82f01280dda0082170801821810013309a82f0128fcd9003309a82f0128f3d9009511e07b10187b15107b1608828908140a0000000000000080330b0414051100000000000080acb91f828b7cb27cbc017cb4027cb3039596fc95ba047b8a7b860855162012140518000000000000807b75283b97cc08d42c0c974410973318d44306d46c0c7b1c9599dc95bb247b8b7b89089578083309206476648764a85010628a24017b6582177a6728018210188215108216089511203200951188fe7b1070017b1568017b166001828b083309027b1718ae9b15140718000000000000808218187b8728da028287140affffffff000000007c797b19107c79017b190895bbfe9577027b877b8b087b173a0760270349211001492118010149212001330551075902d2a7073302017b1a207b18302810821a2001821728c9b70751072902821a10017b172833090002e6970bc95a077b1b40aeb75fc8b506ac56880298683b330752088a0297690557098302510a1397aa057b1240017b1a500133070128053307017b17480195172801330801951a400150106465268217280152073502821230017b1218017b161001821830821b40019756059ab77b1738330c01330701283dc8620795184001330920642550106659230164528215509555017b152001821b40821c48d8bc0795cc01821938c8c90995662082183051190139ff82890853192047018287959ae09579207b897b8a087c78017c797c7a027c7b03978808d4980897aa1097bb18d4ba0ad4a8087c79057c7a047c7b067b1c487c7c07979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1840017c78097c79087c7a0a7c7b0b978808d4980897aa1097bb18d4ba0ad4a8087c790d7c7a0c7c7b0e7c7c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1848017c78117c79107c7a127c7b13978808d4980897aa1097bb18d4ba0ad4a8087c79157c7a147c7b167c7c17979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1850017c78197c79187c7a1a7c7b1b978808d4980897aa1097bb18d4ba0ad4a8087c791d7c7a1c7c7b1e7c771f979908d4a90997bb10977718d4b707821a1001d49707977720d487077b1758017b1550aba58ffe95171001501068f32582121801287efe847701821a205107e8fd82171001510780fd642750106a001f012874fd8217100114080000000000000080aa8763fd2806330201013a08602703821908979908821b10d4b909d2a808e68508140a0c00000000000080821b187bba7bb7087bb2107bb81879b92095b72295185a3309b60050106c6f210182107001821568018216600195117801320082173001821838013309a82f012831d60033073309a82f012826d6003309a82f01281dd600828308330a20aea311140818000000000000807b7832009511f0fd7b1008027b1500027b16f801828b9532e095bc207b8c7b82087cbc017cb67cb5027cba0397cc08d46c0c97551097aa18d45a0ad4ca0a7cbc057cb6047cb5067cb90797cc08d46c0c975510979918d45909d4c909979920d4a9097b1940017cb9097cba087cbc0a7cb60b979908d4a90997cc10976618d46c0cd4c9097cba0d7cbc0c7cb60e7cb50f97aa08d4ca0a976610975518d46505d45a0a97aa20d4a9097b1948017cb9117cba107cbc127cb613979908d4a90997cc10976618d46c0cd4c9097cba157cbc147cb6167cb51797aa08d4ca0a976610975518d46505d45a0a97aa20d4a9097b1950017cb9197cba187cbc1a7cb61b979908d4a90997cc10976618d46c0cd4c9097cba1d7cbc1c7cb61e7cb51f97aa08d4ca0a976610975518d46505d45a0a97aa20d4a909330c087b195801acc209017cb4207cb9217b19f8007cba227cb9237b1900017cb2247cb0257cb5267cb62795b9287b899539d87b8908531908da007b1af0007cb9287b19d8007cb9297b19b8007cb92a7b19c8007cb92b7b19c0007cbc2c7cb92d7b19d0007cb92e7b19e8007cb92f7b19e0009539d095ba307b8a330a087b8908aca98f007cb9307b1998007cb9317b19787cb9327b1988007cb9337b1980007cb9347b19b0007cb9357b1990007cb9367b19a8007cb9377b19a00095b9387b899539c87b8908531908497b1c707cb9387b19507cb9397b19307cb93a7b19407cb93b7b19387cb93c7b19687cb93d7b19487cb93e7b19607cb93f7b19589539c095ba407b8a330c80007b8908aec922140818000000000000807b780182100802821500028216f8019511100232007b107b14087b16107b15187b12207b1728953740ff95b9c0007b897b87089517600133098000648564a850106e921e019517e0016458501070a5338218e00114090100000000000080ab9814140818000000000000808217287b7828938215f800975508821708d457078218f00097881082160001976618d46808d487078218978808821920d49808821918979910821a1097aa18d4a909d49808978820d487077b1700018217b8009777088218d800d487078218c8009788108219c000979918d49808d487078218d000978808821970d498088219e800979910821ae00097aa18d4a909d49808978820d487077b17f80082177897770882189800d487078218880097881082198000979918d49808d48707821890009788088219b000d498088219a800979910821aa00097aa18d4a909d49808978820d487077b17f000821730977708821850d48707821840978810821938979918d49808d48707821848978808821968d49808821960979910821a5897aa18d4a909d498089788208219f001821ae801821be001d478067b1918017b1a10017b1b08019517200195184001330920501072181d018215289557589518600133098000501074041d018217080182181001821918017b577b58087b5910821720018218280182193001821a38017b57187b58207b59287b5a30821700017b57388217f8007b57408217f0007b57487b565028e8fd9511c07b10387b15307b1628828308140a0000000000000080330b2014040700000000000080acb32a01828a9532e095ac207b8c7b82087cac017ca57ca6027cab0397cc08d45c0c97661097bb18d46b0bd4cb0b7cac057ca6047ca5067ca90797cc08d46c0c975510979918d45909d4c909979920d4b9097b19087ca9097cab087cac0a7ca60b979908d4b90997cc10976618d46c0cd4c9097cab0d7cac0c7ca60e7ca50f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19107ca9117cab107cac127ca613979908d4b90997cc10976618d46c0cd4c9097cab157cac147ca6167ca51797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19187ca9197cab187cac1a7ca61b979908d4b90997cc10976618d46c0cd4c9097cab1d7cac1c7ca61e7ca51f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b909330b087b1920aeb211140818000000000000807b7828767ca9217cab207cac227ca623979908d4b90997cc10976618d46c0cd4c9097cab257cac247ca6267ca52797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b199539d895aa287b8a7b8908957a08951808330920647664a714050700000000000080501076ef1a017b6582177b67280182103882153082162895114032009511a87b10507b15487b1640828208330b20140a01000000000000807b1a18acb25901828a9523e095ac207b8c7b83087cac017ca67ca5027cab0397cc08d46c0c97551097bb18d45b0bd4cb0b7cac057ca6047ca5067ca90797cc08d46c0c975510979918d45909d4c909979920d4b9097b19207ca9097cab087cac0a7ca60b979908d4b90997cc10976618d46c0cd4c9097cab0d7cac0c7ca60e7ca50f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19287ca9117cab107cac127ca613979908d4b90997cc10976618d46c0cd4c9097cab157cac147ca6167ca51797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19307ca9197cab187cac1a7ca61b979908d4b90997cc10976618d46c0cd4c9097cab1d7cac1c7ca61e7ca51f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b909330c087b1938acc3327ca0207ca9217ca6227cab237ca5247b15107ca4257ca5267b15087ca5277b1595a3287b839525d87b85085515081b8218189588177b78018210508215488216409511583200979908d4090997661097bb18d46b0bd4b909974408821b10d4b40b821c0897cc108216976618d46c0cd4cb0b97bb20d4b9097b19107ca9297cab287cac2a7ca52b979908d4b90997cc10975518d45c0cd4c9097cab2d7cac2c7ca52e7ca62f97bb08d4cb0b975510976618d46505d45b0b97bb20d49b059529d095aa307b8a7b8908957a08951820330920647664a7501078b518018217187b678217107b67287b6530284fff9511c07b10387b15307b1628828308140a0000000000000080330b2014040600000000000080acb32a01828a9532e095ac207b8c7b82087cac017ca57ca6027cab0397cc08d45c0c97661097bb18d46b0bd4cb0b7cac057ca6047ca5067ca90797cc08d46c0c975510979918d45909d4c909979920d4b9097b19087ca9097cab087cac0a7ca60b979908d4b90997cc10976618d46c0cd4c9097cab0d7cac0c7ca60e7ca50f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19107ca9117cab107cac127ca613979908d4b90997cc10976618d46c0cd4c9097cab157cac147ca6167ca51797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19187ca9197cab187cac1a7ca61b979908d4b90997cc10976618d46c0cd4c9097cab1d7cac1c7ca61e7ca51f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b909330b087b1920aeb211140818000000000000807b7828767ca9217cab207cac227ca623979908d4b90997cc10976618d46c0cd4c9097cab257cac247ca6267ca52797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b199539d895aa287b8a7b8908957a08951808330920647664a71405060000000000008050107ada16017b6582177b6728018210388215308216289511403200951168ff7b1090007b1588007b168000828c08140a0000000000000080330b0414050200000000000080acbca50082837c397c34017c3b027c3a037b1a7895c6fc953a047b8a3302087b8608ac2683007c3a047b1a587c3a057b1a387c3a067b1a487c3a077b1a407c3a087b1a707c3a097b1a507c3a0a7b1a687c3a0b7b1a6095300c7b80649a95c9f47b89085319084264b07c390c7b19207c390d7b197c390e7b19107c390f7b19087c3b107c39117b19187c39127b19307c39137b192895c9ec9532147b827b89085529800024140518000000000000807b7501821090008215880082168000951198003200953994007b8995c96cff7b890864bc974b08d4ab08970910821478974418d49409d498087b1878821a3897aa08821858d4a808821948979910821a4097aa18d4a909d49808821950979908821a70d4a909821a6897aa10821b6097bb18d4ba0ad4a909979920d498087b18708218978808821920d49808821910979910821a0897aa18d4a909d49808821918979908d4c909821a3097aa10821b2897bb18d4ba0ad4a909979920d498087b18689578083309800064766487642850107c1615017b658217707b6788008217687b6790008217787a6798002817ff9511e07b10187b15107b1608828908140a0000000000000080330b0414050400000000000080acb91f828b7cb27cbc017cb4027cb3039596fc95ba047b8a7b860855162012140518000000000000807b75283b97cc08d42c0c974410973318d44306d46c0c7b1c9599dc95bb247b8b7b89089578083309206476648764a850107e7814017b6582177a6728018210188215108216089511203200951178ff7b1080008089348277849a10520a3e8499205209757c7a531a64c700648796a82998880c968964bf9a0a97aa3998aa383309da0f01c8a9097c9a017c9b3309781a02781b01648a28a1007c77330a810095197f0164ab847a0f98770488a20a86ac3095aa57db2c0a789a9599ff95baff5207e495b7fe56278000bb009aab8100641ac87a0a283e7c77330a810095197f0164ab847a0f98770488a20a86ac3095aa37db2c0a789a9599ff95baff5207e495b7fe562780008b009aab8100641ac87a0a01330902648733082523015020820069ce0082108000951188003200330902551a0a1364870186a830641ac89a0a78a82821648797aa3998aa383308da0f01c8a8087c89017c8878190278180133090101641ac89a0a859b0333080133095020840016ce00821080009511880032003309002d0133088000285ecc003309002d01330880002851cc009511d87b10207b151864858288288279825720828a187b193308720801330906b4a00288007b15087817104611113308e40601330bee00951708330905641a50208a0061d9007c19117c1810d4890751094284880152083c8217087c783484880452081a827828827720828a1833085a0f0133090233008c00320a827828827720828a183308590f01330901b4a0028c008477018210208215189511283200827728ee09019511c07b10387b15307b162864858288288276825720828a183308013309b4a00292007b15087817104901461111017b16189566087b16203305d20064179518183309d20050209400d0dd006417951820645950209600c2dd0082187c19108e87d49707510859849901520953521801367c17118215085107307c5734847704520727825828825720828a1833085f0f01330901b4a00298006478330701510809281c82150801825828825720828a183308dd0e01330901b4a0029a008477018210388215308216289511403200827728013c827728aa04019511d07b10287b152082798297140a00000000000000806485aba78900825828825720828a187b1933087f0501330903b4a002a2007b15107817184911084611193309200195170864185020a400f2dc008218087c19188e87d497075108f9008499015209f200521801b9007c17198215105107b2007c57348477045207a800825828825720828a1833085f0f01330901b4a002a60064783307015108890028b600825828825720828a187b1933087d0501330902b4a002a8007b15107817184911084611193309860095170864185020aa006ddc008218087c19188e87d4970751087484990152096e521801517c171982151051074b7c5734847704520742825828825720828a1833085f0f01330901b4a002ac006478330701510824283782151001825828825720828a183308dd0e013309013300ae00320a82151001825828825720828a183308dd0e01330901b4a002ae0084770182102882152095113032009511c07b10387b15307b1628827764858288288276088277107b1708825720828a183308e10e01330901b4a002b2007b1510821508781718461119510535975504330790007b17019567107b17087b162095171095182082195020b400c9dc009555f08216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002b6006478016487821038821530821628951140320082777c779777033309b80c01c879098299330a482b01828b28c8a707827a82872082bc1864a8320c9511c07b10387b15307b1628827764858288288276088277107b1708825720828a183308e10e01330901b4a002bc007b151082150878171846111951053b975703975505c975053307f6007b17019567187b17087b162095171095182082195020be00f9db009555e88216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002c000647801648782103882153082162895114032009511c07b10387b15307b162864858288288276825720828a183308013309b4a002c4007b15087817104901461111017b16189566087b16203309d60064179518185020c60026da003309d20064179518205020c80016da0082187c19108e87d49707510859849901520953521801367c17118215085107307c5734847704520727825828825720828a1833085f0f01330901b4a002ca006478330701510809281c82150801825828825720828a183308dd0e01330901b4a002cc008477018210388215308216289511403200827728ff0c82777c79648751090f33085d05013309042833cb0033081a11013309052827cb00951178ff7b1080008089348277849a10520a1584992052094d82108000951188002832e500827b3309810095177f01649a98bc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000689a9b8100641ac87a0a283f827b3309810095177f01649a98bc0484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520ce295a7fe56278000379a9b8100641ac87a0a01330902648733082523015020d400fcc700821080009511880032003309002d01330880002844c6003309002d01330880002837c600951178ff7b1080008089348277849a10520a1584992052094d82108000951188002896cf00817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000689a9b8100641ac87a0a283f817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520ce295a7fe56278000379a9b8100641ac87a0a01330902648733082523015020d80027c700821080009511880032003309002d0133088000286fc5003309002d01330880002862c500827728593582798297140a0000000000000080aba716828928828720829c183308690501330904320c9511d07b10287b1520828a2882872082ab187b19330a51050133090464853308510501b4b002de007b15107817184911084611193309f60095170864185020e00065d7008218087c19188e87d49707510859849901520953521801367c17198215105107307c5734847704520727825828825720828a1833085f0f01330901b4a002e2006478330701510809281c82151001825828825720828a183308dd0e01330901b4a002e40084770182102882152095113032009511c07b10387b15307b1628648582882882768277087b1708825720828a183308e10e01330901b4a002e8007b151082150878171846111951053b975703975505c975053307f6007b17019567187b17087b162095171095182082195020ea00d9d7009555e88216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002ec00647801648782103882153082162895114032009511c07b10387b15307b1628827764858288288276088277107b1708825720828a183308e10e01330901b4a002f0007b1510821508781718461119510535975505330704017b17019567207b17087b162095171095182082195020f20037d7009555e08216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002f400647801648782103882153082162895114032009511c07b10387b15307b162882777b1808828928827508827610828720829a183308e10e01330901b4a002f8008218087b1810781718461119510632330780007b17019557017b17087b152095171095182082195020fa009ad6009566ff8215085206e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002fc00647801648782103882153082162895114032009511807b10787b15707b1668827664853307800033081f6469015107137d9a9599019577ffada8f5510af2282a3309800001c876087c8851080b957701ab97f528056479016417646850200001d5cd00821751073f7b164911080864177b17583307aa077b17603307d032017b1728491130029517587b1738825720825828491140014911489519283300020128cec9008217088218107b17187b18209517187b17583307b8077b17603307b032017b1728491130024911489519588257208258287b193849114001951928502002018fc9008210788215708216689511800032009511d87b10207b151864858288288279825720828a187b1933086d0501330904b4a00206017b150878171046111133086a0801330bce00951708330903641a50200801cdce007c19117c1810d4890751094284880152083c8217087c783484880452081a827828827720828a1833085a0f0133090233000a01320a827828827720828a183308590f01330901b4a0020a018477018210208215189511283200827728ba219511b87b10407b15387b16308277648582882882768277087b17825720828a183308350501330904b4a00210017b15107817184911084611198217c967079877031408abaaaaaaaaaaaaaaca87077b16207b17283309e6009517089518205020120124d3008218087c19188e87d49707510859849901520953521801367c17198215105107307c5734847704520727825828825720828a1833085f0f01330901b4a00214016478330701510809281c82151001825828825720828a183308dd0e01330901b4a002160184770182104082153882163095114832009511c07b10387b15307b1628827764858288288276088277107b1708825720828a183308e10e01330901b4a0021a017b15108215087817184611195105359755043307c2007b17019567107b17087b1620951710951820821950201c0198d3009555f08216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a0021e01647801648782103882153082162895114032003309026487330815050128aac3009511d07b10287b15207b161864868288288275826720828a189558087b1833086d0801330905b4a00224017b16087817104611113308610501330b3601951708330904645a5020260199cc003308b70501330b8000951708330908641a5020280181cc007c19117c1810d4890751094284880152083c8217087c783484880452081a827828827720828a1833085a0f0133090233002a01320a827828827720828a183308590f01330901b4a0022a018477018210288215208216189511303200951178ff7b108000808934849a10520a6e8499205209aa007e79989a04532a7102fd00648796a84703988813968a1027bfa909979a3098aa3296aa7b1498ab1198aa1084aafe0796bb64bfb909979931989930330bda0f01c8ba0a7cac7caa01c8b9097c9b017c99781a02781c01781b04781903330b01648928de007e7b3309810095177f01649a97b930989c3484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520cdf95a7fe56278000f5009a9b8100641ac87a0a28437e7b3309810095177f01649a97b930989c3484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520cdf95a7fe56278000c0009a9b8100641ac87a0a013309026487330825230150202e0160bf0082108000951188003200330b05330ada0f015319642e989b0296bb7b1498bc1196c764bf7909979931989930c8a9097c97017c99781704781903330b0364c90155190a1664870195bbff6418c8b80886993078892820648797993198993095bbfec8a9097c98017c99641ac8ba0a78a80178a901641ac8ba0a9abb05330801330950203001d8be00821080009511880032003309002d01330880002820bd003309002d01330880002813bd00951178ff7b108000808934849a10520a1584992052094d82108000951188002874c600817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000689a9b8100641ac87a0a283f817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520ce295a7fe56278000379a9b8100641ac87a0a01330902648733082523015020340105be00821080009511880032003309002d0133088000284dbc003309002d01330880002840bc00951178ff7b108000808934849a10520a1584992052094d82108000951188002868da00827b3309810095177f01649a98bc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000689a9b8100641ac87a0a283f827b3309810095177f01649a98bc0484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520ce295a7fe56278000379a9b8100641ac87a0a01330902648733082523015020380132bd00821080009511880032003309002d0133088000287abb003309002d0133088000286dbb009511d87b10207b15187b1610647a82771408ffffffffffffff7fc8870988991714080000000000000080d378089398330916ac89fc009788023309600001c898088188c89808320882a70852071a28e00082a70814080000000000000080d48707aa87cd0082a71028b60082a6187b1a82a7107b170851069d00821508955508280c9566ff95551851068b008257f85107f3825750204001c9fe0028e714080000000000000080d48707aa87840082a708286d82a6187b1a82a7107b1708510655821508955508280b9566ff9555185106448257f85107f482575020460183fe0028e882a6187b1a82a7107b1708510625821508955508280b9566ff9555185106148257f85107f4825750204a0153fe0028e882178278088217085108138210208215188216109511282833fe0082102082151882161095112832009511d07b10287b15207b16186485828828825a20828b1864763308e10e0133090164a7b4b00250017b157817084611097b16103305800064179518103309800050205201a3cd009567017b171064179518106459502054018fcd009567027b171064179518106459502056017bcd009567037b1710641795181064595020580167cd009567047b17106417951810645950205a0153cd009567057b17106417951810645950205c013fcd009567067b17106417951810645950205e012bcd009567077b1710641795181064595020600117cd009567087b1710641795181064595020620103cd009567097b17106417951810645950206401efcc0095670a7b17106417951810645950206601dbcc0095670b7b17106417951810645950206801c7cc0095670c7b17106417951810645950206a01b3cc0095670d7b17106417951810645950206c019fcc0095670e7b17106417951810645950206e018bcc0095670f7b1710641795181064595020700177cc009567107b1710641795181064595020720163cc009567117b171064179518106459502074014fcc009567127b171064179518106459502076013bcc009567137b1710641795181064595020780127cc009567147b17106417951810645950207a0113cc009567157b17106417951810645950207c01ffcb009567167b17106417951810645950207e01ebcb009567177b17106417951810645950208001d7cb009567187b17106417951810645950208201c3cb009567197b17106417951810645950208401afcb0095671a7b171064179518106459502086019bcb0095671b7b1710641795181064595020880187cb0095671c7b17106417951810645950208a0173cb0095671d7b17106417951810645950208c015fcb0095671e7b17106417951810645950208e014bcb0095661f7b1610641795181064595020900137cb003307017c180852081b8217827828827720828a183308610f01330901b4a002920182102882152082161895113032009511c07b10387b15307b162864859888205208b1006476645764987b191850209401132e821818510574975703975505c97507956510282d7b18827708c88707821808646950209601c6fd008218188217c876067b86108217109577e895551851073b7b1710825698672052073e8257f87b1708646750209801bb2d8217188278108279c98909ae69b1646950209a0133d800821718827810289f82103882153082162895114032003307170501330ab02301330be82301330816951927283ec8003307170501330ab02301330be823013308169519272825c8009511c87b10307b15287b162082ab0864866478510b3c82a71051073b7b177b181882a77b17107b1908649850209c0145f6005107406475821810821950209e01f4fc008217105020a0011efa00282f51091f280551091a7b1818649864955020a20112f600821818645964752817646528133305821908821818280982190882181801885701db55067b86087b89107b8782103082152882162095113832009511b87b10407b15387b16306475827a95a70151077c97a801e4780898893c330752096e330904e498069769043308f1140bf8ffffffffffff7fac9b55510a1482570897aa047b17187b1a2833070828053307017b17206417330808951a185020a40102ff82175207198217087b57087b5682104082153882163095114832008217088218103309c02f0128edb0003309c02f0128e4b0009511b87b10407b15387b16306475827a95a70151076f97a801e4780898893b3307520961330904e49806976905570955510a1482570897aa057b17187b1a2833070128053307017b17206417330801951a185020a60177fe82175207198217087b57087b5682104082153882163095114832008217088218103309c02f012862b0003309c02f012859b0009511b07b10487b15407b16386476827a95a701648c51077797a801e47807330804e487053308d800d68509330752095f9659d8003308f1140bf8ffffffffffff7fac9b5f7b1c510a1582670896a8d8007b17207b183033070828053307017b1728951708330808951a205020a801d4fd82170852071f8217107b67087b65821048821540821638951150320064c928c7af00821710821818821c64c928b9af0064c928b3af009511b87b10407b15387b16306475827a95a70151077c97a801e4780898893a330752096e330904e498069769063308f1140bf8ffffffffffff7fac9b55510a1482570897aa067b17187b1a2833070828053307017b17206417330808951a185020aa0139fd82175207198217087b57087b5682104082153882163095114832008217088218103309c02f012824af003309c02f01281baf009511b87b10407b15387b16306475827a95a70151078c0097a801e47807330804e48706330718d676083307520874976803976905c989093308f1140bf8ffffffffffff7fac9b5b510a1a82570897a80397aa05c98a0a7b17187b1a2833070828053307017b17206417330808951a185020ac0191fc82175207198217087b57087b5682104082153882163095114832008217088218103309c02f01287cae003309c02f012873ae009511b87b10407b15387b16306475827a95a70151077597a801e47807330804e4870633085001d68609330752095d96695001570956510a1582570896a850017b17187b182833070128053307017b17206417330801951a185020ae0100fc82175207198217087b57087b5682104082153882163095114832008217088218103309c02f0128ebad003309c02f0128e2ad009511b87b10407b15387b1630c88909ac895464758277977801330a08e4a909e498065706415107108258087b18187b1728330701017b17206417330801951a1864695020b00185fb82175207248217087b57087b56821040821538821630951148320033073309302f012874ad008217088218103309302f012865ad00827a08827910648764a828cbb5009511c07b10387b15307b162864858288288276088277107b1708825720828a183308e10e01330901b4a002b6018218087b15107817184611195108389685d80033070c017b17019567d8007b17087b162095171095182082195020b80109c500955528ff8216085205e07c17188477010133080152071f821710827828827720828a183308610f01330901b4a002ba01647801648782103882153082162895114032009511c07b10387b15307b162864858288288276088277107b1708825720828a183308e10e01330901b4a002be017b15108215087817184611195105359755043307c2007b17019567107b17087b162095171095182082195020c00168c4009555f08216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002c201647801648782103882153082162895114032008279140a0000000000000080aba916828928828720829c183308690501330904320c9511d07b10287b1520828928828a20829c187b17330b51050133090464a764853308510501b4c002c6017b15107817184911084611193309f60095170864185020c80175c2008218087c19188e87d49707510859849901520953521801367c17198215105107307c5734847704520727825828825720828a1833085f0f01330901b4a002ca016478330701510809281c82151001825828825720828a183308dd0e01330901b4a002cc018477018210288215209511303200951110ff7b10e8007b15e0007b16d80064857b1764875020ce013cd5008377510724140700000000000000808218017b878210e8008215e0008216d8009511f0003200838749219000951698004911280849219800084921a000510710044911389788209888201407f8ffffffffffff7f7b17086487140800000000000000807b18107b154028228217387b173828067b1738018217208477015207a103821718c987075107ea03821a90007b17183308aa02e68709821838c98a077b1930ae9742c89805ac852604330718d67508330752083604975803975905c98909821b08ac9b2604510a2097a70397aa05c97a0a8217287b17c0007b1ad0003307082809649828383307017b17c8009517a800330808951ac0005020d00108f88217a8005207d5038217b0007b17287b1798007b159000821540821830018253083302491120018217382838952201821938979703979805c978086497821928c898087b8a8219687b89087b8b109577017b17a000821830d882097b1920aa8215ff7b1738510312ff82577c7a9536ff9579017b597b5608491168017b1248510a6f522aff00820053130985027c78027c79017c7a037c7b04978808d4980897aa1097bb18d4ba0ad4a8087c79067c7a057c7b077c7c08979908d4a90997bb1097cc18d4cb0bd4b909979920d489099533f79577097b17607b577b5308989720951698008218305107c7002880fe646364ab9516980082179000821838aa78a201282bff6ca857080b33054921c000284a84874033050151073a84872033050251073184871033050351072884870833050451071f84870433050551071684870233050651070d8487013305075207a6024921c000ac5683019517c0007b187864987b197064595020d201cbf300821248c95603821770c857078219407b17607b978557079b77ff821878e078078218c0007b9308975503cf57076495c87809989720951698008218305207c1fdac93bdfd5109de006498330a33064911680101330700407b1858e67805c96a077b1370ae5721c85609ac69db015709d701510a188217687b17c0007b1ad000330701280ac8560b28353307017b17c8009517a800330801951ac0007b19785020d401f6f58217a8005207a7018217b0007b1768821b7864ba82137001ac6b7b01ac539d00821768c86707821660646864597b1a507b1b785020d601edf200821b78821a50821370821248c95303c856068217407b16607b76821858c958087b730864b6520850ff82154095169800821710aa7a820082179000821838ab78a2fd2811330a330b82179000821838ab7891fd95179000643664a57b1b785020d80158f8821b78645a646395169800821540821248821798007b17282864fd510a238217685020da0195ef0082172084770182154095169800821830510799fc2832821540951698008217388218307b17382879fc8217388218307b1738286dfc82172084770195169800821830510767fc821798007b1778821538510534821678956608280b9555ff9566185105238267f85107f482675020dc0122ef0028e882680882697b1888007b1980002836821790005107a5fb8217785020de01ffee002897fb8268821790008269087b188000140800000000000000807b198800aa8779fb8218880082198000821a7ba81064a87ba9082870fb3309902f01646764b828c4aa0033073309a82f012860a6008217b0008218b8003309a82f01284fa60033073309a82f012844a6008217b0008218b8003309a82f012833a6003309a82f01282aa6003307782f012807b20083775107121407000000000000008082182893027b151883874921d0009519d800330b087b1b407b1bd8004921e00051073002330a9788209888201407f8ffffffffffff7f7b1708330407821318281384770152076502821810c9080851081102821bd00033070004e67800c9ab077b18107b1020ae0770c80a05aca5640298583c330752086602975904821c08ac9c5c0264a6510b1697bb048217407b1700017b1b100133070828053307017b1708019517e800330808951a00015020e2014af38217e800520708028217f0007b17407b17d8007b15d000821318646a3304078210200197ac049a077b173833020133070128b4009755088217a000d4570782189000978810976618d46808d48707821898009788088219b800d498088219b000979910821ba80097bb18d4b909d49808978820d48707821848978808821968d49808821958979910821b5097bb18d4b909d49808821960979908821b8000d4b909821b7897bb10821c7097cc18d4cb0bd4b909979920d49808821940821c8800c8c9097b977b980895aa017b1ae000d80207952201821838c8280895cc10330901aa98b4fe823808ae84a6fe82397c9b7b1ba0007c95017c9b027b1b90007c96037c9b047b1bb8007c9b057b1b98007c9b067b1bb0007c9b077b1ba800959b087b3b958bf87b3b08aeb466fe7c97087b17687c97097b17487c970a7b17587c970b7b17507c970c7b1780007c970d7b17607c970e7b17787c970f7b17708217d0009599107b399588f07b38087b1c8800ab7ac4fe9517d0007b1a307b12285020e40165f2821228821020330407821a308213188217d8007b1740289afe82980882997b18c8007b19c00028259519d80082988217d0008299087b18c000140800000000000000807b19c800aa8778fd8218c8008219c000821a7ba81064a87ba908017b878210280182152001821618019511300132008217d000510749fd8217d8005020e601a6eb00283afd8217f0008218f8003309a82f012842a30033073309a82f012837a3003309a82f01282ea3007c779777033309b80c01c879098299330a482b01828b28c8a707827a82872082bc1864a8320c9511d07b10287b15207c7964855219078c00825828825a20828b187b1733087d050133090264a7b4b002ec017b15107817184911084611193309200195170864185020ee0189b9008218087c19188e87d497075108fb008499015209f400521801bb007c17198215105107b4007c57348477045207aa00825828825720828a1833085f0f01330901b4a002f001647833070151088b0028b800825828825a20828b187b1733087f050133090364a7b4b002f2017b15107817184911084611193309b80095170864185020f40102b9008218087c19188e87d4970751087484990152096e521801517c171982151051074b7c5734847704520742825828825720828a1833085f0f01330901b4a002f6016478330701510824283782151001825828825720828a183308dd0e013309013300f801320a82151001825828825720828a183308dd0e01330901b4a002f80184770182102882152095113032009511d07b10287b15207c79648551098e00825828825a20828b189577017b1733087f050133090364a7b4b002fc017b15107817184911084611193309b80095170864185020fe012bb8008218087c19188e87d497075108fe008499015209f700521801be007c17198215105107b7007c57348477045207ad00825828825720828a1833085f0f01330901b4a0020002647833070151088e0028bb00825828825a20828b189577087b1733087d050133090264a7b4b00202027b15107817184911084611193309d200951708641850200402a1b7008218087c19188e87d4970751087484990152096e521801517c171982151051074b7c5734847704520742825828825720828a1833085f0f01330901b4a00206026478330701510824283782151001825828825720828a183308dd0e0133090133000802320a82151001825828825720828a183308dd0e01330901b4a002080284770182102882152095113032009511887b10706482957c80009578a0009579c0007b17683307fe007b17609517687b1758491150083307c705017b17483307c6077b17407b1938491130033307a405017b17283307ba077b17207b18184911100c33089805017b18083308820501330a91050133090f330b077b17642750200c0229b9008210709511783200828a08510a559511d87b10207b1518828b7cb995aaff95bb017b8b7b8a08510948330a01aba94e6475641750200e024cc800821814070000000000000080ab784c140701000000000000807b578210208215189511283200140801000000000000807b78320014080000000000000080280d14080100000000000080017b78821020821518951128320082171082180882197b57107b58087b5982102082151895112832009511d07b10287b15207b16188279140a00000000000000806485aba91b8258108257aa877c825708c8870746079588017b5810285d8258108259aa8976825908c88909827610330a01789ac8a8089869207b5810520982008277087b17086467645850201002001a8258108257c98707ac67567b18825708c88707821808646950201202bce9008217c876067b56100182102882152082161895113032003309016457502014024dc4008258102878ff330901647664575020160239c4006467825810287aff645764695020180226c400825810289f3307170501330ab02301330be823013308169519172842b400951148ff7b10b0007b15a8007b16a0008289087b1714070100000000000080510942828a7cab9599ff95aa017b8a330a017b8908aaab22520b2a6487648550201a0212c800837751073014070100000000000080280d140700000000000000800182187b878210b0008215a8008216a0009511b80032007b151083874911509519584911580149116051075202330697882098882049112801821b10281384770152075702821808c9c80851080f02821a5033070002e6780cc96a077b18087b1c20aec761c8c605ac65530298583b33075208550297590557094e02510a1697aa058217287b1780007b1a900033070128053307017b178800951768330801951a800050201c021aeb821768520700028217707b17287b17587b1550821b10821c20019765059ac77b1718330a01330701283c821728c857079518800033092050201e020de8008216389566017b1660821c20821a30d8ca0795aa01821818c8a808955520821b105118013dff82b808330920ac982bff82b79588e09579207bb97bb8087c78017c797b1a307c7a027c7b03978808d4980897aa1097bb18d4ba0ad4a8087c79057c7a047c7b067c7c07979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1880007c78097c79087c7a0a7c7b0b978808d4980897aa1097bb18d4ba0ad4a8087c790d7c7a0c7c7b0e7c7c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1888007c78117c79107c7a127c7b13978808d4980897aa1097bb18d4ba0ad4a8087c79157c7a147c7b167c7c17979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1890007c78197c79187c7a1a7c7b1b978808d4980897aa1097bb18d4ba0ad4a8087c791d7c7a1c7c7b1e7c771f979908d4a90997bb10977718d4b707821a50d49707977720d487077b1798007b1638aba68ffe95175050202002a9ea8217587b1728287cfe95195882988217508299087b1840140800000000000000807b1948aa8775fd280e82980882997b18487b194001821848821940821a7ba8107ba908286cfd82175051074ffd821728502022027fe3002841fd8217708218783309a82f01281d9b0033073309a82f0128129b003309a82f0128099b009511f0fe7b1008017b1500017b16f8008279140affffffffffffff7fc8a90b88bb17140a0000000000000080d3a90993b9979902330abc0001c8a9098199c8a90964853209957818957938957a40957b48957c509576587b17707b187833070024017b1780007b19880033072024017b1790007b1a98007b17a0007b1ba8007b17b0007b1cb8007b17c0007b16c80033074024017b17d0009517707b17d80082582833076024017b17e000825720828a189516d80033085d060133090db4a00228027b15e8007817f0004621f10033082706013305aa069517e800330909951a78330baa0650202a02ccab003308a705019517e800330908951a8800645b50202c02b3ab0033083006019517e80033090c951a9800645b50202e029aab0033083c06019517e80033090c951aa800645b5020300281ab0033084806019517e800330909951ab800645b5020320268ab0033083105019517e800330904951ac800645b502034024fab0033085106019517e80033090c646a645b5020360238ab007c17f1007c18f000847901d487075109160d84880152080f0d8217e8007c78348488045208cd0b827828827720828a1833085a0f013309023300c602320a825828825720828c1833081e070133090528050b825828825a20828b189577087b17e80033080f070133090564a7b4b0023c027b157878178000462181003308390501330bce00951778330904951ae80050203e02a2aa007c1981007c188000d489075109830c84880152087c0c8217787c78348488045208b10a827828827720828a1833085a0f013309023300c602320a957c209578249579289577087b1778330718017b17609517787b1758491150083307bf05017b1748330732017b17407b19384911300933091721017b19287b17207b18184911100633081121017b18083308ef0601330af40601330905330b07281b02825828825a20828b189576289577087b17e800330899060133090564a7b4b00244027b1578781780004621810033089e0601330b3201951778330906646a50204602c0a9003308270601330b9e00951778330909951ae80050204802a6a9007c1981007c188000d489075109870b8488015208800b8217787c78348488045208cc09827828827720828a1833085a0f013309023300c602320a825828825a20828b189576209577087b17e8003308fb060133090664a7b4b0024c027b1578781780004621810033083d0501330b2c01951778330904646a50204e0227a9003308010701330b9c00951778330905951ae800502050020da9007c1981007c188000d489075109ee0a8488015208e70a8217787c783484880452084a09827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e8003308de060133090664a7b4b00254027b157878178000462181003308e40601330bb000951778330905951ae800502056028fa8007c1981007c188000d489075109700a8488015208690a8217787c78348488045208e308827828827720828a1833085a0f013309023300c602320a957c980095788800957990009577087b177833078e007b17609517787b17584911500433073105017b1748330736017b17407b19384911300933098206017b19287b17207b18184911100633077c06017b1708330732013308e70501330a710601330908330b0b017b17645750205a0288af0028ce09825828825720828c18330814070133090a28ea07825828825a20828b189577087b17e80033088b060133090764a7b4b00260027b157878178000462181003308920601330bd600951778330907951ae8005020620287a7007c1981007c188000d4890751096809848801520861098217787c78348488045208f207827828827720828a1833085a0f013309023300c602320a825828825a20828b189576089577287b17e8003308c6060133090664a7b4b00266027b157878178000462181003308390501330bba07951778330904646a5020680208a7003308c30601330bd200951778330903951ae80050206a02eea6007c1981007c188000d489075109cf088488015208c8088217787c783484880452087007827828827720828a1833085a0f013309023300c602320a825828957930825a20828b187b19e8009576089577287b176833086a060133090764a7b4b0026e027b157878178000462181003308270601330bba07951778330909646a5020700269a6003308300601330b360195177833090c821a685020720250a60033083c0601330bd20095177833090c951ae8005020740236a6007c1981007c188000d4890751091708848801520810088217787c78348488045208e606827828827720828a1833085a0f013309023300c602320a825828825a20828b189576289577087b17e800330823070133090764a7b4b00278027b1578781780004621810033082a0701330b320195177833090a646a50207a02b7a5003308170601330bce00951778330908951ae80050207c029da5007c1981007c188000d4890751097e07848801520877078217787c783484880452086406827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e8003308df050133090864a7b4b00280027b157878178000462181003308340701330bd200951778330905951ae800502082021fa5007c1981007c188000d48907510900078488015208f9068217787c78348488045208fd05827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e8003308a4060133090b64a7b4b00286027b157878178000462181003308af0601330bba0095177833090d951ae80050208802a1a4007c1981007c188000d489075109820684880152087b068217787c783484880452089605827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e800330839070133091364a7b4b0028c027b1580007817880049117846218900330922019517789518e80050208e0241a9008218787c1988008e87d4970751080706849901520900065218013b057c17890082158000510733057c573484770452072905825828825720828a1833085f0f01330901b4a0029002647833070151080a0528c205825828825a20828b189577087b17e800330806070133090964a7b4b00294027b157878178000462181003308410501330bda00951778330904951ae800502096028fa3007c1981007c188000d4890751097005848801520869058217787c78348488045208b704827828827720828a1833085a0f013309023300c602320a825828825a20828b189576089577287b17e8003308bc060133090764a7b4b0029a027b157878178000462181003308390501330bba07951778330904646a50209c0210a3003308c30601330bd200951778330903951ae80050209e02f6a2007c1981007c188000d489075109d7048488015208d0048217787c783484880452083504827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e80033080f060133090864a7b4b002a2027b157878178000462181003308750501330bdc00951778330904951ae8005020a40278a2007c1981007c188000d4890751095904848801520852048217787c78348488045208ce03827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e8003308e9060133090664a7b4b002a8027b157878178000462181003308750501330bba00951778330904951ae8005020aa02faa1007c1981007c188000d489075109db038488015208d4038217787c783484880452086703827828827720828a1833085a0f013309023300c602320a82582895792c825a20828b187b19e80095760895770c7b17683308cc060133090664a7b4b002ae027b157878178000462181003308d20601330b3201951778330907646a5020b00275a1003308390501330bba07951778330904821a685020b2025ca1003308d90601330bd000951778330905951ae8005020b40242a1007c1981007c188000d489075109230384880152081c038217787c78348488045208c602827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e80033084c070133091764a7b4b002b8027b15800078178800491178462189003309a0009517789518e8005020ba02e2a5008218787c1988008e87d497075108a8028499015209a1025218016b027c17890082158000510763027c573484770452075902825828825720828a1833085f0f01330901b4a002bc02647833070151083a02286302825828825a20828b189577087b17e8003308af050133090864a7b4b002c0027b157878178000462181003308750501330bba00951778330904951ae8005020c20230a0007c1981007c188000d489075109110284880152080a028217787c78348488045208e701827828827720828a1833085a0f013309023300c602320a825828825720828c1833086307013309090182100801821500018216f80095111001320c827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a8215800001825828825720828a183308dd0e013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a8215800001825828825720828a183308dd0e013309013300c602320a827828827720828a183308590f01330901b4a002c60284770182100801821500018216f8009511100132009511d87b10207b15187b16106486828828826a20828b18647533086c070133090364a7b4b002ca027b167817084611093308450501330be0026417330904645a5020cc02b89d007c19097c1808d4890751094184880152083b82177c783484880452081a827828827720828a1833085a0f013309023300ce02320a827828827720828a183308590f01330901b4a002ce028477018210208215188216109511283200951170ff7b1088007b1580007b16788279105109eb007b198275087b156833070a027b17107b177033078024017b17389517687b17488287208288284911400149115849112801491150019519387b17207b18185020d2026f970052078e0037075827035317026b6e779558500182169569ff9575ff3307a824017b1708017b193051099d007b18688217107b17708217087b1738491140014911589517687b1748491128014911500195193882172064868218185020d4020e970052072d956850019555ff8219309599ff5205b3821718827a183308610f01330901821720b4a002d6027b1728018217288210880082158000821678951190003200828928828720829c1833086f0701330902821088008215800082167895119000320c33099024018217647828188f00951170ff7b1088007b1580007b16788279105109ed007b198275087b15683307ba077b17107b177033078024017b17389517687b17488287208288284911400149115849112801491150019519387b17207b18185020da02439600520790003a076027035317026d97772098772095582082169569ff9575ff3307a824017b1708017b193051099c007b18688217107b17708217087b1738491140014911589517687b1748491128014911500195193882172064868218185020dc02df950052072c9568209555ff8219309599ff5205b4821718827a183308610f01330901821720b4a002de027b1728018217288210880082158000821678951190003200828928828720829c1833086f0701330902821088008215800082167895119000320c33099024018217647828ea8d009511d87b10207b15186485828828825a20828b187b173308c6070133090664a7b4b002e2027b15087817104611113308cc0701330b0e01951708330902641a5020e402bd9a007c19117c1810d4890751094284880152083c8217087c783484880452081a827828827720828a1833085a0f013309023300e602320a827828827720828a183308590f01330901b4a002e60284770182102082151895112832009511d87b10207b15188179979902330a1c0101c8a9098199c8a9093209828928828720829c183308790501330904821020821518951128320c828928828a20829c189577047b17330bfa070133090d64a764853308fa0701b4c002ee027b15087817104611113308ed0701330bd60095170833090d641a5020f002e699007c19117c1810d4890751090e01848801520807018217087c78348488045208cd00827828827720828a1833085a0f013309023300fc02320a828928828720829c183308d70701330907821020821518951128320c828928828a20829c189577047b17330bde070133090f64a764853308de0701b4c002f6027b15087817104611113308ed0701330bd60095170833090d641a5020f8024d99007c19117c1810d4890751097584880152086f8217087c783484880452084d827828827720828a1833085a0f013309023300fc02320a828928828720829c183308ce0701330909821020821518951128320c827828827720828a183308590f013309013300fc02320a827828827720828a183308590f01330901b4a002fc0284770182102082151895112832009511c87b10307b15287b16208179330a016485ad9a265209bb00825828825720828c18330807080133090a821030821528821620951138320c5219021001825828825a20828b189576049577087b1708330816080133090b64a7b4b00200037b15107817184611193308210801330b320195171033090e646a5020020341980033082f0801330bd600951710330911951a08502004032898007c19197c1818d4890751099a01848801520893018217107c783484880452084201827828827720828a1833085a0f0133090233001203320a825828825a20828b189577047b170833081f060133090864a7b4b00206037b15107817184611193308110801330bd600951710330905951a0850200803b097007c19197c1818d489075109220184880152081b018217107c78348488045208e100827828827720828a1833085a0f0133090233001203320a82582895790c825a20828b187b19089576049577087b17330840080133090b64a7b4b0020a037b151078171846111933082108013305320195171033090e646a330b320150200c032d970033084b080195171033090e821a645b50200e031797003308590801330bd600951710330911951a0850201003fe96007c19197c1818d4890751097084880152086a8217107c7834848804520848827828827720828a1833085a0f0133090233001203320a827828827720828a183308590f0133090133001203320a827828827720828a183308590f0133090133001203320a827828827720828a183308590f01330901b4a002120384770182103082152882162095113832009511c87b10307b15287b162083796482510932977720987320330807330902330a0133070128098b8801ca990901848b01510bf6ca7907aba8f0ae732233042831028228108227aa875403822708c8870746079588017b281028ee02330807330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b33090e330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330401ac83cc0133080e330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b330915330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330402ac838001330815330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b33091c330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330403ac83340133081c330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b330923330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330404ac83e800330823330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b33092a330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330405ac839c0033082a330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32a330931330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330406ac8350330831330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32c01330938330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330407ae8304018d4808330a02330b0133090128098b8801caaa0a01848c01510cf6ca9a09abb8f07b1210ae73228228108227c99305aa871201822708c887097895958c017b2c1033080128688a4603330802330a01330701646b28098bbb01ca88080184bc01510cf6ca7807ababf051071601822810822acb7305c99505aa8ae800822708c887097895958c017b2c10330902330a0133080128098b6601ca990901846b01510bf6ca8908aba6f05108fa008229cd8308c9c9097b1818ac492ec8c707951818644964c6644550201403a4cc00c865058217107b7510018210308215288216209511383200642764c8644964266445502016032fa7006454826c1082670828bc461118ff95181833090164276425643650201803b9a6007b1618951818330908645750201a03a7a60028ae3309016427642550201c03eca6006452825810289dfc330901642764367b140850201e03d3a600821408646382121082281028d6fe33090164277b137b140850202003b4a60082140882138212108228102800ff9511d03307e02e017b1749110801491120491110084911183308b82401641728f784003307d0240128dea4009511d07b10287b15207b16186486647533088c0050202203ebc4005107850081680849018c007b17089889187879039889107879028269787898880878780178790498983878780b98983078780a9898287878099898207878089898187878079898107878069899087879054911100c95680c95770c330980005020240345cb00821782180833098c007b59107b577b5808821028821520821618951130320033078c00282c80009517f0069518bc017b18f805330824077b18e8007b1800069518b8017b1808063308b6067b1848017b1810067b171806330720077b1718017b17200633070026017b17d8014921e001034921f8019518f8057b18e8014921f001039517c0019518d801502028033180008216c0018215c801821bd0013308490501330702330904645a0a640151060c645750202a03c0c7004921a8070e9517c0079518a80750202c0341a6008218c007140700000000000000807b183801ab7806284e498218c8078217d0077b1830017b18d8077b17e0079517d80750202e0344a900837752078e0a83874921e807330a083309087b1980017b19f0074921f807330c52070628df45978820988b203302b8001407f8ffffffffffff7f7b174001330320140700000000000000807b1760012811821b50013302b800c99b0b510b520a821ae807330759e67b05c9ca077b1578017b1b5001ae577fc8c505aec506289645d6250833075108062895459659b80082164001ae96062899457b1cb001510a1896a7b800821880017b18d0067b17e00633070828053307017b17d8069517f805330808951ad0065020300342cc8217f805510706284b45821700067b1780017b17f0077b15e807821cb001330320821578010196c7b800957ab0009a577b176801330601330b01281a0182178001c857077b7250ff951800108288f0f87b7858ff951800108288f8f87b7860ff821890017b7868ff821888017b7870ff821870017b7878ff95180010828830f97b789895180010828828f97b789095180010828820f97b788895180010828818f97b7880951800108288e8f87b78b8951800108288e0f87b78b0951800108288d8f87b78a8951800108288d0f87b78a0951800108288c0f87b78d0951800108288b8f87b78c8951800108288b0f87b78c0951800108288c8f87b78d8951800108288a8f87b78f8951800108288a0f87b78f095180010828898f87b78e895180010828890f87b78e0821898017b7895cc017b1cf80782197801d8960b95660182176801c86707955ab800330320330801aa8733fe7b1b88017b16a0017b1aa8017b1cb0018212e007ac320e088218d8079529e07c8a017c8b7c8c027c860397aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b057c8c047c86067c850797bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a40037c8a097c8b087c8c0a7c860b97aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b0d7c8c0c7c860e7c850f97bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a48037c8a117c8b107c8c127c861397aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b157c8c147c86167c851797bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a50037c8a197c8b187c8c1a7c861b97aa08d4ab0b97cc10976618d46c0c7c861d7c851c958a20d4cb0b976608d465057c8c1e7c861f7b1ad8077b19e00797cc10976618d46c0cd45c0c97cc20d4cb0b7b1b5803ac39e106952bc07ca9017cac7ca6027ca503979908d4c909976610975518d46505d459097cac057ca6047ca5067ca70797cc08d46c0c975510977718d45707d4c707977720d497077b17d8017ca7097ca9087cac0a7ca60b977708d4970797cc10976618d46c0cd4c7077ca90d7cac0c7ca60e7ca50f979908d4c909976610975518d46505d45909979920d497077b17e0017ca7117ca9107cac127ca613977708d4970797cc10976618d46c0c7ca6157ca514958940d4c707976608d465057cac167ca6177b19d8077b1be00797cc10976618d46c0cd45c0c97cc20d4c7077b17e8017ca7197cac187ca61a7ca51b977708d4c707976610975518d46505d457077cac1d7ca61c7ca51e7caa1f97cc08d46c0c97551097aa18d45a0ad4ca0a97aa20d4a7077b17f001ac3bb805952ba07c97017c9a7c9c027c9603977708d4a70797cc10976618d46c0cd4c7077c9a057c9c047c96067c950797aa08d4ca0a976610975518d46505d45a0a97aa20d4a7077b17f8057c97097c9a087c9c0a7c960b977708d4a70797cc10976618d46c0cd4c7077c9a0d7c9c0c7c960e7c950f97aa08d4ca0a976610975518d46505d45a0a97aa20d4a7077b1700067c97117c9a107c9c127c9613977708d4a70797cc10976618d46c0c7c96157c9514958a60d4c707976608d465057c9c167c96177b1ad8077b1be00797cc10976618d46c0cd45c0c97cc20d4c7077b1708067c97197c9c187c961a7c951b977708d4c707976610975518d46505d457077c9c1d7c961c7c951e7c991f97cc08d46c0c975510979918d45909d4c909979920d497077b171006ac3b8f04952780958880007b18d8077b17e0077ca7017ca87ca9027cab03977708d4870797991097bb18d4b909d497077ca8057ca9047cab067cac07978808d4980897bb1097cc18d4cb0bd4b808978820d487077b17d0067ca7097ca8087ca90a7cab0b977708d4870797991097bb18d4b909d497077ca80d7ca90c7cab0e7cac0f978808d4980897bb1097cc18d4cb0bd4b808978820d487077b17d8067ca7117ca8107ca9127cab13977708d4870797991097bb18d4b909d497077ca8157ca9147cab167cac17978808d4980897bb1097cc18d4cb0bd4b808978820d487077b17e0067ca7197ca8187ca91a7cab1b977708d4870797991097bb18d4b909d497077ca81d7ca91c7cab1e7caa1f978808d4980897bb1097aa18d4ba0ad4a808978820d487077b17e8069517d807502032033ca4003303205207570364858218e00751084d038219d8077c979588ff9599017b19d8077b18e0077b159801510717330807ae872b033306821560017b15900128a0009517ff07957701019518d807502034039aa00095170010827700f9821c6001aac71e037b179001951700107c7708f9951800107c880af9951900107c9909f9951a00107caa0bf9951b00107cbb0cf9978808d4980897aa1097bb18d4ba0a951900107c990df9951b00107cbb0ef9d4a808951a00107caa0ff997992097bb28d4b90997aa30951b001082bb10f97b1b7001d4a909d4980897860864c501d476069517ff07957701019518d80750203603fa9f0095170010827200f9aa523b027b1688019518ff0795880901828708828882194003821a4803951b00107bb788f8951700107b7880f8951700107b7960f8951700107b7a68f882175003821858038219d801821ae001951b00107bb770f8951700107b7878f8951700107b7940f8951700107b7a48f88217e8018218f00182191006821a0806951b00107bb750f8951700107b7858f8951700107b7938f8951700107b7a30f8821700068218f8058219d006821ad806951b00107bb728f8951700107b7820f8951700107b7900f8951700107b7a08f88217e0068218e80695190010829988f8951a001082aa80f8951b00107bb710f8951700107b7818f8951700107b79f8f8951700107b7af0f895170010827778f895180010828870f895190010829968f8951a001082aa60f8951b00107bb730f9951700107b7828f9951700107b7920f9951700107b7a18f995170010827758f895180010828850f895190010829948f8951a001082aa40f8951b00107bb7e8f8951700107b78e0f8951700107b79d8f8951700107b7ad0f895170010827738f895180010828830f895190010829928f8951a001082aa20f8951b00107bb7c8f8951700107b78c0f8951700107b79b8f8951700107b7ab0f895170010827718f895180010828810f895190010829908f8951a001082aa00f8821be807951c00107bc7a8f8951700107b78a0f8951700107b7998f8951700107b7a90f8821cb0018215a8018216a001abbc11f79517e8077b125801502038037ba00082125801821cb0018217f0077b17800128eef61407000000000000008082189001d47808330320aa7810646750203a0335bd003303200182178801847701520729821cb001821b50013302b80082197801c99b0b520bfff52849821788018477013303205107dd8218b00151080628663c8217e80751070e8217800150203c03e5bc0033075a1f01330a083001330b2830013308239519ff079599500128818a008218e80714070000000000000080aa78d48215f0077b180801821738015107138217300164c650203e039abc00646c0196c7b800c857077b177001140700000000000000807b1798011407f8ffffffffffff7f7b1750014921380121384c4921d000abaaaaaa7b150001280d9595b80082179801ab763582177001ab750628a63a645982578256188258207b1868018258287b18b0015107d682970864955020400322bc00645928c57b16f0007b19f80082176801951800107b8760f88217b001951800107b8768f89517ff0795776150204203bf9d0083773303d80051070628e23a83874921d8013309087b19e0014921e801520706280d3a330c97882098852033020828153303d8008212a80182157801c9a50551057c018214d80133074be6750ac9c4077b1578017b1a9001aea73fc8ac06aec60628a73ad63608330751080628a63a9669d800821b5001ae9b0628aa3a7b16b00164c551041a9647d8007b12d0067b17e006330708280b7b12a80128433307017b17d8069517f805330808951ad00650204403d6c08217f80551070628583a821700067b17a8017b17e0018217b0017b17d801645c821a90013303d8000196c9d8009aa77b178801330501330b01283f7b18a801c868079518d0063309d80050204603b6bd00821cb00195cc017b1ce801821a9001d8a50b95550182178801c857079569d800330801aa8707ff64b67b1cb0017b19a0019517f8059518ff0795886150204803358d8217f805821898019588187b188001aa87409517d0069518f8053309d80050204a034fbd008217d8018218a8018216a0018219b001ab7973ff3308c02f019517d80150204c03bdc18218e001285cff846701821578015107062812393303d8008212a801821cb001821a9001c9a50552058cfe8218d80182179801ab78062825397b18d80096c7d800c872087b12a801520c062803381407ffffffffffffff7f7b17600164267b18400128169517f80550204e0394ba82184001ab860628dc3782659568089566d8009517d8013309d000502050039cbc0082178001ab750628bf377b1678017b15f8059518d8013309d000951700065020520377bc009517f8057b174003330724027b17b0017b17480333073026017b17d0064921d806014921f006951840037b18e0064921e806019517a8029518d00650205403b2718216a8028215b002821bb8023308490501330703330904645a330056030a640151060c6457502058033eb900821af80582196001c89a07887817959501d35a079387541717072838199777023308300101c887078177c887077b15580132077b1a2001951c10067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17d8027cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17d0027cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17c8027cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d48707821830067b18b0018216400682184806821538067b17c002951700107b7620f8951700107b7840f89517d006330980009518500650205c0381ba00821700067b17c800821708067b17e0009517c0028218b0016459646a0a090198782051080628a91b977720d4780884880152080628c61b33081150205e0380b300821678015207062845393308050a0133091164755020600325ba00330b11330733083309645a0a6464575020620343b7003307018219200182186001d29808510826262849348218100682190806821700067b17c8009787037b18b001978805c97808c898087b19e0007b1940037b1820017b18480395174003951800107b8718f93307c802951800107b8720f933079029017b17d0064921d806014921f0069518ff07958819017b18e0064921e806019517a0049518d00650206603fd6e8216a0048215a804821bb0043308490501330702330904645a0a640151060c6457502068038db60033080450206a038fb20052070628be3864758218b00178789887187857039887107857029887087857013307f70501330808330a0464590a03013308f7ac870c3308ffaa8706285538645750206c033bb6008216e0008217b00152070628d21833058216e00095661828199566188217900195771895550182182001ab870628ed188268e882179801ab780628a3187b1888019567e88268f07b18b0017b1790018277107b17a001951700107b75d0f89517ff079577d1007b17400333078e067b1748033307a029017b17d0064921d806014921f006951840037b18e0064921e806019517b8049518d00650206e03e46d8217c0048218c8048219b8047b1930017b1728018219b001821aa001330070030a03013308f7ac870c3308ffaa870628de358217300151070e821728015020720358b50082178801510732ff8217b0015020740345b5002823ff8216000682179801aa760628e81833076d0b013308093309330a330078030a0301957801821678015518f8ba3d2806240a0833080d50207c0312b1005207062847373308dd0b0133090d647550207e03bbb7003308490501330702330904330b0d645a0a6464573300380428d0b400801720067a179405951600067c68197c69187c6a1a7c6b1b978808d4980897aa1097bb18d4ba0ad4a8087c691d7c6a1c7c6b1e7c6c1f979908d4a90997bb1097cc18d4cb0bd4b909979920d49808951900107b9830f97c68117c69107c6a127c6b13978808d4980897aa1097bb18d4ba0ad4a8087c69157c6a147c6b167c6c17979908d4a90997bb1097cc18d4cb0bd4b909979920d49808951900107b9828f97c68097c69087c6a0a7c6b0b978808d4980897aa1097bb18d4ba0ad4a8087c690d7c6a0c7c6b0e7c6c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d49808951900107b9820f97c68017c697c6a027c6b03978808d4980897aa1097bb18d4ba0ad4a8087c69057c6a047c6b067c6c07979908d4a90997bb1097cc18d4cb0bd4b909979920d49808951900107b9818f99518ff07958819013309200a1b01520706285c19957809951900107b97d0f85318070728b6379a87062848197f171806951a000682a81082a90882aa951b001079b7b0f8951700107b7828f9951700107b7920f9951700107b7a18f99517ff079577b1007b17400333072c017b1748039517ff07957719017b1750033307d8027b1758033307f028017b17d0064921d806024921f006951740037b17e0064921e80602951730059518d00650208403376b8216300582153805821b40053308490501330702330904645a0a640151060c645750208603c7b20095170010827828f9590806286e35951700107e77b0f87b17900195170010827720f97b17a0017b1788017b18b00197860595650364585020880396ae00520706284e347b15d0067b17d8064921e0068218b00152080628e91b7b17b0013305821aa001018217d006c9570754171f378217b0010195a8207b18a0017b17b001c8570733092064a850208a0305b5009555209566e07b15e006821aa0015206c8281b159517d006330920645864a550208c03938f00645a8215e0068217d80628b681172006951800107a87b0f8951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8730f97cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8728f97cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8720f97cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8718f99517ff079577b1007b1740038217e8007b1748039517ff07957719017b1750033307ba077b17580333075027017b17d0064921d806024921f006951740037b17e0064921e80602951720049518d00650209003c3688216200482152804821b30043308490501330702330904645a0a640151060c64575020920353b000951700108077b0f89518ff07958819010a0c01520706288c16957809951900107b97d0f85318070728e2349a8706287816951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1758037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1750037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1748037cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d487077b174003951740030a100151070628473195174003951800107b8718f933074e01951800107b8720f933072029017b17d0064921d806014921f0069518ff07958819017b18e0064921e80601951778059518d00650209603f9668216780582158005821b88053308490501330702330904645a330098030a640151065915284c15951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1758037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1750037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1748037cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad48a0a821820068219280697aa20d4a7077b174003951740030a0a01510706283f3133080950209c035ca9008216780152070628152f3308160a01330909647550209e0301b0003308490501330702330904330b09645a0a64645733007a042816ad00821700067b1728018218100682160806951700104927d0f87b1630017b18880152080628c513978703978805c978092826951700108277d0f8957701951800107b87d0f88219b001019599e89566185209062843107b19b0017b165004951750047b1740033307f6007b1790017b1748033307a027017b17d0064921d806014921f006951840037b18e0064921e80601951758049518d0065020a203c364821758047b17a00182156004821b68043308490501330702330904645a0a64018217a00151070c64575020a4034bac00821850048287088288103309330a0a03018219b0015317f766ff5117ff07286e2c951750047b174003821790017b1748033307b027017b17d0064921d806014921f006951840037b18e0064921e80601951770049518d0065020a603356482157804821b8004821770047b17a001330733083309645a0a64018217a0015107e6fe64575020a803c1ab0028d9fe9518000682861082878288087b1650037b1740037b18b0017b18480359060628492e966550017b15900195550364585020ac0394a700520706284c2d7b15d0067b17d8064921e006520606286c157b17880133068217b0019577c0007b17b001018217d0067b17a001c9670754171fcb0082178801018218b0019588c06475c867073309205020ae03fead009566208217a001c967077b16e00654171fc0006457018218b0019588e06475c867073309205020b003d2ad009566208217a001c967077b16e00654278f00b7006457016475c86707330990008218b0015020b203a7ad00956690008217a001c967077b16e00654177faf006457018215b001955840ff7b178801c86707330980005020b40376ad00956680007b16e006821790019577b0fe955550017b15b0017b179001520731ff28f00d9517d00633092064685020b603f787008216e0068217d8068218d0067b18a001281fff9517d00633092064685020b803d487008216e0068217d8068218d0067b18a0012828ff9517d0063309900064685020ba03b087008216e0068217d8068218d0067b18a0012831ff9517d0063309800064685020bc038c87008216e0068217d8062840ff81170006951800107a87b0f89517ff079577b1007b1740038217e8007b17480333074027017b17d0064921d806014921f006951840037b18e0064921e80601951708049518d0065020c003fc618216080482151004821b18043308490501330702330904645a0a640151060c64575020c2038ca9009517d0065020c403f3900082171007531751580d811818075218024f0d9578af9517d0060a0f018216780152070628aa17957809951900107b97d0f8531807072800333308265020c6034ba500520706283b33647533086b0a0133092664575020c803f2ab00330b26330733083309645a0a64645733007a04280ca900330528143300cc030a08018355015225e8030828c40f951700107a75d0f89517b8017b174003821648017b1648039517ff079577d1007b1750037b1658033307482a017b17d0064921d806024921f006951840037b18e0064921e806029517b0059518d0065020ce03ec608217b0057b1790018217b8057b17b0018217c0057b17a0019517ff079577d1007b1740037b16480333070009017b17d0064921d806014921f006951840037b18e0064921e806019517c8059518d0065020d00397608216d005821ad8058217c8057b1788018217b0018218a00164690a03013308f7ac870c3308ffaa87062862278217880151070c64675020d20313a80082179001510704ff8217b0015020d40300a80028f5fe811790068218800682198806951a00107aa700f8951700107b7820f8951700107b7940f89517400333098000951800065020d80392aa000a951800107b87d0f83307e026017b17d0069517ff079577d100951800107b8718f982171801951800107b8720f94921d806014921f0069518ff07958819017b18e0064921e806019517c0039518d0065020da03b85f8216c0038215c803821bd0033308490501330702330904645a0a640151060c64575020dc0348a7009517ff079577017b17d006821748017b17d8069517ff079577217b17e006821718017b17e8069518ff079588417b18f0067b17f806951740037b1700073307c0077b1708073307f02601951800107b8718f995170010492720f9049517ff079577390149079517d006951900107b9728f995170010492730f9049517d8039518ff07958819015020de03045f8215e003821be8038216d80333070233083309645a0a640151060c64575020e00398a60095170010807700f895180010828820f895190010829940f8951a40030a0b015107190e957809951900107b97d0f853180707285c2b9a870628040e951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8730f97cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8728f97cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8720f97cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad48a0a8218200697aa20d4a707951900107b9718f9951700107b78b0f89517ff07957719010a0e015107062862289517ff07957719017b1740033307ba077b1748039517ff079577b1007b175003821718017b1758033307c027017b17d0064921d806024921f006951740037b17e0064921e806029517e8049518d0065020e403085d8216e8048215f004821bf8043308490501330702330904645a0a640151060c64575020e60398a4003307390b013308099519ff0795991901330a203300e8030a03015317f7460b5117ff410b28ee27951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8730f97cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8728f97cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8720f97cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a80882162006978820d48707951800107b8718f99517ff079577190164680a0d01837b987a20551b03cd0d951900107a97d0f8951900107a9ad4f8951900107a98d8f8531b02fb0d8119b801521b02d60d3907682703be8707330b04ae97d70d330728dc0d8217000682169801aa67b1097b179001821708067b17a0018215100633083e0c019517d0063309145020ee033c89008218d006ab68770e4921b0015205120f28851082170006951800107b87d0f89517ff079577d1007b174003821718017b1748033307e029017b17d0064921d806024921f006951740037b17e0064921e806019517d0049518d0065020f203aa5a8216d0048215d804821be0043308490501330702330904645a0a640151060c64575020f4033aa2003308085020f6033c9e0052070628ff236475951700108277d0f878579878387858079878307858069878287858059878207858049878187858039878107858029877087857013307cf0501330808330a0864590a03013308f782167801ae870628c2103308ffab87b22328b810801c1806951700107a7c40f8951a000682a780181c068019200682ab10951200107b2718f982a608951700107a7890f8951700107a79b0f8951700107b7b28f9951700107b7620f9510b260a7b1ba0017b19c8007b18e0007b1c20017b17b00133080c5020fa036f9d00520706282c218065826808787598591878790398591078790298590878790178780498893878790b98893078790a9889287879099889207879089889187879079889107879069888087b17880178780533080c5020fc03159d0052070628d220647a8217a001977704c867077b1730019567107b17900182178801957b0c9576018458ff0078a84921d0060c7b1ad80633080128188219d0067c65956601aa98a400c88a097895958801017b18e0068215b001abb6e27b1a88017b18a0015020fe039ea000821690015106250482173001aa761d0433080c50200004909c00520706284d2081688269087878988a18787a03988a10787a0298880878780178790498983878780b98983078780a9898287878099898207878089898187878079898107878069899087879059566107b169001957b0c64768218a001821a88012856ffc96b0a3309fee69a099599017b1788019517d0067b18a0017b1b280150200204d3a9821b28018218a00182178801821ad806282eff9517d0069518f8053309d80050200604a5a2009517d006951800107b8718f98217b001951800107b8720f93307802a017b1740034921480301492160039518ff07958819017b18500349215803019517e0059518400350200804d8578216e0058215e805821bf0053308490501330702330904645a0a640151060c645750200a04689f009517d00650200c0400a0821678012868e5951c04067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1758037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1750037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1748037cc7017cc87cc9027cca03977708d4870797991097aa187cc805d4a909d497077cc9049788087cca067ccb07d489098018000697aa1097bb18d4ba0ad4a909979920d497077b1740039517ff07957719019519400350201004af83009517ff0795771901951800107b87d0f83307c401951800107b87d8f833078828017b17d0064921d806014921f0069518ff079588d1007b18e0064921e80601951718059518d0065020120420568216180582152005821b28053308490501330702330904645a0a640151060c645750201404b09d0095170010827618f982179801ab763a0333076d0b013308093309330a330016040a03019578015318f80728862695170010827718f982189801d48707aa873c0495170010827520f9645733003804285b9d0082172001aa673982172001c967078219d000979820c89808d68705985504956608280b9555ff9566185105148267f85107f4826750201804239d0028e88217c8005107e5038217e00033003804280a9d008217d0067b17a001821790018216b00164680a060164758217a0015207800651058906955709951800107b85d0f88216780153170707284f2133081150201a04d69800520706289f1e64753308bf0b01330911645750201c047d9f00330b11330733083309645a0a64645733005a0428979c008216d0068215880164570a0701647952068b06289506951700108277d0f882188801284a038215d0068216d806821c20018218e0008219c800821ba00164c7646a0a050151070a05280c1f82150806821a100633076d0b01330809645933001e040a03013308f7ac87f9013308ffaa87f20128982433082f502020042798008216780152070628442564753308910a0133092f645750202204ca9e00330b2f330733083309645a0a64645733007a0428e49b005517f707286425957809951900107b97d0f8531807072807259788089a8700068678018488015108062840e4987720951800107a8700f89517ff07957701951800107b8718f98217e800951800107b8720f933074026017b1740034921480302492160039517ff07957719017b17500349215803019517e0029518400350202404ac538216e0028215e802821bf0023308490501330702330904645a0a640151060c6457502026043c9b0095170010817700f8951800107a87b0f87b1738330807502028042b970052070628ca2464754615066446150565461504744615036146150265461501724605633308079519ff079599b100330a0464570a03013308f7ac870c3308ffaa8706285225645750202a04d19a009517ff07957719013308ff9519c00250202c0445800095170010827518f982179801aa75620a95170010827620f995170010827928f995170010807700f864680a1b017b15b0017b16a0015107a609957809951900107b97b0f8531807072821279a870628910995170010827520f995170010827a28f933076d0b01330809645933002e040a03013308f7ac870c3308ffaa8706280d235206f90028fe0033070701951800107887d0f8951794057b174003821748017b1748039517ff079577d1007b1750033307ea017b17580333075829017b17d0064921d806024921f006951740037b17e0064921e80602951798059518d006502030041e52821698058215a005821ba8053308490501330702330904645a330032040a6401520675287b33070701951800107887d0f89517ff079577d1007b1740033307ea017b17480333073027017b17d0064921d806014921f006951840037b18e0064921e80601951738049518d00650203404ac518216380482154004821b48043308490501330702330904645a330036040a640151060c64575020380438990082167801282508330701c97807951800107b8718f99517ff07957719017b17400333078e067b1748039518ff079588d1007b1850037b17580333077027017b17d0064921d806034921f006951740037b17e0064921e80602951788049518d00650203a041a5182159004821b98048216880433070233083309645a33003c040a640151060c645750203e04aa9800821678018218880151082e82153001955508280b9588ff95551851081c8257f85107f482576486502040047c980064688216780128e082172801510761078217300133007a04285d98003307070195180010788700f99517ff0795770101951800107b8718f93307ea01951800107b8720f933073027017b17d0064921d806014921f0069518ff07958819017b18e0064921e806019517f0039518d006502042044f508216f0038215f803821b00043308490501330702330904645a0a640151060c645750204404df970095170010817500f895170010827620f895184003330980009517dc0650204604889a007a15d8067b16d0069517ff079577d1009518d00650204804aece951700108275d8f895170010827ae0f833077c0c0133080b645933004a040a03013308f7ac870c3308ffaa8706281f1b951700108277d0f88216780152074e06285306330533060164c7330a010a050151070628091a51050c646750204c044097009517ff079577417b17d006821748017b17d8069518ff07958891007b18e0067b17e8069518ff079588b1007b18f0067b17f8069517ff07957719017b1700073307bc017b1708073307b028017b1740034921480304492160039517d0067b1750034921580304951748059518400350204e04144f82155005821b58058216480533070233083309645a330050040a640151060c645750205204a4960095170010827718f9510764fd95170010827720f9330038042885960064768217900164680a066475646733005404286f960064675020540469960052057ff9330809502056046792008216780152070628201864753308b60b013309096457502058040a99003308490501330702330904330b09645a0a64645750205a0423960095170010827718f951070c0595170010827720f933007a042804960064750a076479016457649550205c04f5950064590151090628691895174003951800107b8718f93307d002951800107b8720f933071029017b17d0064921d806014921f0069518ff07958819017b18e0064921e80601951760059518d00650205e04eb4d8216600582156805821b70053308490501330702330904645a330060040a640151060c6457502062047795008217400351073bfc821748033300380428609500511b030728cc1f988b20951900107a97d0f88119b801951700107a7ad4f8951700107a78d8f8951700107a7bdcf8013907682703be870c330b03330701ac9c0e390a682703c88a0a64b7017b16b001951800107a87b0f8951700107a7ab4f89517ff079577d1007b1740033307fe027b1748039517ff079577b1007b1750033307e8027b17580333072828017b17d0064921d806034921f006951740037b17e0064921e80602951700059518d00650206404f74c8215000582160805821b10053308490501330702330904646a0a640151050c6467502066048794009517ff07957719018218b0010a0f01510706285f173307560b0133080b9519ff0795991901330a20330068040a0301821678015317f745035117ff40032864177b1588018217e00655170807288b1e8217d8067c797c76017c72027c73037c7a047c75057c7b067c7c075108437b1ab0017b16c80064b67b1c30017b1928017b15200164257b13e00050206a04f793008213e00064528215200182192801821c3001646b8216c800821ab00101976608d49607972810973918d49808d48707975808d4a80897b91097ca18d4a909d49808978820d487077b17b0018215880151057b019755058216a00128179566209555e08217b0019577017b17b00151055d017c67197c68187c691a7c6a1b977708d4870797991097aa18d4a909d497077c681d7c691c7c6a1e7c6b1f978808d4980897aa1097bb18d4ba0ad4a808978820d47802951700107b7230f97c68117c69107c6a127c6b13978808d4980897aa1097bb18d4ba0ad4a8087c69157c6a147c6b167c6c17979908d4a90997bb1097cc18d4cb0bd4b909979920d48903951700107b7328f97c69097c6a087c6b0a7c6c0b979908d4a90997bb1097cc18d4cb0bd4b9097c6a0d7c6b0c7c6c0e7c670f97aa08d4ba0a97cc10977718d4c707d4a707977720d47909951700107b7920f97c67017c6a7c6b027c6c03977708d4a70797bb1097cc18d4cb0bd4b7077c6a057c6b047c6c067c680797aa08d4ba0a97cc10978818d4c808d4a808978820d48707951800107b8718f97b1258037b1350037b1948037b1740039517400333082095194003330a200a03013308f7ac87a0fe3308ffaa8799fe2841128217900151070e8217a00150206c041b920033080850206e041d8e008216780152070628dc1364758218b001787898873878570798873078570698872878570598872078570498871878570398871078570298870878570133073e0c01330814330a0864590a03013308f7ac87aa003308ffaa87a30028f719951700108176b0f87b17b00133087450207004aa8d0052070628131e647578769867187857039867107857029866087856019557043309703308502072048996006457330833090a0a0151070628e51d6457502074045b9100330809502076045d8d008216780152070628161364753308620a013309096457502078040094003308490501330702330904330b09645a0a64645733007a0428159100645750207a040f91008217f80582186001c8780888881782195801d3970793875417160728f8d682183801d078078477015107ebd628f1d633070701951800107887b0f89517ff079577b100951800107b87d0f83307ea01951800107b87d8f833076026017b1740034921480301492160039518ff079588d1007b18500349215803019517f8029518400350207c04cf488215f80282160003821b08033308490501330702330904646a0a640151050c646750207e045f90008217b00151070e8217a001502080044d90000a951800107b87d0f833077026017b1740039517ff079577d100951800107b8718f982171801951800107b8720f94921480301492160039518ff07958819017b18500349215803019517100395184003502082043f488216100382151803821b20033308490501330702330904645a0a640151060c645750208404cf8f0095170010807700f895180010828840f895190010829920f8951ad0060a0b0151071a957809951900107b97d0f85318070728a3199a870628063307070195180010788700f99517ff079577417b174003821718017b1748039518ff079588217b1850037b1758039517ff07957701017b1760033307ea017b1768033307802601951800107b8718f995170010492720f9039517ff0795773901490795174003951900107b9728f995170010492730f903951728039518ff07958819015020860455478216280382153003821b38033308490501330702330904645a330088040a640151060c645750208a04e18e008217980182182001aa78d80b33081450208c04d78a0052070628701849214003147b174803492150033307109518400350208e049cc18215500382194003c9590754170f290c7b19b0010182164803c856073308100901330910502090044c91009559109517ff0795771901646850209204e6748217b00151070c6467502094045d8e0095170010827818f982179801ab78113306330a330b08821320012888007b181095170010827820f995170010827728f97b1808951900107b9890f8951800107b8798f89517ff079577910050209604da6f83778213200151073c0482169801821828821920018217107b18287b1920510718821708643550209804df8d0064538219208218280114070000000000000080ab760c3306330a330b082807649b648a018217d8028218d0028219c802951c00107bc730f9951700107b76d0f8951700107b7828f9951700107b7920f98217c00295180010818800f87b189001951800107b8bd8f8951800107b8ae0f8951800107b8718f97b1ba001510ac0007b163001330995b5107b1a880197a6068218e000280f9599019566c0955540510685008257ab78f18257f88218c8007b19b0018219e00050209a04a192008219b0018218e00083775207ce9797068218a001c8870782768278087b1828019578408599ff82158801c8590997990650209c047090009555ff7b158801951700107b75e0f88213200151062a82172801643650209e04c78c0064638216300182158801281d821320018216300182158801280f8216300182158801280533050195170010827730f995180010828828f995190010829920f9951a001082aa18f97b1758037b1850037b1948037b1a4003ab65269517ff079577d100645664355020a004606864536465951700108277d8f87b17a001019758068217a0017b18b001c887077b738218c8007b78088218e0007b7810821850037b7828821848037b7820821840037b7818821858037b7830821890017a7838955501951700107b75e0f859050628ee16951700108277d8f87b17a00197560695660464685020a204e98700520706288a167b1640037b17480398572049215003510706287a169518400364575020a404a7be51052f018217a0019578388217b001957640282f821690017b1ba00195a8e0c85b073309205020a604568e008218b0019555207b1550039566c09588405106ff007b1690018285d89857205207ae0c7b18b0018287d07b1730019518400364575020a80441be8216500382184003c96807ac5782007b18880101821748037b17a001c867078218300164595020aa04f08d00c856087b185003821ab00181a5821c8801c98c075417036a821ba00101c88b079859187879039859107879029859087879017875958504c95c077b15500356171f44ff9517400333092064585020ac045168821ab00182155003821b48032824ff95174003646864595020ae04346882165003821740037b178801286dff951740033309045020b0041868821ab00182185003821b4803821c40032882821748037b17a0018215500301821740037b17b0013308145020b204748600520706280d1449214003147b17480349215003330710951840035020b40439bd821a500382194003c9a90754170fe4077b198801017b1a900182164803c8a60733081009013309105020b604e58c00821890019588107b16300164678219a001645a3300b8040a030164795519f70d7b1990013306286c065119ff5f06959709951800107b89b0f8330807ac87062869179a99fd7b1990013306012846068387951900104929b0f8951a0010492ab8f808951900104929c0f85107d706978820988820282082132001018217b0008477015207c9068218308217b800c9780851084e076489951700108277c0f895180010828ab0f8330800017b1930e68909c97a087b19b800ae988b008215b800c87505ae750628ba1598583a330751080628b915975906821b5001ae9b0628ac156436510a1b951700108277b8f897aa067b1740037b1a500333070828053307017b1748039517ff0795771901330808951a40035020ba049b8e95170010827718f951070628431595170010827720f9951800107b87b8f8951700107b75b0f864630195170010827698f833044921b00001288f00951700108277b8f8976806c88707821890017b78821888017b78088218b0017b7810821840037b7818821848038214c0009544017b7820821850038219a001979908821ab000d4a909821aa80097aa107b782882185803821b280197bb18d4ba0ad4a9097b78307a7938956601951700107b76c0f88217b800d874087b18b0008213200182163001aa749ffe51069bfe95170010827790f87c72956bff957a01951800107b8a90f8951800107b8b98f83308017b1888017b14c00051026a5222ff006c330809ac8661fe7c78027c79017c7a037c7b04978808d4980897aa1097bb18d4ba0ad4a8087c79067c7a057c7b077c7c08979908d4a90997bb1097cc18d4cb0bd4b909979920d49808956bf7957a09951700107b7a90f8951700107b7b98f828c100642c28d3016c2857080f330595170010492718f9285084874033050151073c84872033050251073384871033050351072a84870833050451072184870433050551071884870233050651070f84870133050751070628251595170010492718f9ac5b87039517ff07957719017b18b00164a864597b1aa00164b65020bc04eb8900821aa001c9560bc85a0a951700107b7a90f88557079b77ff8218b001e0780795180010828818f9951900107b9b98f8975503cf5707c878088213200101988720520741fdac8b3dfd330251080701330549218801010133070040e67806c952077b1aa0017b183001ae672ec86509ac59600757095c077b1b280151022182178801951800107b8718f9951700107b7228f9330701280ac865072851330701951800107b8720f99517ff079577d100330801951aff0795aa19017b19b0015020be04f78b951700108277d0f85207f307951700108277d8f87b1788018217b0016472821aa001821b2801017b17b001ac57d206ac6b550282178801c8570764a8646964b57b1290015020c004dd88008218300182129001821aa001c9650bc96808c86a0a821cb00164c552082dff951700107b7b98f814070000000000000080951800107b8a90f882132001ab72092833fc330c01531b0424027ca57ca7017b17a0017ca7027ca60395b0fc95a804951900107b9890f8951900107b9098f833091fae09f7017b17a8007b15b0007b1628017b1cb0017b12900195b9dc95a724951a00107ba790f87b193001951700107b7998f87c87117c89107c8a127c8b13977708d4970797aa1097bb18d4ba0ad4a7077c89157c8a147c8b167c8c17979908d4a90997bb1097cc18d4cb0bd4b909979920d479057c87077b17a0007c87067b1798007c87047b1790007c87057b1788007c87037b1780007c87027b17787c877b17707c87017b17687c870f7b17607c870e7b17587c870c7b17507c870d7b17487c870b7b17407c800a7c86087c84097c831f7c821e7c8c1c7c8b1d7c8a1b7c87197c89187c881a7b155003977708d4970797881097aa18d4a808d4870797bb08d4cb0b972210973318d42308d4b808978820d487077b175803974408d46407970010821840978818d40808d48707821848978808821950d49808821958979910821a6097aa18d4a909d49808978820d487077b174803821768977708821870d4870782187897881082198000979918d49808d487078218880097880882199000d4980882199800979910821aa00097aa18d4a909951a001082a6c0f8951a001082aab0f8d49808978820d487077b174003aba628fb9517ff079577b1005020c204de8b2816fb951700107b7b98f8951700107b7a90f8510225fa821788015020c404b983008217b0008477018213200151071bfa28dc00510209fa8217880164355020c604968300645328f6f933068217187b179001018217880151070e821730015020c8047583008217b00151070e8217a0015020ca0463830051060628320e951700108276e0f8951700108277d8f87b17b0015106268215b001955508280b9566ff9555405106148257f85107f482575020cc0425830028e8951700108277d0f851070e8217b0015020ce040d8300821790017b171882179801018216780182192001d3970788770182186001d298085108ddf15107d9f18217c80033007a0428d582003308330633090828daf4951700108276c0f8951700108277b8f87b17b0015106268215b001955508280b9566ff9555405106148257f85107f482575020d00496820028e8951700108277b0f851070e8217b0015020d2047e8200821320012878f49517400333091064585020d404e75f82155003821740037b17b00128c6f39517400333091064a85020d604c95f821a5003821740037b178801280bf8951700108276b0f814070000000000000080aa7627f4951700108279b8f8951700108278c0f8281ef4642601ab680c283782184001aa683114075f427b09ed25b497c96808d6780598550764673300d80428868264675020d80481829555ff9566d8005205f18217d80051070e8217a8015020da04c381008217f8008218f0009575b8005208062864c5821768015020dc04a681002856c5821708018218000151070c64875020de048f8100821710014607951178078210e8018215e0018216d8019511f001320033050849210801821738015107d1c428bdc433073309a82f01280639281282170006821808063309a82f0128f4383309a82f0128ec388216b0015106228215a80164573300e00428c88164575020e004c3819566ff9555d8005206f18217d80151070e8217a8015020e2040581003307960401330ab02301330b902a0133082b9519ff079599500128a14e33073309a82f01288d38281282170006821808063309a82f01287b383309a82f01287338957809951a00107ba718f95518078b008d87067817d0063307580c01330a782301330b682a0133080b9519d006284d4e82178001957520140600000000000000807b16a801280f9588ff9555b800520806287cc38257f88219a801d49707ab970b8257e052071c28e082577b18b0015020e4044e80008218b0018257e05107ca8257e864865020e604388000646828b933070c2807389517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628e23b957809951a00107ba7b0f85518079d008d87067817d0063307ef0501330a782301330b302a013308089519d00628784d957809951a00107ba718f9551807b8008d87067817d0063307981f01330a883001330bc030013308449519d00628484d957809951a00107ba718f9551807e4008d87067817d0063307ef0501330a782301330bb029013308089519d00628184d3309902f0164578218b00128593b33073309a82f0128f6369517ff079577b100951800107b87d0f882171801951800107b87d8f833071832017b17d0064921d806014921f0069518ff079588d1007b18e0064921e8060133082832019517d00628ef3a9517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628b03a3307170501330ab02301330be823013308169519ff079599500128594c9517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628543a951700108277d8f8951800108288e0f83309a82f0128f83533077d1f01330958300133081b28784c6457280d3633070928073633070828013633071128fb35957809951a00107ba718f955180758038d87067817d0063307ef0501330a782301330b002a013308089519d00628ab4b957809951a00107ba718f955180767038d87067817d0063307ef0501330a782301330bc829013308089519d006287b4b33070428953533070d288f35957809951a00107ba7d0f85518076a038d87067817d0063307981f01330a883001330ba830013308449519d006283f4b959709951a00107ba9d0f855170785038d78067818d0063307fb1f01330a883001330b2031013308339519d006280f4b3307d82f012808353307d82f01280035957809951a00107ba7d0f855180790038d87067817d0063307f30301330a782301330b9823013308489519d00628cf4a957809951a00107ba7b0f85518079f038d87067817d0063307960401330a782301330b70280133082b9519d006289f4a957809951a00107ba7b0f8551807ae038d87067817d0063307ef0501330a782301330b5828013308089519d006286f4a957809951a00107ba7d0f8551807bd038d87067817d0063307960401330a782301330bf8270133082b9519d006283f4a957809951a00107ba7d0f8551807cc038d87067817d0063307ef0501330a782301330be027013308089519d006280f4a957809951a00107ba790f8551807db038d870695180010788718f93307ef0501330a782301330ba82a013308089519ff079599190128d749957809951a00107ba7d0f855180708048d87067817d0063307fb1f01330a883001330bf030013308339519d00628a74933073029017b17d0064921d806014921f0064921e006084921e80633084029019517d00628ba379517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006287b379517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006283c379517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628fd369517ff079577d100951800107b8718f982171801951800107b8720f933071832017b17d0064921d806014921f0069518ff07958819017b18e0064921e8060133082832019517d00628b2369517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d0062873369517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d0062834369517ff079577d100951800107b8718f982171801951800107b8720f933071832017b17d0064921d806014921f0069518ff07958819017b18e0064921e8060133082832019517d00628e9359517ff079577d100951800107b8718f982171801951800107b8720f933071832017b17d0064921d806014921f0069518ff07958819017b18e0064921e8060133082832019517d006289e359517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006285f359517ff079577b1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d0062820359517ff079577b1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628e1349517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628a2349517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d0062863349517ff0795779100951800107b87b0f882171801951800107b87b8f83307183201951800107b8718f995180010492820f9019518ff079588390149089518ff079588b100951900107b9828f995180010492830f90133082832019517ff079577190128fe339517ff079577d100951800107b8718f982171801951800107b8720f933071832017b17d0064921d806014921f0069518ff07958819017b18e0064921e8060133082832019517d00628b333957809951a00107ba718f9551807b0028d87067817d0063307ef0501330a782301330b182a013308089519d006284945957809951a00107ba718f9551807bf028d87067817d0063307ef0501330a782301330b7829013308089519d006281945957809951a00107ba718f9551807ce028d87067817d0063307981f01330a883001330bc030013308449519d00628e944957809951a00107ba7b0f8551807dd028d87067817d0063307ef0501330a782301330b9828013308089519d00628b944957809951a00107ba7b0f8551807f8028d87067817d0063307981f01330a883001330bc030013308449519d00628894433072f28a32e9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328723233074032017b174003492148030149216003492150030849215803330868320195174003284b32330714282b2e33070728252e9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328f43133072628d42d9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328a3313307960401330ab02301330bc02a0133082b9519ff0795995001284c433307102801282039957809951a00107ba7d0f855180779023308fdbf780664575020ea046c75781640033307ef0501330a782301330bc8260133080895194003280943646728242d3307170501330ab02301330be823013308169519ff079599500128e74282179001781740033307ef0501330a782301330bb026013308089519400328c6423307d82f0128bf2c9517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628b9309517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006287a309517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006283b309517ff079577b100951800107b87d0f882171801951800107b87d8f833071832017b17d0064921d806014921f0069518ff079588d1007b18e0064921e8060133082832019517d00628f02f9517ff079577b100951800107b87d0f882171801951800107b87d8f833071832017b17d0064921d806014921f0069518ff079588d1007b18e0064921e8060133082832019517d00628a52f95170010827720f995180010828828f93309a82f0128492b33073309a82f01283f2b3309a82f0128372b330774285b2b957809951a00107ba7d0f8551807bb008d8706781740033307fb1f01330a883001330b08310133083395194003280b419517ff079577b100951800107b87d0f882171801951800107b87d8f833071832017b1740034921480301492160039518ff079588d1007b185003492158030133082832019517400328fa2e9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328af2e9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328642e9517ff079577b100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328192e3307782f0128b335951180fc7b1078037b1570037b166803648564765020ec044a5f9766209866209755209855207b1618017b152001951718015020ee04b453837751070628bb1e951718015020f004a253837751070628a91e64859517f001951818015020f2041d528218f00114070000000000000080ab780628851e7b1808821720018218f8017b1830330820ae8706285f1e821700027b17c8007a15d4009517d4007b171801330724077b1720013307e824017b17f0014921f8010249211002951718017b17000249210802019517d8009518f0015020f40490298216d8008215e000821be8003308490501330702330904645a0a640151060b64575020f6042071140700000000000000807b1790008217307b17f0008218c8007b18f800492100014921080108492110014921c0000851087b091407b9e5e41c6d4758bf7b17401407eb113113bb49d0947b1738821890009587187b1788003307c4017b172833072825017b172033074905017b171833073825017b171407f9ffffffffffff7f7b175049114808104911580006209588127b18102841821708019658d800c88707821890009588168219f802821a0003821b08037b787b79087b7a107b7b189555017b15100128077b151001018217f8005107d9089517f0019518f0005020f804cb42ff8217f00182188800ab870628d21c951718019518f0013309d8005020fa04e8728217180182199000c879089588ff888817d3790993895a190966511908340352190995047b19800082163001330718d6760751070628a11c976703976505c97505821750ac7506288f1c821728017b1778821720017b17705105330664585020fc04c46b646982184852070628721c7b19607b17685206260628b406951c100251190a6e045219152f047c19280182172001492148034921500301492158037b19a00051097402140854f029fde5e6dd783302c8780898891ed39808821340ca380898891bd3980814093f74df7d2c6da6da821538ca5808988a1fd38a0bc8790998981ed39808ca380898891bd3980814092af894fe72f36e3cca5808988a1fd38a0ac8790998981ed39808ca380898891bd398081409157c4a7fb979379eca580898861fd38606c8970798781ed38707ca370798781bd38707ca570798751fd375054921a800019517f001283d9727058218a800c88707951828033309205020fe0483718212c8009522017b125803951c10028217a000821bb800821ab000aa7297019517f00101c85b089e8829c85808976911d35a0ad36b0bd3a606d3b505d39a0a98882078789577019ebb13abc7da7b1ab0007b1bb8007c1709027c1808027c190a027c1a0b02977708d4870797991097aa18d4a909d497077c180d027c190c027c1a0e027c1b0f02978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1740037c1701027c1800027c1902027c1a0302977708d4870797991097aa18d4a909d497077c1805027c1904027c1a06027c1b0702978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1738037c17f9017c18f8017c19fa017c1afb01977708d4870797991097aa18d4a909d497077c18fd017c19fc017c1afe017c1bff01978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1730037c17f1017c18f0017c19f2017c1af301977708d4870797991097aa18d4a909d497077c18f5017c19f4017c1af6017c1bf701978808d4980897aa1097bb18d4ba0a82194803d4a808978820d487077b1728037b12c800ab9254fe9517480350200005b3498212c800821750037b17a800283afe82175803821850038219480382151001821a00017b1708037b1800037b19f802aba55bfc3308a0250195170001502002053074ff2847fc7c1744015107620180152001951c24017cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1740037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1738037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1730037cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1728039518280364573309330a330b0a110164765516f7150359060628ce1851061b036468502004058b675207062843187b17c8003308646950200605817028010382171001821800019676d800957501ab87133308b825019517000150200805a572ff82170801c86707951818013309d80050200a05fd6d28d9fa7b198000821720017b17a00082183001821728017b17b0007b18a80051080a018218a800978703978805c978088216b000c868087b18b800280d9566188217b800aa76e5008267088268107b1610030a13015517f70c33087b172003281b9578097b174803531807072804178d8706781719033308010178181803951710037b1728033307f6007b173003951718037b1738033307fa017b17400333078025017b17f0014921f8010249211002951728037b170002492108020233082850200c05776652070628881649214803287b175003492158033308802b01951748039519f00150200e05112e510706286816821748037b17c80082155003821b58033308490501330702330904645a330010050a64018217c800510723ff6457502012050b6a2817ff8215100182170001ab7513330868250195170001502014054c71ff821708019658d800c88707821890009588147b788216a8007b76089555017b1510015106258215b000955508280b9566ff9555185106138257f85107f4825750201605a86928e98217a000510708018217b00050201805946928fa003307088218484911607b176851069600976604821978c896067b1698003306957a08649b017b1ac8007b16b00082b77b17b8007b1ba80082b7087b17a000648550201a055765520706284b1564763308645950201c054f6e330a06646733086459821bb800330c33001e050a12018219c8005317f7072825158217a000e657077b95f87b967b97088216b000956601821ba80095bb10959a18645882179800ab7b8682177051070c82177850202005e0688215100182170001ab7513330850250195170001502022052470ff821708019658d800c88707821890009588137b788218607b78088218687b78107b76189555017b15100101821880005618151201821758d08707847701520735f82800015116ff07289215821790007b17480328274921c80001019518280364578215c8006459330a646b0a117b1648037b1550037b16580301951748037b1718038217287b1720038217207b17f0014921f8010149211002951818037b18000249210802019517c8029518f001502024055a208215d002821bd8028216c802330702330904821818645a0a640151060b645750202605ec6782175803821850038219480382151001821a00017b17f0027b18e8027b19e002aba510951700018218502028051b6fff821708019658d8008219e002821ae802c887078218f0027b79087b7a108219107b797b78189555017b1510019517180133002a05282568ff9517180150202a051d68ff2826f7951700017b1748033307b4017b17500333070825017b17f0014921f8010249211002951748037b1700024921080201951728039518f00150202c05821f8216280382153003821b38033308490501330703330904645a0a640151060b645750202e0512678215100159050628ff13821708017b17b8009656d800956604646850203005fd6252070628b5137b16f0017b17f8019857204921000251070628d5139518f001645750203205bc998216b800520506284c129657d800c867077b17a00033079001017b17a800330a07140700000000000000807b17980028649517f001330908502034050c4482180002018217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178759588087b180002018216b8009566d8008217a000330a07ab760628c311826782199000c879089588ff888817d3970793879777028218a800c887078177c887077b16b8003207821800028217f001aa87140f8217f801c8870746078219f001958501c959077b15000254171f0d0f7b19c800018217f8019568187b17c000c857073309205020380596688266389558208217c800c987077b180002541707f90e8217c00001c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178768217f0018216b800826540958808c987077b180002541707c30e8217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f001826548958808c987077b1800025417078e0e8217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f001826550958808c987077b180002541707590e8217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f001958508c957077b15000254177f270e8217f801956858c857073309800050203a054667955780007b1700029518f001646750203c05f67cff2819fe821800028217f001aa87dd0b8217f801c8870733091078799588017b18000228f7fd821800028217f001aa87cf0b8217f801c8870746070e8217f001958501c957077b15000256171f42069517f001330920645850204205884182150002282a06821800028217f001aa87a40b8217f801c8870746070b8217f001816520958801c987077b180002541703990b8217f801c8870798591878790398591078790298590878790178758217f001816524958804c987077b1800025417037c0b8217f801c8870798591878790398591078790298590878790178758217f001816528958804c987077b1800025417035f0b8217f801c8870798591878790398591078790298590878790178758265189588049857207b1800025107062890108266109518f0016457502046052896330a075105e7fc646b82180002975504c8560c7b1cc80028438217f801c88707985938787907985930787906985928787905985920787904985918787903985910787902985908787901787595880895bb107b180002aacb94fc8217f00181b682b508c987075417034f8217f801c8870798691878790398691078790298690878790178768217f001958804c987077b180002ac7a869517f00133090864b6502048051140821cc800646b330a07821800022867ff9517f0013309047b1bc00050204a05f03f821cc800821bc000330a07821800022895821800028217f001aa87520a8217f801c88707330904282904821800028217f001aa874d0a8217f801c8870746070c8217f0017e6520958801c987077b180002541701420a8217f801c8870798590878790178758266189585027b1500025106b5fb8217b800827810976605282b8217f8019589207b19c800c8570733092050205005a6649555209566e07b1500028218c800510680fb8217f001c9570756171fd09517f0013309207b18c8006458502052052a3f8218c8008215000228b1821800028217f001aa87cf098217f801c887074607098265189588019857207b1800025207de0e8266109518f0016457502056055c94330a0751051bfb646b82180002975504c8560c7b1cc80028438217f801c88707985938787907985930787906985928787905985920787904985918787903985910787902985908787901787595880895bb107b180002aacbc8fa8217f00182b682b508c98707ae7a678217f801c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178768217f001958808c987077b180002ac7a6fff9517f00133090864b6502058052d3e821cc800646b330a0782180002284fff9517f0013309087b1bc00050205a050c3e821cc800821bc000330a0782180002287cff821800028217f001aa87bd088217f801c887074607028217f00181659800958801c987077b180002541703b4088217f801c8870798591878790398591078790298590878790178758217f00182658800958804c987077b180002ae7a99088217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f00182659000958808c987077b180002ae7a67088217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f001958508c957077b15000254177f36088217f801956808c857073309800050205e054562955780007b1700022825f9821800028217f001aa8721088217f801c8870733090f78799588017b1800022803f9821800028217f001aa8713088217f801c887074607039588018217f0017b180002816508c9870754170308088217f801c8870798591878790398591078790298590878790178759588047b18000228b2f8821800028217f001aa87ea078217f801c88707787a288803821800028217f001aa8786088217f801c887074607018219f001958501c959077b15000254171f7e087b19c800018217f8019568087b17c000c85707330920502068056a618266289558208217c800c987077b1800025417076a088217c00001c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178768217f0018219b800829530958808c987077b180002561707b8f728a1f7821800028217f001aa8724088217f801c887073309110178798217f001816528958801c987077b18000254170346058217f801c8870798591878790398591078790298590878790178758217f001958504c957077b15000254171fc8f98217f801956808c8570733092050206c0593609557207b1700022874f7821800028217f001aa87be078217f801c887074607149588018217f0017b180002826508c98707ac7a0ff728f9f6821800028217f001aa87a7078217f801c8870733090528e003821800028217f001aa87a2078217f801c887074607159588017b1800028217f00164698266087c9510c98707ae7a95078217f801c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178768217f0019588087b180002aa8767078217f801c8870778759588017b18000228a8f6821800028217f001aa875c078217f801c8870733090d78798267189586017b160002510783f68218b8008288109588c0007b18c8009675500128398215c800955840ff8217c000c867073309800050207605695f956680007b1600028217b0009577b0fe955550017b15c8006475510737f67b15b0008217f0016475c9670754171f9e008218c8008217f8017b18c8009588c07b17c000c86707330920502078051a5f956620c965077b16000254171f8a008218c8009588e08217c000c8670733092050207a05f45e956620c965077b16000254278f0086008217c000c86707330990008218c80050207c05cf5e95669000c965077b16000256177f42ff9517f00133098000646850207e056339821600028217f8017b17c0002821ff9517f0013309206468502080054539821600028215f0012850ff9517f0013309206468502082052b39821600028217f8017b17c0008215f001285cff9517f001330990006468502084050839821600028217f8017b17c0008215f0012860ff821800028217f001aa87f4058217f801c88707460706018219f001958501c959077b15000254171f9d027b19c800018217f8019568087b17c000c8570733092050208805fb5d8266289558208217c800c987077b18000254170789028217c00001c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178769588087b1800022892f4821800028217f001aa876e058219f801956708c889094609129588017b1800029518f00150208c054173ff2864f4821800028217f001aa8754058217f801c8870733090a28fe00821800028217f001aa874f058217f801c887074607089588018217f0017b180002816508c9870754170344058217f801c8870798591878790398591078790298590878790178758219f001958604c969077b16000254171f2a057b19c800018217f8017b17c0008215b80095580cc8670733092050209205e65c7c592c8215c0009567207b1700028218c800aa781105c8750578599577017b17000228acf3821800028217f001aa8712058217f801c887074607169588017b18000282670882199800ab97778217f001aa872b058217f801c887073309017879c898087b1800022867f3821800028217f001aa87e1048217f801c887073309130178799588017b1800028267108268189519f00150209805415eff2833f3821800028217f001aa87c1048217f801c8870733091778799588017b1800022811f38217f001aa87cb048217f801c88707460764698266189588019867207b1800025207ef068295109518f001646750209c05198c5106dbf2645882150002976605282b8217f8019589207b19c800c8570733092050209e05cd5b9555209566e07b1500028218c8005106a7f28217f001c9570756171fd09517f0013309207b18c80064585020a00551368218c8008215000228b19517f0013309045020a2053a368218000228aefa9517f00133092064585020a4052436821500028217f0017b17c8002852fd9517f0013309085020a6050836821800028217f801286cfd9517f0013309015020a805f035821800022816f49517f0013309015020aa05dc35821800022824f49517f0013309015020ac05c83582180002284ff49517f0013309045020ae05b43582180002285bf49517f0013309045020b005a035821800022878f49517f0013309045020b2058c35821800022895f49517f0013309015020b40578358218000228a1f59517f0013309015020b60564358218000228a6f59517f0013309025020b80550358218000228b2f59517f0013309015020ba053c35821800022824f69517f0013309015020bc052835330a07821800022833f79517f0013309045020be051135330a0782180002283df79517f0013309085020c005fa34330a07821800022857f79517f0013309085020c205e33482180002288cf79517f0013309800064585020c405cc348215000228bbf79517f0013309015020c605b8348218000228d2f79517f0013309015020c805a4348218000228e0f79517f0013309045020ca0590348218000228ecf79517f0013309015020cc057c34330a07821800022806f89517f0013309015020ce0565348218000228dff09517f00133092064585020d0054f34821500028217f0017b17c80028e2f09517f0013309085020d2053334821800028217f80128fcf09517f0013309085020d4051b34821800022831f19517f0013309085020d6050734821800022866f19517f0013309085020d805f33382180002289bf19517f0013309800064585020da05dc338215000228caf19517f0013309015020dc05c83382180002286df79517f00133092064585020de05b233821500028217f0017b17c8002871f79517f0013309085020e0059633821800028217f801288bf79517f0013309015020e2057e338218000228cff79517f0013309015020e4056a33330a07821800022832f89517f0013309015020e605533382180002284cf89517f0013309015020e8053f33330a0782180002284ef89517f0013309085020ea05283382180002285ef89517f0013309015020ec05143382180002288cf89517f0013309015020ee050033821800022897f89517f0013309015020f005ec328218000228fff99517f0013309015020f205d832821800022885fa9517f0013309015020f405c43282180002289ffa9517f0013309015020f605b0328218000228a4fa9517f0013309045020f8059c328218000228b0fa9517f00133092064685020fa058632821600028217f0017b17c80028c5fa9517f00164953309015020fc0568326459821700028215f80128dafa9517f0013309015020fe054e328218000228e1fa9517f001330901502000063a32821800022812fb9517f001330901502002062632821800022832fb9517f0013309015020040612328218000228c8fa9517f00133090150200606fe31821800022828fb8217f8017b17c000821700027b17b80082161001821708017b17c8005106248215c80064573300080628eb54ff645750200806e554ff9566ff9555d8005206f08217000151070d8217c80050200a06265482170851070c82173050200c0617548217c0008218b8008210780382157003821668039511800332003307081028d00b3308ffab876d646750200e06e8533307e8250128761733072828b40b3307120e01330a102c01330b302c0133085695196703287b21951748037b172803330720077b17300333071832017b17f0014921f8010149211002951828037b180002492108020133082832019517f001287a0f33074032017b17f0014921f801014921100249210002084921080233085032019517f00128530f3307960401330ab02301330bd0250133082b95196703280021330760230128f90a645728130b6467280e0b82170851070c8217305020100625533307960401330ab02301330bd0230133082b9519670328c6203307d82f0128bf0a3307170501330ab02301330be823013308169519670328a5209567097b1618035517075e8d78067818f0013307981f01330a883001330b8031013308449519f001287a20330768310128730a3307170501330ab02301330be82301330816951967032859203307170501330ab02301330be8230133081695196703284020951718037b174803330720077b17500333071832017b17f0014921f8010149211002951848037b180002492108020133082832019517f001283f0e3307170501330ab02301330be823013308169519670328ec1f9511c07b10387b15307b16286485647650201206633f9766209866209755209855207b167b1508641750201406d2338377520791006485641750201606c2338377520781006486641750201806b233837752077183588369641733001a06951110fe7b10e8017b15e0017b16d801951188f87b1710017a18b8017a19bc019517d006330026032818397c1751072433082033052050201c06a44d51073f951801330920647650201e06535464672805330501645882103882153082162895114032003307960401330ab02301330bd0230133082b641928131f330720282d09951128fd7b10d0027b15c8027b16c0026485647650202006803e9766209866209755209855207b16b8017b15c0019517b80150202206ea32837752079d089517b80150202406da32837752078d089517b80150202606ca32837752077d084921480210951760029518480250202806772f82186002140700000000000000807b1808aa786e0882186802821770027b187b18a0017b17a8019517a00150202a068032837752074c078387492180023309087b1988024921900233055107d206978820988b20330c98001407f8ffffffffffff7f7b17103302044911300828178407015207fd06821b18c93b0b330c9800510b23038214800233076be67b03c954077b1b187b1328ae3772c8350aac5a0c07d6ca08330752080d0796a99800821610ac96020764a6510417964798008218307b18b8017b17c80133070828053307017b17c00195171801330808951ab80150202c069955ff821718015207ae06821720017b17307b1788027b168002330c98003302048213280196579800957694009a377b17203304013300012841018217e800977708821878d487078218f8009788108219f000979918d49808d487077b17f8008217c8009777088218e000d487078218d8009788108219d000979918d49808d487077b17f0009765088217a800d457078218980097881082169000976618d46808d487078218a0009788088219c000d498088219b800979910821ab00097aa18d4a909d49808978820d487077b17e800821738977708821858d48707821848978810821940979918d49808d48707821850978808821970d49808821968979910821a6097aa18d4a909d49808978820d487077b17e00082153082160001c8650595576cff9518b8013309800050202e0699518217e8007b57ec8217e0007b57f48217f8007a57fc8217f0007a57821588009555017b15900282132882148000d83400954401821820c8480895669800330204330901aa981dfe7b1600018219a801ac290afe821aa0017ca77ca8017b18e8007ca8027b18f8007ca8037b18f0009598fc95ab047b1ba0017b18a801ac28ddfd7ca8047b18e0007ca8057b18c8007ca8067b18d8007ca8077b18d0009598f895ab087b1ba0017b18a801531808affd7ca8087b18a8007ca6097ca80a7b1898007ca80b7b1890007ca80c7b18c0007ca80d7b18a0007ca80e7b18b8007ca80f7b18b000959bf095a8107b18a0017b1ba801330c8000accb64fd959b70ff95ac90007b1ca0017b1ba801330c08accb4dfd7b17787ca790007b17587ca791007b17387ca792007b17487ca793007b17407ca794007b17707ca795007b17507ca796007b17687ca797007b1760959768ff95a998007b19a0017b17a8019517b801330980007b148000502030062c50821780027b158800ab7594fd95178002502032063632821788027b1730287ffd8218800214070000000000000080aa78cf037b18b000821788027b17b80082170851070b821750203406144d51052703965798008216b800c867077b17c0003307870c017b17f800140700000000000000807b17d0003307b6067b17f000330720077b17c8007b15000128168216e800956698008217c00082150001aa76d70295171801330980006468502036067c4f81679000826880007a170c017b18100133080e50203806a3485107e4027b16e80033090e64758218f80050203a064c4f9517b80133090e645850203c06eb32645750203e06694c8218b8018217d000aa783a8215c8018217c001531504227c79017c7a7c7b027c7c03979908d4a90997bb1097cc18d4cb0bd49b0601510809502040062b4c561503073306017a169c019566017a16b0028216000133080e50204206184851075902647533090e8218f80050204406c54e33080e9519b002330a0464570a03013308f7ac870a3308ffab87b902645750204606d54b3308800050204806d74751071e026475951818013309800050204a06834e9517b80133098000645850204c0636138216b8018217c0017b17e00033080350204e069e475107ec016458936880004617023f4617013f46073f51061c3305037b1560027b1768027b157002648750205006634b282494658000821ae000936a80007b1860027b1568027b1a700250205206434b3305030195170c017b17b8018217f0007b17c001951710017b17c8018217c8007b17d001951760027b17d8013307b2017b17e0013307d82a017b1780027b1588024921a0029517b8017b1790027b1598029517a00195188002502054062d038217600251070d8217680250205606d34a8216a0018215a801821bb0013308490501330702330904645a0a640151060b645750205806ac4a95179c017b1780028217f0007b1788023307082b017b17b8014921c001014921d801951880027b18c8014921d00101951748029518b80150205a06b802821748027b17d8008215500281170c0182181001821958027b19e0007a17c0017b18b80195181801330980009517c40150205c06054d951760029518b80150205e06388182166802821a700264578218e00064690a03019578013309f8ae98728217600251070b646750206006004a8217d800510728fd645750206206f049281cfd8217b00051070d8217b80050206406dc49330733088210d0028215c8028216c0029511d80232004921b0004921b8000882170852079efc28a2fc33070e28870133078000288001330703287a019578097b177802551807a7008d8706781780023307ef0501330a782301330b182b0133080895198002282e178217800251070d8217880250206606634933075a1f01330a083001330b2830013308239519b00228041782172001821828013309a82f0128ea0033073309a82f0128e0003309a82f0128d8009578097b1760025518076a3308fdbf78066457502068060f497816b8013307ef0501330a782301330b302b013308089519b80128ac16951778027b17b0028217c8007b17b80233071832017b17800249218802014921a0029518b0027b189002492198020133082832019517800228ab04951760027b1780028217c8007b17880233071832017b17b8014921c001014921d801951880027b18c8014921d0010133082832019517b8012870043307960401330ab02301330bd0230133082b9519b002281d1633077d1f01330970300133081b28931652070764972806648728219511d064783307b02b017b1749110801491120491110084911186417281d049511b87b1764177b173833078e067b17403307c02b017b1708491110024911289517387b1718491120019517089511b88278288279207b18287b1920827818827910827a0882777b18187b19107b1a087b1764177b17303307e02b017b17384711409517303300da0628fa319511a87b10507b15487b1640648a828608828b7b171851065a330595b7086468018279c895059588ff9577105208f582a91851091957054582b708887701885810d28707520736975501015705dc005105327b197b1b087b1a10645850206a0667435107cd00821a10821b088219281782a9183305330701280d33053307012806330701017b15207b17284911305116011352061252090f33063305330801283b51092e3308802b0195172064a950206c06d50a5107383307120e01330a102c01330b302c0133085695193f28a81482b60882b8ac653a330501c85707646950206e06a449c865057b153001821730821828821920821a187ba7107ba8087ba98210508215488216409511583200951720648533086469502070062024645882153082172828b53307f82b012849fe64572863fe828928828720829c183308ce1e01330905320c827851080982770828684632009511e07b10187b15107b16086475827a108277c9a7076496ac972b7b1a825708c8a7076469502078060a498217c876067b56103307821018821510821608951120320064577b1864a8646950207a0698238218825a1028c59511d87b10207b15187b1610330980006475ae98268256108257ab761064577b1850207e066901821801825708c8670778789566012899008b870b48110c52071595170d9889068699c00078190c33090228528b871052072295170e98890c8699e00078190c97893498993a9599800078190d330903282d95170f9889128699f00078190c97892e98993a9599800078190d97893498993a9599800078190e33090401825610825a84883f86888000c96a0a7878ac9a2d7b19825708c8670795180c5020800615488217c87606017b56103307821020821518821610951128320064576468649650208206a422646982561028c59511e07b10187b15107b16086489828a08828818330b01aaba13520a13520810827a103306330501282b5108173308802b0182101882151082160895112028a5088298827a1082798286088285c9a909ac69317b17827808c8a80764586469646564a6502086068047c8560682177b7610330782101882151082160895112032007b17821764a86469502088060e228217827a1028bf9511b87b10407b15387b16306475827795780151084d977901e48908330908e4980657063e5107108258087b18187b1728330701017b17206417951918646850208a06392282175207238217087b57087b56821040821538821630951148320033073309482c0128cdfb8217088218103309482c0128bffb9511e87b177b180847111001641750208c063b2e009511a07b177b180864177b174033078e067b17489518087b18507b17583307482d017b1710491118024911309518407b182049112802951710649828b09511a07b177b18089517087b174033078e067b174864187b18507b17583307902c017b1710491118024911309518407b18204911280295171064982873ff8277019511b87b10407b15387b16307b18987904330814330ada0f01532971028000330814951b2e14094b598638d6c56d347b1910491108102733037b143300ffe0f505016472821710d6720798770b821908ca9709bf9206976930989932ca39099894119899108499fe07964c64bfc606976631986630c8a9097c9c7c9901c8a6067c65017c6678b9ff78bcfe78b50178b69588fc95bbfcac20ac5417633697793098993296997b14989911969b64bfb7079777319877309588fec8a7077c7b017c77951c1cc88c0c78cb0178c764970155170a149588ff95191cc889098677307897281c9777019588fec8a7077c79017c77951a1cc88a0a78a90178a701951a1cc88a0a9a8b1433080182173309502090061382104082153882163095114832009511987b10607b15587b16506473807034649c7b184884080133022b3309000011840704c8b80552070849114828376457510c23330664c4821948c8c905649c017dc98999c085990195cc01c89606ab5cf2280764c4330601c87605644c3309000011018237db82097b1b307b1a2851072e823608ae65288407085207577c3838330201c956077b1c207b1918ad829b005208a9007b1710648728a40082352082362864576468821a4864cb502092066e013308015207b700826c186457821828821930821060821558821650951168320c7b16388137307b17107c38387b1808823720823828481330307b13461338017b17406486821a4864cb502094061f01520767821738c95705955501019555ff5105c900826920330830821740b4900296065107eb2842521802129578019877019888017b18102806491110018238207b18408235288138307b1838957601019566ff510628825920821740821838b4900298065107ec330801016487821060821558821650951168320082164064676458821918821a48821b2050209a068e003308015207d7825a186467821828821930b4a0029c063308015207c18217109a777b17483306ff01821748c867075117ff45825920821740821838b490029e069566015107e52839826a18821740821828821930b4a002a00633080152077eff330882168217107a6730821708786738286aff821610821710d87608285eff821710d876082855ff9511d87b10207b15187b161064a6647c5139000011286485828a2064c764987b1b087b1cb4a002a206821c6458821b08647933070152092051061a828a1864c7646864b9821020821518821610951128320a33070182102082151882161095112832009511a87b10507b15487b1640647c827a827b106486520a0a84b80151081e0384bb01510b910064c082cb186494c869093308510b46330c3303e0003302f0006467646528149556010195cc01c98505c956086465aacb27aa95557d565906e98466ff00ac360bac260d95560428dd95560228d895560328d3646701aa96317d6b64496476640c590b0c84b7ff00330be0000151089d02ae989502c886077d77330bc0afb78d02520a1128900264496476640c01510a860282c3087b19187b161055192027330251091ac89608646a017da78977c085770195aa01c87202ab8af2ac32aa01285502646a9565078455f8c89606c956068468073309aaa5147dab89bbc085bb0195aa01c8b909ab5af27b1c087b13330c510821846bf8c8b50ac85b0bc8b808017dab89bbc085bb0195aa01c8bc0cab8af2986303140a0101010101010101330bff00ff001408ff00ff00ff00ff007b182814000000000001000100140801000100010001007b1820c89c022830821530c89505821638c96303846c03821b28d2b407984808d2b808c88707821820ca8707987730c87202520c97005103820064593307c000e673077b17389778037b18303304531304ba8485e007c8950564980182808507ff987707980b06828c08d4b707d2a707c8470785cbff98bb0782861098cc06d4cb0bd2ab0b856cff98cc07986606d46c0c828618d2ac0cc8cb0bc8b707856bff98bb07986606d46b0bd2ab0b958820c87b04ab58aa2853ff821918821610821c088213ac325d28090133088467fc00977703c8790397cc03821918821610018237953308857bff98bb07987706d4b707d2a70795ccf8c87808520ce6821a28d2a807988808d2a808c88707821820ca8707987730c87202821c088213ae32b3007cca38c923085a1a010c510a1a491120281a521a02129587019888019877017b172028087b182033080182c7207b173882c52881c7307b1730958601019566ff51061a825920821738821830b49002a4065107ec330701288300825a18821738821810821918b4a002a606647833070152086a8217209a777b17283306ff01821728c867073308ffaa8740825920821738821830b49002a8069566015107e32834ab987bfddb6809db6606520a82fd82c82882c720828c186468821050821548821640951158320c821620821720d876072809821720d876070182105082154882164095115832009511c064783307e20e017b17304911382b9517307b17491108014911204911100849111864172838f88279088277829c18320c9511807b10787b15707b166849115820330a03781a608296204911284911387b17487b18505106f70082972851075b0182957b19088298107b18109578ff9788039888039588017b18955908977803977706c98705956618017b1920829951091b821a508217488218208288f882aa18b4a002ac06520738017b15188267107b17587c69188268f8826778196051082152180112977704821810c8870782785108073308280e8277083308012806330801018269e87b18287b1730511902198268f05219011e978704821810c8870782785108073309280e827808330901280633090101826708977704821a10c87a0a82a782aa087b19387b1840951828b4a002ae068215185207a7008219209599109555c8956638520543ff2860829718510768829510977804c858087b18207b190882969577ff9777049877049577017b17956608018269510917821a508217488268f882aa18b4a002b0065207528257825908951828b49002b206520742955510956610821720ab75ce821908821a829708ac7a0d282d330a829708540726829797a804c88709821a50821748829882990882aa18b4a002b40651070833070128053307018210788215708216689511800032009511c07b10387b15307b162880707b18980b0433080a3309da0f01532b7102da0033080a951b261402000000005917b7d17b1210491108102733047b143303ffe0f50533066401970c20821710d67c0c98cc2d821708ca7c05bf500a97a530985532ca45059857119855108455fe07ca6707bf7a0a97aa3198aa30c895057c57017c55c89a0a7ca20178b7ff78b5fe7ca778b201830a9588fc78b795bbfc64c0aca3a7541c635897c730987732967a7b1498aa1196a764bf7c0c97cc3198cc309588fec89c0c7cc7017ccb951c1ec88c0c78c70178cb531a0a2b97aa019588fec8a9097c97017c99951a1ec88a0a78a70178a9281d640c561063b083ca551a0add9588ff95171ec8870786a930787901951a1ec88a0a9a8b0a330801821733095020b806faf68210388215308216289511403200951178ff7b108000808934849a10520a1484992052094a821080009511880028a7f5330a810095197f0164ab987c0484770f88720a867a30957757db2a0778979599ff95baff64c7520ce295b7fe56278000659aab8100641ac87a0a283d330a810095197f0164ab987c0484770f88720a867a30957737db2a0778979599ff95baff64c7520ce295b7fe56278000359aab8100641ac87a0a01330902648733082523015020ba0643f6821080009511880032003309002d0133088000288cf43309002d01330880002880f49511d87b10207b15187b16107b1751098501330a330b0fe4b90b95bbf1958c0784ccf8c98c079584089a93330cee11017b1c08280995aa01ae9a5b01c8a8057d5557054ebfa7058455075205ebaeba26c8a8058255c8a4068266140c8080808080808080d46505d25c0c520c0995aa10acbae0ae9aa2009aa5c88a0a017dac570c93009555ff95aa01ab53f42807018455ff00821c08c85c0c7cc6330c04aac650330c03aac626330201330c02abc6f60095a001ae90eb00c8080c7dcc3305013306bfafc6c90028ea0095a601ae96d200330201c886067c665125e000426c605225ed0052330c9faf0c6528b80095a601ae96ae00c886067c663302015125f0002b6c665225f4005a330c8faf6c602894009a5a01ac9a27ff287a846ce0003305a000aa5c2a287d956c70ff531c30402873955c1fff33060cac6c0e845cfe003305ee00ab5c5f330cc0afc05995a002ae904fc8080c7dcc581cbf34284c955c0fff561c0241330cc0afc63a95a502ae9530c885057d5c3305bfadc52e95a003ae901fc8080c7dcc5a1cbf25950a01ac9aadfe330a82177b78087b7910282333022810330501280b33050228063305030182177b7a08787210787511330a01017b7a8210208215188216109511283200951168ff7b1090007b1588007b168000647c7c77087b1b107b1a08330a0133050152077b016492648382c68067347cc9098478047b1c1852088d00826828330a4f0f01826720828b183308520f01da9a0885990364257b13b4b002bc0682186459821c18330501330a0152073201826a2882672082aa18b4a002be06330a01821c1833050152071701826828826720828a1833084d0f01330902b4a002c006330a01821c183305015207f4008217086468821910b49002c206330a01821c18647528db00520932826828826720828a183308540f013309037b126435b4a002c40664538212330501821c18330a015207ae008167340182682082692833050178153f7b18207b192895183f7b18308168307c6938826a826b08826c108266187a17747a18707819787b1a407b1b487b1c507b16589517207b17603307d02c017b1768951720643864295020c6066e52074733084d0f019517203309025020c8065b520734951840821708821910b49002ca06520723821868821760828a183308570f01330902b4a002cc066475821c18330a012809821c18330a010178c508461c090164c78210900082158800821680009511980032009511807b10787b15707b166833063304491148330b0a0a0a0a14000a0a0a0a0a0a0a0a140a00010101010101017b1a60827a107b1a40827a7b1a388277087b17307b18509588ff7b1833077105017b17089a977b171033050a7b19287b1020283e8217c867077c779577f6887701018218407887821730827a18c94609821850c84808821738b4a002d006645482192882165882102033050a5207610182174884770152075301ae690a280301ac690001c96902821350c8630333070fac2723aa69ea003308821c10c86c0c643a017cabaa5b3a9588ff95aa01ab8cf528cd00953b0784bbf8c93b08510831330a01c8a3077c77aa571a95aa01aba8f57b14487b1658952af0ae8a2228599a8a01c8a608958601ae98992881007b14487b1658952af095bb08280695bb080182bcf882b6d30c04d30606821560c96507d4670714068080808080808080c94505d45c0cd2c707d26707ab670c95881095bb10ae8acdaa288d00330ac88307c9890b821658c96b0b82144833050a01c8a70c7cccaa5c0b95aa01ababf52821c88a0ac8a608958601ae9819ffc8a3077c77ab5710ff330764657b165828126496017b165833070164456496aa943f7b17488217407c77510724821730827a183309048217388218087b15186445b4a002d2066454821518520717ab467bfe33072883fe649682144828b933072806330701018210788215708216689511800032009511d87b10207b15187b16108276107c69827b827508510938825c18330a71050133090464b77b180833087105017b1bb4c002d606821b82180851071433070182102082151882161095112832009587f68877017867825c2064b7821020821518821610951128320c6489828a08828818330b01aaba11520a1152080e33093308012871fd51080b3308d02c01281cf682998298829908285cfd9511907b177b18087b19107b1a1864177b17503307b8077b17589517107b17603307aa067b17683307b02c017b1720491128024911409518507b18304911380295172064b828f5ed9511a07b177b180864177b174033078e067b17489518087b18507b17583307882d017b1710491118024911309518407b182049112802951710649828b7ed9511b07b177b180864177b17403307b8077b174833070009017b1710491118014911309518407b18204911280195171064982882ed00951168ff7b1090007b1588007b1680007c7a10827c330501520a1701648b827608806834848a047b17187b1c10520a4f7b1b7b190888c201330b520f0182692833085c0f01826720829318dbcb08330b02c92b09b43002dc06821c1064788217185208ce0082176468821908b49002de06821c10647582171828b600520c337b1b7b1908826828826720828a1833085d0f01330902b4a002e0066478821718821c1052088d00816834821908821b01826720826a2833050178153f7b17207b1a2895173f7b17308167307c6a388263826c088262108266187a18747a1770781a787b13407b1c487b12507b16589517207b17603307d02c017b176895184064b7b49002e206520723821868821760828a183308570f01330902b4a002e4066475821718821c102809821718821c100178751095c8017b78821090008215880082168000951198003200951170ff7b1088007b1580007b16787c7a08330b01330501520a1301648c82768068347c7a09848b047b1710520b49510a32826828826720828b183308520f017b190833090264c5b4b002e606645c330b0182190864788217103305015208ce0064c76468b49002e806330b01647582171028b900520a36826828826720828a183308600f017b19083309013305017b1cb4a002ea06821c8219086478821710330b015208880081683401826720826a283305017815377b17187b1a209517377b17288167307c6a38826b8263088262108266187a186c7a1768781a707b1b387b13407b12487b16509517187b17583307d02c017b176095183864c7b49002ec06520723821860821758828a183308570f01330902b4a002ee066475821710330b012809821710330b0101787508461709018210880082158000821678951190003200951160ff7b1098007b1590007b16880064758217e8007b17708217f0007b17688257288216f8007b1660821600017b16588216c8007b16508273188217d0007b17488217d8007b17408217e0007b17388217a8007b17308217b0007b17288217b8007b17208217c0007b17188217a0007b17108257207b1c087b1b64a6b43002f0067b1578781780004621810095177864688219821a08821b105020f206c0f7951778821830821928821a20821b185020f406abf7951778821850821948821a40821b385020f60696f7951778821870821968821a60821b585020f80681f77c1981007c188000d4890751094284880152083c8217787c783484880452081a827828827720828a1833085a0f013309023300fa06320a827828827720828a183308590f01330901b4a002fa068477018210980082159000821688009511a00032009511c87b10307b15287b16205618271d978a02330bf00101c8ba0a81aac8ba0a320a33085c3028d201330a5caba80a33085c5c28c501330a0003aca804018499015109fd0088892f1801330a11949a95ac0897cb023309541a01c89b0b81b68a8b0b8a660bd86b06da6c0a95a604976c02c89c0c81cc8acc0bd8cb0cdac60a95a602976c02c89c0c81cc8acc0bd8cb0cdac60a95a601976c02c89c0c81cc8acc0bd8cb0cdac60a95a601976c02c89c0c81cc8acc0bd8cb0cdac60a97ac02c89c0c81cc8acc0bd8bc06d3cb0b88bb01c86a0ac8ba0a330c21acacf40297ab02c89b0b80b99896153302ef02511a210d80bc0498c215510a0c80bafc84aaffff1f01856bffc82b0b510b30330cbfa80b330aef02e4a6039522ff330adc1a0101aa639f02c86a097c99be9c0caccb0c956601ab62ee64260184660152060a01551820db008b8914330a300901c8a9097c99868b0178191797892c98993cc8a9097c9c97893098993cc8a9097c9669b9989902781c18781619978b3498bb3cc8ab0b978c3898cc3cc8ac0c84880fc88a0a9598fe4611167cbb7ccc7caa471114781b1a781c1b781a1c46111d7d951a14c88a0b330c5c78bcc8a9094619ff7546097b811914811a187f1b1c28020133085c6e2828849900015109dc0133085c27281a33085c72281433085c74280e97992f5909c30133085c22017a784717084717064717043308000279780a01821030821528821620951138320053187f99018b89105209be00330629016e823303df1601330a2f1701330c511801330928330b220128c1008b8914330a300901c8a9097c99868b0178190b97892c98993cc8a9097c9c97893098993cc8a9097c9669b9989902781c0c78160d978b3498bb3cc8ab0b978c3898cc3cc8ac0c84880fc88a0a9598fe46110a7cbb7ccc7caa471108781b0e781c0f781a104611117d951a08c88a0b330c5c78bcc8a9094619ff7546097b811908811a0c7f1b10017a797a7a04797b0878780a33080a78780b282fff8b89115209633306e6016e823303d11301330a291401330cf9140133092c330bd000017b16647564276486643833000a079511d07b10287b15207b16187b1c083303821c307b1c987008979901c8980c8476ff007b1a1028ef03646864796457510904fe286f848afeff1f513a1eb802f9fd8489e0ff1f5139e0a602edfd8389c048fd5619f9e1fd83895031fd5619f1d7fd838a1014fd330bf0acabcafd83890008fd56295df6c1fd83890000fd56291dfab6fd8389b0ecfc5619faabfd838900fff1330aaf22f5ac9a9cfd5538f0010e98fd3309800078797a78042850fe3309c82e013308ef026437283ae63309b02e0133082264a7282de600951130ff7b197b1a08532801014f7d7300013302bf330c03ad32197d73ff00330c025a13bf0f7d7cfe0089ccc085cc010195c3fd00c8370c7dcc581cbfcd007b17107b1318330205330cee12017b1c207b1228ae9817285b7b17107b1818330c017b1c20491128ac984aaca847ae9aa50064177b17800033078e067b1788009518087b1890007b1798009517107b17a0003307b8077b17a8009518207b18b0007b17b8003307a82d017b17503307042840d89807db790a7b1a409517407b17800033078e067b1788009517107b1790003307b8077b1798009518207b18a0007b17a8003307382e017b1750330703017b1758491170951880007b18607b176895175064b828d3e43309643a33000c0728f1fe510912ae890fc8970c7dcc89ccc0dbc90a017b1a306489ae8a3a330903e49a029522fd95ac01ae2c0f3309682e01642764c828a1f6c92c09c87a0a3303c00151090e7dac9599ff95aaffad3cf5c8290901c8970a52090bab891364b7280becae891c7da25a12bff328167da88487ff005708177a173c3308012876aa89dc648a33000c07286afe7cac0184781f84cc3f5427df00337ca20297c306842c3fd4c30c5327f0002a7ca70397883d98882b97cc0684773fd4c707d4870751370000119f2811978706d4c707280997870cd4c707017a173c3308015327800017987a0b330802510a0c9877108e7895880301c898087b19407b18489517307b17800033078e067b17880095173c7b17900033070e077b1798009517407b17a00033071a077b17a8009517107b17b0003307b8077b17b8009518207b18c0007b17c8003307e82d017b17503307052893fe9511c87b10307b15287b1620828528828920825a2064763308277b190864977b1ab4a0021007647833070152085d816895171433090101951614502012078df97c1714522780002581181882150864578219b49002140752072d33082764578219b49002160728227c181e7c191f825a18c98909c868088215086457b4a00218075107d93307010182103082152882162095113832009511e87b10107b15087b1664758277648650201c079eed52071c826828826720828a183308de0e01330902b4a0021e075107133307018210108215088216951118320082570864688210108215088216951118285ced7c847c8901958802c89302ab0421ac32850064b4ac2b8b00c8a3030151091a7c359533019599ffab65f52855ac40146423abc8cf280c6423644b821a10abc8c3821c088214c8c404330801017dc995ca0157090d64acbf970759071e2826aa4a86007cca0195cc0284997f979908d4a909bf970757070e858801ab4cd2280533080184870182102882152082161895113032003309982e016437642828fef3642764489511a07b177b180864177b174033078e067b17489518087b18507b17583307682d017b1710491118024911309518407b1820491128023308982e0195171028abe13307802e012845e99511b87b10407b15387b1630827a7b1898ab043308143309da0f01532b71028300330814951b2e14074b598638d6c56d347b1710491108102733007b143303ffe0f5053305640164a4821710d67a0a98aa0b821708ca7a0cbfc406976c3098cc32ca0c0c98c71198cc1084ccfe07ca5707bf7606976631986630c89c0c7cc77ccc01c896067c62017c6678bcff78b7fe78b20178b69588fc95bbfcac43ac541a633697a73098773296777b14987711967b64bfba0a97aa3198aa309588fec89a0a7cab017caa951c1cc88c0c78cb0178ca647a01551a0a149588ff95171cc8870786a9307879281c97aa019588fec8a9097c97017c99951a1cc88a0a78a70178a901951a1cc88a0a9a8b14330801821733095020220741e28210408215388216309511483200951178ff7b108000817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000289a9b8100641ac87a0a3309026487330825230150202607dfe1821080009511880032003309002d01330880002828e09511d064783307f02e017b174911080149112049111008491118641728f4df9511b87b10407b15387b16306475827a95a70151077e97a8018fa9da970898893a330752096e330904e498069769063308f1140bf8ffffffffffff7fac9b55510a1482570897aa067b17187b1a2833070828053307017b17206417330808951a18502028073529ff82175207198217087b57087b5682104082153882163095114832008217088218103309002f01281fdb3309002f012817db9511b87b10407b15387b16306475827a95a70151077197a8018fa9da970898893b3307520961330904e49806976905570955510a1482570897aa057b17187b1a2833070128053307017b17206417330801951a1850202a07a928ff82175207198217087b57087b5682104082153882163095114832008217088218103309182f012893da3309182f01288bda9511e07b10187b15107b16086475827a108277c9a7076496ac97297b1a825708c8a707646950202c077c258217c876067b5610821018821510821608951120320064577b1864a8646950202e070c8218825a1028c89511b87b10407b15387b1630c88909ac895064758277977801330a08e4a909e4980657063d5107108258087b18187b1728330701017b172064179519186468502030073882175207238217087b57087b56821040821538821630951148320033073309302f0128cdd98217088218103309302f0128bfd99511d07b10287b15207b1618829a087b1710510a3282971051072c7b1782957b180850203207f31d51074964766458821950203407a424645750203607d021821508280d648550203807cf1d647601886701330801db66088219107b98087b95107b978210288215208216189511303200330682150828d97c779777033309282301c879098299330af03201828b28c8a707827a82872082bc1864a8320c9511907b10687b15607b1658828a97a902330b900201c8b9098199828608828810c8b9097b1720330b330c3209330c646b2807646b648c017b18107b16183307013306f73308330964a5330042070a12016478ae6746570807017b1808510853648650204407151d51071e017b1733086469502046070f26645a9757023308d80201c887078177c88707330b330c64693207330c821b18283f5218ffcc00140700000000000000808218207b8728a200645a9757023308200301c887078177c88707330b330c3207330c821b182849821b18821c10013305f78217647633080a12015317f7295217ffa500140800000000000000808217207b78646782106882156082165895117028632064652833821b18821c10013305013307013306f7330833090a12015317f71a5217ff8200140800000000000000808217207b7828118217208218087b787b75087b78100182106882156082165895117032003307f02f0128c7d733074032017b17284911300149114849113808491140330850320195172828dfdb646728c0d733074032017b17284911300149114849113808491140330850320195172828b9db33074032017b1728491130014911484911380849114033085032019517282898db951178ff7b1080007b15787b1670648664756487502054075901837752070f826b08978720987a20aeab20140700000000000000807b57018210800082157882167095118800320083885108d50064a77b157b1610826a330c3306491120016478017b182833070040e67805c96c077b1b30ae5722c85609ac69d0005709cc007b1a08510c168217207b17587b1c68330701280ac8560728343307017b1760951740330801951a587b1938502056078f24ff8217405207aa008217487b1720821738647c821b30821a08017b1738ac678400ac5b64821720c8670764a8645964a67b1c18502058078621821c18821b30c95b0bc8560a8217107b7a821828c958087b7b088219386496520859ff140700000000000000808215aa7c12ff280b330c330949112001017b5c8217207b57087b59102805ff8215510cf3fe82172050205a075f1e28e6fe33073309a82f012802d63309902f016467821838284eda8217488218503309a82f0128e7d59511c87b10307b15287b1620827a08330901510a1a01827b7cb895a2ff95b5017b757b720851080101330cff00abc85f330c09accaf9007cb8027cb9017cbc037cb604978808d4980897cc10976618d46c0cd4c8087cb9067cbc057cb6077cb508979908d4c909976610975518d46505d45909979920d4980895aaf795bb097b7b7b7a089887208e7928a3006c8a570a0a3306491118284c84a84033060151083984a82033060251083084a81033060351082784a80833060451081e84a80433060551081584a80233060651080c84a801330607520868491118ae6208330901284c7b170895171864587b1a1064697b1250205c070c208217c96707c865058218087b857b87088567079b77ff821818821910e07907976903cf9707c878089887208e7928089887208e7901649782103082152882162095113832003307782f012889e09511b87b10407b15387b16306475827a95a7015107830097a801e47807330804e487063308b800d68609330752096a9669b8003308f1140bf8ffffffffffff7fac9b56510a1582570896a8b8007b17187b182833070828053307017b17206417330808951a1850205e072322ff82175207198217087b57087b5682104082153882163095114832008217088218103309c02f01280dd43309c02f012805d49511c87b10307b15287b1620827a08330901510a1101827b7cb895a6ff95b5017b757b7608510863330cff00abc861330c09accaf10033097cb8027cbc017cb6037cb504978808d4c808976610975518d46505d485027cbc067cb6057cb5077cb80897cc08d46c0c975510978818d45808d4c808978820d4280895aaf795bb097b7b7b7a08289e0064892899006c8a570a0a3309491118284c84a84033090151083984a82033090251083084a81033090351082784a80833090451081e84a80433090551081584a80233090651080c84a80133090752085e491118ae960833090128427b170895171864587b1a107b16649650206007291e3309c865058218087b858217c967077b87088567079b77ff821818821a10e07a07976603cf6707c8780801649782103082152882162095113832003307782f0128b0de9511b87b10407b15387b16306475827a95a7015107830097a801e47807330804e4870633089800d68609330752096a966998003308f1140bf8ffffffffffff7fac9b56510a1582570896a898007b17187b182833070828053307017b17206417330808951a18502062074a20ff82175207198217087b57087b5682104082153882163095114832008217088218103309c02f012834d23309c02f01282cd29511887b10707b15687b16607b17103305f77b1864877b190864983309330a330b0a010164765517f724570660510632646850206407501651078200647533086469502066074a1f281b5216ff48140700000000000000808218107b87281f3305010182178218086459330a646b0a018217107b75087b767b7610018210708215688216609511783200330738310128a5d19567097b1618551707278d78067818203307981f01330a883001330b503101330844951920287ce764672897d19517187b1750330720077b175833071832017b1720491128014911409518507b18304911380133082832019517202880d59511887b10707b15687b166064753307ff7b18087b1910330a330b330c0a020164763308f7ae86415106507b1557068d006468502068075f155107890064753308646950206a07591e3307ff821808821910645a330b646c0a0282177b75087b767b761028205216ff2c140700000000000000807b57280e49053307017b57084915100182107082156882166095117832009567097b16185517072f8d78067818203307981f01330a883001330bc83101330844951920288ce63307b031012885d06467289fd09517187b1750330720077b175833071832017b1720491128014911409518507b18304911380133082832019517202888d4951158ff7b10a0007b1598007b169000647533084c50206c07851451070702647633094c330850206e077e1d3307ff64680a04015517f7f4017c67197c68187c691a7c6a1b977708d4870797991097aa18d4a909d497077c681d7c691c7c6a1e7c6b1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17787c67117c68107c69127c6a13977708d4870797991097aa18d4a909d497077c68157c69147c6a167c6b17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17707c67097c68087c690a7c6a0b977708d4870797991097aa18d4a909d497077c680d7c690c7c6a0e7c6b0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17687c67017c687c69027c6a03977708d4870797991097aa18d4a909d497077c68057c69047c6a067c6b07978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17609567207b17504911582c95175050207007e6fa7b18285207cc0095175050207207d6fa7b18205207bc0095175050207407c6fa7b18185207ac0095175050207607b6fa7b181052079c0095175050207807a6fa7b180852078c0095175050207a07b4f883777b1852077b821778821870821968821a607b17487b18407b19387b1a30646750207c07be16821748821840821938821a307b57187b58107b59087b5a8217287b57208217207b57288217187b57308217107b57388217087b574082177a57488210a00082159800821690009511a800320033074c2846ce3308ffab871b646750207e075e163307dc1f013309d8300133081f288ee49578097b1750551807228d87067817603307981f01330a883001330b98310133084495196028e0e39517507b1730330720077b173833071832017b176049116801492180009518307b187049117801330828320195176028e8d151092a827b0001330aff0001acba1e7c8cc87b0b78bc827b000195880195bb019599ff7b7b00015209e5330732009511a0fe7b1058017b1550017b17330a2e2001330701330b15330833090a6433050149210801330843200195170833090f50208207a164177b174001330786077b17480133070009017b1710017b15180149213001951740017b1720017b15280195170895181001502084073401821b0801330701951a08330833090a6400951168ff7b1090007b1588007b1680008285288276828720825a1833080d0f0133090c7b17187b1a10b4a00288073308015207d8007b168267089578109579147b17503307b8077b17587b18603307b6067b17687b19707b17783307602c017b1720491128034911409518507b18304911380395192082161864677b1508645850208a078dd8510708330801287d3308190f013309026467821a10b4a0028c073308015207668217827a82a77b175082a9087b195882a8107b186082ac187b1c6882ab207b1b7082aa28330b017b1a78aab913821808520913520c103309330801281b821808510c10951950646750208e071fd828128278827908016467821a10b4a002900764780164878210900082158800821680009511980032006489828a08828818330b01aaba0b520a0b520808283e51080b3308482f0128d2d7829982980851082c829b827a00013309ff0001aca91e7cbcc87a0a78ac827a000195bb0195aa019588ff7b7a00015208e532009511f8481104552880000e9519057818042880008b890b52091c951906988a0686aac000781a0484883f8688800078180528608b8910520929951907988a0c86aae000781a04978a3498aa3a95aa8000781a0584883f868880007818062834951908988a1286aaf000781a04978a2e98aa3a95aa8000781a05978a3498aa3a95aa8000781a0684883f8688800078180701827a0001951b043308ff0001aca81d7cbcc87a0a78ac827a000195bc0195aa017b7a000164cbab9ce6330795110832006489828a08828818330b01aaba0b520a0b520808283e51080b3308482f0128bdd6829982980851082c829b827a00013309ff0001aca91e7cbcc87a0a78ac827a000195bb0195aa019588ff7b7a00015208e5330732009511e8fe7b1010017b1508017b1600014921c8009517e0009518c8005020960732f18218e00014070000000000000080aa7882028217f00053170861028479f8511908590251191054028479fe5119184c02957be6531b044402957be2531b043c02957ade531a223402957abc531a122c0251195627029579a85319061f02957aa2531a101702957a92531a100f0295778253170807028217e8007c7c187c79197b19607c721a7c701b7c731c7c741d7c796b7c7a6a7c7b1e7b1bb0007c7b6c8a9908d4a9097c7a1f7b1aa8008aba107c7b207b1ba0007c7b217b1b98007c7b427b1b387c7b437b1b287c7b547b1b307c7b557b1b207c7b567b1bc0007c7b577b1bb8007c7b5c7b1b707c7b5d7b1b687c7b6d7c757a7b15587c757b7b15507c757c7b15487c757d7b15407c7582007b1590007c7583007b1588007c7584007b1580007c7585007b15788abb18d4ba0ad49a065108247b121864057b13107b14087b1c502098075611821c8214088213106450821218018467015207480151064e0133070810c3670752074b018215b8009755088218c000d45808510843018217a8008a77088219a0008a9910821a98008aaa18821bb000d4b707d4a909d49707c3870852082201821888008a8808821980008a9910821a788aaa18821b9000d4b808d4a909d49808ae780701821768977708821870d487079678abaa6e8856285555fb00821860978808d4c808978901c898086e88ab87ee008218508a88088219488a9910821a408aaa18821b58d4b808d4a909d4980852288000d60039096c27035629ff00d500970908973a10974b18d42909d4ba0a821b2897bb08821c2097cc08d4a909821a38d4ba0a821b30d4cb0b3d096827033d0a6c27033e0b6027033c0758270382101001821508018216000195111801320033075a1f01330a083001330b2830013308239519ff0028c7dd33077d1f01330940300133081b283dde330704230133081d2857330780320128cfeb3307c8220133083c2845330798320128bdeb330793220133083528333307602201330833282933072e2201330832281f330701220133082d28153307ca2101330837280b3307882101330842017b17e0007b18e8003307522001330ae03101330b00320133081f9519e0002837dd9511907b10687b15607b1658828a2882797b19408276088285207b1a2082a92033082264577b1910b490029c073308015207670251061e027b153302330a3303a149110801000133002233045c82154064687b1618280fc95808c89a0a8216185108c0013307c885097b193801c875097c9b95bc81ac3c12aa0b0faa4b0c957701ab78ec2898017d988486ff007b123057080b9599017b1928285d7c9b0184681f84bb3f330cdf00ae6c337c9c0297bb0684cc3fd4cb0b330cf000acc62e959c047b1c287c990397883d98882b97bb0684993fd4b909d48906281d9599027b1928978806d4b806280f9599037b192897880cd48b0601c8a70595174c646882190850209e07bce07c174c522780001664593303a182123033002233045c28d9007c17577c1856bf87078477ff0082123033045c5217010f64593303a133002228b700ac257d01821840c82808510216821718ae720c7d875a17bf0b286401ab72610151051c821718ae7512821740c857077d775a17bf0b284801ab754501821720827a18c9250982177b1a30b4a002a007520716017c174c52278000188118508217821910b49002a20751072328fa007c18567c1957c9890995174cc878088217821a30b4a002a4075207de00330701532680001798680b33070251080c9867108e77957703016459c857023303a133002233045c01821838821528330a0133078000ac7654fe98670b330a0251074afe9866108e6a95aa03283efec88a0a01ac2a8c008215510230ae6236821740c827077d7764285817bf76510a21ae6a29821740c8a7077d7764825817bf6264a6281b330828173308520ae53306280e6468aa62d928486482ab6a44821720827a18c98609821740c878086457b4a002a6073308015207133308226457821910b49002a8076478016487821068821560821658951170320033080128ed330b302d018217406468642933000c0728dbe2330b182d018217408218186429645a33000c0728c5e2951130ff7b10c8007b15c0007b16b800827508827955250304a0005105da018287207b17308287287b17283307b4077b1720140720000000080000007b171833070009017b1710017b19389517387b17708217207b177849218000024921900049219800024921a0008217187b17a8004621b000038217107b174049114801951880007b1860491168019518707b18504911580164969519408217308218285020ac07cfcf520754019555ff9569015205992843017b15088287207b17308287287b1728330600023307b4077b1720140720000000080000007b171033070009017b17187b196495017b15389517387b17708217207b177849218000024921900049219800024921a0008217107b17a8004621b000038217187b174049114801951880007b1860491168019518707b1850491158019519408217308218285020ae0730cf5207b5009566ff95550152069b821728827a183308212301330903821730b4a002b0076478330701520891008217088218c88707957500fe33060002140720000000080000007b1710017b15389517387b17708217207b177849218000024921900049219800024921a0008217107b17a8004621b000038217187b174049114801951880007b1860491168019518707b1850491158019519408217308218285020b2078bce5207109566ff95550152069c33072806330701018210c8008215c0008216b8009511d0003200951178ff7b10800082777c77330a810095197f0164ab847a0f98770488a20a86ac3095aa57db2c0a789a9599ff95baff5207e495b7fe56278000289aab8100641ac87a0a330902648733082523015020b607efc7821080009511880032003309002d01330880002838c6827a827908648764a82847ca9511b8fe7b1040017b1538017b1630017d79330b1fad9b075209a7037d7a015a1a1f08520a9c037d7b0233061fadb607520b8f037d7c035a1c1f08520c84037d720433061fad2607520277037d74055a141f0852046c037d730633051fad350752035f037d76075a161f08520654037d75087b15c0005a151f0c8215c000520541037d75097b15b8005a151f0c8215b80052052e037d750a7b15b0005a151f0c8215b00052051b037d750b7b15a8005a151f0c8215a800520508037d750c7b15a0005a151f0c8215a0005205f5027d750d7b1598005a151f0c821598005205e2027d750e7b1590005a151f0c821590005205cf027d750f7b1588005a151f0c821588005205bc027d75107b1580005a151f0c821580005205a9027d75117b15785a151f0b821578520598027d75127b15705a151f0b821570520587027d75137b15685a151f0b821568520576027d75147b15605a151f0b821560520565027d75157b15585a151f0b821558520554027d75167b15505a151f0b821550520543027d75177b15485a151f0b821548520532027d75187b15405a151f0b821540520521027d75197b15385a151f0b821538520510027d751a7b15305a151f0b8215305205ff017d751b7b15285a151f0b8215285205ee017d751c7b15205a151f0b8215205205dd017d751d7b15185a151f0b8215185205cc017d751e7b15105a151f0b8215105205bb017d751f7b15085a151f0b8215085205aa017b1888a801889a01d4a80888ca0188bb018e99d4ba0a85bb03db890bd4a808884901882a01d4a90985aa05db8b0ad49808886901883b01d4b90985bb07db8a0bd498088219c000889901848a01859c09dbab0c821ab80088aa01d4a909d498088219b000889901848a01859b0bdbac0b821aa80088aa01d4a909d498088219a000889901848a01859c0ddbab0c821a980088aa01d4a909d4980882199000889901848a01859b0fdbac0b821a880088aa01d4a909d4980882198000889901848a01859c11dbab0c821a7888aa01d4a909d49808821970889901848a01859b13dbac0b821a6888aa01d4a909d49808821960889901848a01859c15dbab0c821a5888aa01d4a909d49808821940889901821a4888aa01821b5088bb01d4ba0ad48a0a84880185bb17db8c0b84a801859c19db8b0c821838888801d49808d4a808821920889a01821928889901821b3088bb01d4b909d4980684880185b91bdb8c0984680185ab1ddb890b8218088e8895881f33091e821c10dbc809821818888801d4a808d46808848801db8b0964789517c80064855020bc07ffcd8217c80051076282186457017b17c8004921d000089517c8007b1720013307aa077b1728013307d032017b17f0004921f80002951720017b1700018287208288284921080101492110019519f0005020be07e8c98210400182153801821630019511480132008217d0008218d8007b17e0007b18e8009517e0007b1720013307b8077b1728013307b032017b17f0004921f80002492110019519200182188287208288287b19000149210801019519f0003300be072881c99511807b10787b15707b1668648564763307800033081f6469015107137d9a9599019577ffada8f5510af228293309800001c876087c8851080b957701ab97f52805647901641764685020c207f8cc821751073e7b164911080864177b17583307aa077b17603307d032017b1728491130029517587b1738825720825828491140014911489519283300c40728f2c88217088218107b17187b18209517187b17583307b8077b17603307b032017b1728491130024911489519588257208258287b1938491140019519285020c407b4c88210788215708216689511800032009511807b10787b15707b1668648564763307900033081f6469015107137d9a9599019577ffada8f5510af228293309900001c876087c8851080b957701ab97f52805647901641764685020c80719cc821751073e7b164911080864177b17583307aa077b17603307d032017b1728491130029517587b1738825720825828491140014911489519283300ca072813c88217088218107b17187b18209517187b17583307b8077b17603307b032017b1728491130024911489519588257208258287b1938491140019519285020ca07d5c78210788215708216689511800032003307011409e0ffffff1f000000ae8907330732009511d87b10207b15187b161095861f3305080003955948278499fc013e04000003977420994420819ad44a047a943a0400000384aaff00520ae59557002082770006986605510734011407ffffffff00000000aa760d015648e0ffff3f090183620164675328c10f2a69278d7918c6920a84aa7f9099ffe092098e99977707c97a0ac8a9099597ff079577800401330b0020c8b509829a38078b7906d09a0c84cc01510c1d979c03955b0020c8cb0b82bb0806330cffcf7c07d2b7075207219599019b97ffd2a7075107d4006a7997970395580020c8870782770806016a748a9706d44707977702c8750b80bc80ca04836798aa01aea76b7b12089557002082730006bf3c008b0805c868028568ffbea808817744077b188e88c82808838a01aea72433076577c9370797a825988220ae270bc97207657751072b955700207a7a44070180ca0881cc8407e0c837077aba510aa300481a0c821408821028d000330701955800204628480782102082151882161095112832007b18085020cc078b011408ffffffff0000000064793307aa86d38218085209c1fe28c964675428c00f1f69278d7818c6820884887f977707c978089587ff0795778004018b7906d09a0884880151081e979803955b0020c8b80882880806330bffcf7b07d287075207fdfe9599019b97ffd2a7075207dffe2872ff979a0333020020c8250bc8ab0b82b808063303fedc430ad2a8087bb8080682140882105208169552002082283807dc9309d298087b283807019748019588019779209899207a9c7a9804957320976a05c8a30951008b00640754107f2a330700000002e67007697a8dab18c6b70784777f97aa07c9a7079577ff079577800495770101837bff8bb70684b23f97bb02c8b50c80cb7ac9970c01979a2098aa207aa47aac047aab08481a0c510b077ab90c01977a03955c0020c8ca0a82ac08069b2801d4c8087ba80806955b002082b838079b7701d487077bb738076404019557002081784007817a0006970705be9707978805bea808ae87149777209877207a749737209877202868fe973720987720285ffe33087127033307657795881f8488e0ae8714c9780765793307510962fe520809285bfe51084333076577c9870756171f0d9a79206597510745fe3303080003953b002081ba4007482b4407013307000000027bb80006aca71c510a12552a80001383a4ff282833072813fe3304281f64a70169798d9a18c6a70784777f979907c997079577ff0795748004018b4b0697b70395390020c87909829c0806330701cf470ad4ca0a7b9a08069532002082293807812a4007cfb70bd4b9097b29380797aa0148087a8a0448180848180c974402c843037a3828a5fd3303080003953948278499fc330a01013e0400000397a420994420819bd44b047a943a0400000384bbff00520be5510794029511d87b10207b15187b16109570e09709209537002082750006989920809a04819bbf500c98a901541c1fd60097ba05bfa00c97ca2098aa2080a4048446015206bf009848013306000000027b185644000000041651042a6486552400010a958bff282069688d8518c6560684667f978807c986069568ff07958b80042805330b0181a87b180897b52098551ec83505815280a608abc20d7a5651061648160c284780aa0c7aa60851063f7a6a0c28398bb606976a0395380020c8a808828a080684bc3f3305fedcc50cd2ca0a7b8a0806520a169532002082283807dc650ad2a8087b2838070195380020828500068218be8909974404bf4000821b0801817a400797aa05be5a02979c05be0c0cae2cd10097ca2098aa2080a6048467015207c1007b1b08986b013305000000025646000000041651062a64b5552600010a95b4ff282069578d7818c6850884887f977707c978089587ff0795748004280533040197472098771ec87305815780a608abc7137a5651062248160cbeb909821b08285e80aa0c7aa60851064f7a6a0cbeb909821b08284a8b4a0697a703330c0020c8c308c887078278080684453f3306fedc5605d258087b78080652081d953c002082c73807dca608d287077bc73807beb909821b082809beb909821b080151098900649a54197f2a330700000002e6790769788d8a18c6a70784777f978807c987079577ff07957a800495aa010183a8ff8b8a06848c3f978802c8380880867a809798019707209877207a7b7a78047a760848170c5106077a600c0197a70395380020c88707827808069bcc01d4c8087b78080695360020826738079ba801d487077b67380701979705be0707ae270c9777209877207a790182102082151882161095112895370020462748073200953700204627480732009511e87b10107b15087b16531910698d7a84a207c82706ae671a642c647a648b017cb578a595aa0195ccff95bb01520cf3c82804c929028423f8844807c8360a51083eaea64e9748038480388445f8825b8d88848c38955808018285d00b0bcfc509d4b9097b69956608958808645baca6eb281f647ac8970bacb7242831aea61364480182897b69956608958808aca6f6c83408842907c89a0baeba137c8b78ab95aa019599ff9588015209f3821010821508821695111832009511d87b10207b15187b1610c9870aae9a8500c8970c330a10c89804aca91b0184c20784c0f89a23aec01ac898059555ff017c5695cbff78c6ff9555ff64bcacb0f3c83404c929097b19088499f8844b079a9a7b1ac9900c510bb100ae0ccc00974903849338844bf882b58d9984993895bbf864060182bacf9505d03a02d452059568f87b65f895bbf8648664a5ac8ce82897005319106b8d7a84a207c82705ae571a642c647a648b017cb678a695aa0195ccff95bb01520cf3c82804c929028423f8844807c8350a51088a00aea599009748038480388446f8826b8d88848c38956808018286d00b0bcfc609d4b9097b59955508958808646baca5eb2869647ac8970bacb76e287bae0c1f821908c898089588f8640901828a959bf87b9af89588f864b9acbcf38218c8800cc8840482190884990701c99c08aec848954bff017cb995caff78c9ff95bbff64acaca8f32831aea51364480182897b59955508958808aca5f6c83408842907c89a0baeba137c8b78ab95aa019599ff9588015209f38210208215188216109511283200531910558d7a84a207c8270baeb713642c647a0178a895ccff95aa01520cf8c92909849af8c8ba0aaeab22978238140c0001010101000000d6c202972c20d42c0c017bbc95bb08acabfb849907c89a0bacba0d2815647ac8970baeb70e78a89599ff95aa015209f832005109167c7a7c8babba139599ff9588019577015209f033073200c9ba07320021449248924a9224292529499224294992a42493a49288881455921409295145442480520009204a22499294449224495242924892a4942488228a484494049024521111a98888448988a822228088882401948a884800250144a988885444442a22228924499224499224499212404914228088882401942844440450921021222a218a484492882422454422225223122122222212a3488a440489882412914244a2a4445222224a444489880812058982880822228888880891444424228088484544240a1140444492004a1422808888240194444992944452224989101285082022224900250a1140444492004a00252925414a9224499294004a0154194a0025494450442208a002684444894422224924912289482252231221222222899822492291481249249244128924494a2422892442125191442229914422914422112989884842124944121111112426495212045022491149a228120144120194244992244992244992882492004a92440489085188108588081122491240024825494a045049929494a40a2a93a48888240920a5004a929442842892a44c22124992489148225223122122229229498914944492244948282929499224499224491249922449922449924492244992244992249124499224499224924422422489888244005186244902484992441011514488428448124045449494942449922449922491244992244992244924499224499224491249922449922449922422892492242521128944228944222989449248241289444a22499224499224499104d088884a924494880aa21001248054248944929224499244241289249124124992482291481289442249124992244992244992884844442448228288288988489248249224490248005152529224499224499224499224499224499224499224499224499224499224499224499204504992244992244992949424154050992425491240a2a4a424499224499224499224499224499224499224499224499224499224499224499224499224494a49244d92922449929424499224499224499224494952414a9224490009204a4a4a92244992244992244992244992244992244992244992244992244992244992244992002a49922449922449929292a402082a93a48888049000a29224298924499224499254122529499224495242008d88288a9424499224499224499224494a9224499224499224aaa04422912449000920952429114025495252922aa84c92225292144a9224249594224b92a42409a254644992942441942904224a34a54a42529225291051880811494a4921214988a4409224499224244a121292a422494a490a4991244a044a8152924492244942424999242424492a45920aa048524848924881a4442224914492905022490a0949122990942491244992905052260989324948489292a4922409094992449324059224932409092d49aa841492924a924a922424244992449324059224932409092d494a925292425224891288122825492449922424949449424292a4522524422222254944145992242525214a459624494949883285404421224444a4248988224b92a4a42444a9c8922429290951a6108828448488520124494849921452084912299094249124499290505226090949529294922424244992449324059224932409092d494a924a9224242449124d921448924c922424b42429494a9224242449a249920249924992848496242549459a92a49892d40a94a24442224922894892241212499248204994a49414129284480a244992244942a2242121492a495229252121494a024812492029492249922421a1a44c1212922425492549121292248926490a2449264912125a9294422449294921214988a44088a4409224499224244a1212922445244994a0482489249224a42425494a912549929292204a459624494a4a82285308449410892449529246535225492a69490a4414224244449224228a2c4992929210a5224b92a4a42444994220a210112222922411516449929494842815599224252521ca1402118588109124154012404a24a494240124929444521249810248a49424294952a09424294952a09224492449495292420a25891281a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a4144852929090242549892a41922495a4404a922425494152522029490a2124114248224952494a498194020994920aa4aa929249529294249524294912409224a929094a52929484089124952429499224494d49509292a424448824a99424899448002989243549822429492a928a22492a4952922480244952531294a42429091122492a91244949920092244952531294a42429091122492a499248892491a4a624284949521222449224952449924c4941494a920a9184482a92a4244942429224a29124052249264912125a9294242549121292248926490a2449264912125a92540049125292248554084912299094249124499290505226090949524444152801b488888a88109124801440525226914424922489a44892489244a46a44228888442453a42449494912492251498924084992244992244992242989442a229152942449922449922411112905922429492249492422aa1849924424112935229120229232224925812449929214894424aa484425484512491248128912499224492422495292a44029a2880452240288882825a1a21011210a1111224488124049114944940490a44412114992448a244a12911a91082222112999922491489248241249922449922489244992245212499244a22289241289444a22499224912449a4242292204912498a288a44001111a52c22a2880824224421429490425252492a2429a4902491024989444822892421a14492145248924881a42489244992848492324948944942429294a422490a292449a440522211924822494828912429a490249102494912499224090925659290289384842429292212492224920889244242482a909424a9a424a9400124809214400550004b5252529292a4004a4a52524a929424920a525292022593a414485281920aa41042121111298054529254a00450002b22a29444a22452228944922491229124223592081249924c494a228124499224218992929224499224499224912449922449922449244992244992244912499224499224499224120992a42401244ac9a4248904928428448888880248024809299524491222424444444488444224111222224424110891281022512044a240884481108902215281882412914492902849489224420a8908914420229148124942a2244924899048222489901092244922a49088104981104904221289249124244a92444821112192022192084424124922494894241152484488240211894492481212454412492224920849222442485620494292241152484488240211894492481212254922a49088104981104904221289249124244a92481252484488a440882481104904221289249124244a924448211121920221920844241249224948942411524844882402118944924812122549841412112289404422912492844449122185884424028912891011492409092592241152484488240211894492481212254922a49088104981104904221289249124244a9208292422441281884422492409899224420a8908914420229148124942a224892421854484480a84481208914420229148124942a22489904244221281448944888824928484124992082924224412818844224924098992848c882849489424244a12122509899284444942a224215192902849489424244a1212c524215192902849489424244a1212250989629290284948484444495292420a25214a8124494a9224244a1212922445441225918424492422492042142521134992481291944022499290844c44a2242111894245442451124948924422920422449292908924492489484a204992242421139128494844a25091a42485149284480a244992244942a2242121494a52424a4942929424855408494224059244224924098992842429490aa9109284480a244992244942a22421494a12122509094952929412494292a490244948214988a440882481249148124942a22449482149882481249148124942a224495248214988a40884a40221920492244992242151929028494894242424494a924a92242593242545529224299924499292499248929249922429992489242999244992924992489292499224299924892429992449929249924892924992242999244992944c9248929249924892924992a444529292a49249122989a42449c924899224a90249264955054a8aa40a2429502a50924a202549259092241489842244925404229124a92449922449924412811229495244444444444444448408111122021111929a02212210014412111111288a24224a9208201240149188241149929288241211911a910822898888c48822492422222222222222129188442422118948442212918844242211894844a244249148222222229292244992244992249224499224499224892449922449922449224992244992241149924424254992244992244924499224499224491249922449922489489224922449922449922489484a922449922449924892244992244992249224499224491291244924499224499224491291884849922449922449124992244992244992449224499224499224912449922449922449442051449444441245242202111111111111119124898848449244449226220211111129222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222202111109209214c84822221245924822110821242222804444a24023114004108180282291541289a440454444444402455224222949119188241149929288244a44a4a81189209288882862144991884024228944a2888804118944442022224204914412119188482212490051144124252202914444442022224284880811414484a4680a44244991849428224992244992244992489224499224499224922449922449922489942449922449922411111111111111888888a826499224104948815455201189444424924444444444840811112122888890d4144820294a922424354952202229522289249144129188888888102122424410111111d124492402111148441252d144480924210542922a222292244992244992248948922449922449922422499224499224499288942449922449922411116992884812911411111111111111111122444488082222243505229288888888a4401211914431123111498124228954a0882222922449922449922489489224499224499224224992244992244992889424499224499224111111111111214244848820224252532022a2492222492449922449922449124992244992244992449224499224499224919224499224499224a249444444881011112282880849d148244992244992244912499224499224499244922449922449922491922449922449928824a249029184140849aa888888888824491189884912111111214244848820222224355220926a8448444488101121228888a86a4481444a444412910249442451448c488891924022111a29092412414b442022111a118940444444442205222212291011918802111189281091888888881021224244101121a92910810811128d2411910492420aa4aa222a9a908888888888102122424410111111111122444488082222226a922452202202898888888888088c081111111121222244041111929a0291888844442422222244448828222244441011a99a02111111d1484424892449922449922449229224499224499224894892244992244992242225499224499224914444443489888888880811222244041111929a0221118986904892244992244992242249922449922449928824499224499224492252922449922449124944442d21222222440849948888880891404444222222224284880811414484a4a640024911254992244912929a481289882822118988888888888848202925499224499224492481a4482211491422458994118908444424909492244992244992248914912489880822221211818888881021222244041111929a0211442249922449922449924892244992244992249224499224499224899424499224492449121111888888881021222244041111929a02111121154d242212114544229124294952a088888888a8454422229240524881545544442d1211898888a84622428a2612492092420aa4aa2222898824922411918888081122224244101121a92910119104922222229288d424498188442022222222a24644222249444448d124492423222222222244888810114444488aa68c8888881021224244101121299a029126222222222242848808114144a48aa640442225492a50141111191111111121424444880822222435052222222210111141444448d1244944444a69920291888888888888888810212242441011a9a2291011111145554481482092420a84245520222222aa15681211111122444484882022425234052222229288888888189224a411111111111111112244448808222224350522a2494844a2114222225192244944115104a2882862922449922411912889442449922449922449129124499224499224494492244992244992241129499224499224492222222291261289442081488a922449922409494d241211092495244952a06a522081480a29109254110522924891482219111111112142444488082222243505221118112222222242444488082222243505222222a24944246544242222111111112222441411112222888890144d8188482009118920222146420289288814888854228988484444445044249324054a06904a352222222222222222222222a244a2245112118188924889249240222212058a28228aa811111111111189a8a02262225224128944229144241191144822124914442452449240249288242211051189189128908822119324494984484144242a88884482888822624402498848041109312221814814458b282111491249111122222946229128222292444492942492129188d48848041149444444230a8948a248241291442412494492241191888888288988484442224992244992244992242222a51451922449922449922411111115052289482211114962228ac248229288484444a4d48888444410111111458c884815818848124544001191349448928888444444241111912449922449922449229148249292244992244992244922499224499248922449922449124992449248241249444492888820111111814844220a94122311884420898888444a921428221188c48824121111954444442225490a149108442205119148414424228088884853248024450a12894420229208242205a28888281191428a08098944918244221108212422851411129288900821241209204a248924a908449202954444448810112144121112218444121112218444121112218444429142222222224488880821121111214244841009212422111111224444089188889010929224492242228490482242228490489224222442088924222442088984842422244208892422244208892422244208892422244208892422244208892422242284908824222442088984081122442222224488881022111111224444089188880811222284484444448408111121442222224488881022111111224444089188888808112222428844444444881011114224222242848808211211112142448410898888102122428844444488101121442222224488881022111111112222441411112144242222224284888810228908891042228908891042228908891042228908891042228908891042224944444484081111214442840811224912111111214244440891242222224284888810222184442424112129881042222584908844841012098988881021224288444444881011214422222244888810221111111122444484108988888810212222844844444821214922422284904844444484081111214422222222448888082112111111214244440891888888081122224288844444549024111194445052441001248948928888880811222244041111929a82002211112142049000128948484828808420292291882849a218111188482222884892228488244992241289829224910821224244045092240920499224092049922401244992244988128904918888889824499294242222224992888824499288884892242222499224222292248988489224898888244922229224912422228888444444444488402222922449922449922489244992244992244922499224499224499248499224499224491251d542120549549088484204221141222222222249244a44128da2442491111111111122444448908488101124111121291a5190888408442452249148499282222248924492288d282251905441aa281a91484a249222498288840844245292243142924824a2308aaa8818914884880811414424a92988888888481488884824298988884022224284880811414484a4a62092884841129148a22092488448005124889124499224494a8c882445929448892222522411622492208944884992244992a4882411224992244992144922449224499224299244882449922449522411228920220a242222299188882492089182484444124922449224299244882449522489104992a42491240a124512459224499224298988928824495224914841944824828844111149222292481221929444445224922089884844a220a28888244944a2205124512449922449929288284924499224499222898814448944228848242222894822449224292289882449922449524412114992244992a448224412414422229212898848129184489224251211911211914412214622099248849824499224498a4812211111c9224984489224451221920489444424894822111149222292449424224992244992148988944844445212118988222211444444141125444422099208914882248248441089105110118914442452101191888288884444242389102391044924424c92244992242512119124118144442489884812918448922445122146449220918848894444248944222229918888648924028988484a2422524922120551a448244112119188444144912012298888448288448248248844824824884482482488448248248844824824884482442241229120914810892888448248248844824824482412442205119148109148108904914810892888448248a4202212092212092291209148108904894482482488448248248844824824884482482488440a2222518222120922912012092291201209221111119128a24022910892041111114592829024849048444488101121444284081122218444424a491284101209092124928844082191901042222184442222428488082112424824492a4892525054505450aa88888888884849822405d592a410424a1211514192444450445044508488200248441411415124a2482280244a24119124492245244a24223522118888444432a2248924914812914824924422914812894422491249922449922449924422111144229128128924892422224a2412494424128944121112492412894422898888884824128944229148242222228288882092082022222988884488002222228a888888828888041129824841414422222549924c108d4424881241223549144410450411051191208a88241151504444449011111111111121222222228844101121a929888808112222440411111111111141441051d424918288824422a888882224924492444884101289441042482422a49090442405114248242222448888102211112142448410092124124292aa148a444a2a919048124992242949252489244929292d499224493252521049a554ca2491544a4841420849529a826492242549a98292424a92904a529294524a0a4a4a92aa8252924449a9a04949922492922492445212492249245322a524419992a42a2825494992a494902429a5a4a42a482949aa0a4a49524992244932a5a0242549852421295130a5449284449248a5249192904812294d92921024012444b02449922491242949494a124924499294a44692549294a4495221494a9252929024954a5a92a49ac29224499224112949a52091922429499244915290241349428a24299324992409c992a45492204949485232892421294942a29224499224492a289542a524a992a42649495229894429a5882a93949448524a4d552246a444528d2451d29224919224a524492a49b22449020802480001242549922449125124491169499224499224499224499224292992244b9224499224495224911449529a24c92449489224a12425932449488a2492942425299924a59048914849294922499244a224495292644a92244492a42492944aca249148522a2993a42409892489922449495296240929094992945452929484a4268992a492102401244490499224491249929424a594124924499294a44892925451244993a482242922491291c89224292909518a2c4992929210650a414421122249529424490a299192242905902489b24892484a9224122989249242214922251492348a48114512912449514492a4244991a8a44a294b92254911112549a24a9248484252a1924492902492848424911292922421152a4964922449529224499284a442421292242449482829992822a224a904010490a42449212549a5644a9224942491441249922869499224694a9224354992a44c499200922449a22449a624258914959a5492922449a1244aa932499424a9242125144a922425a524a924494a1252494a522249242492444a4a892409892491925222219124524644a4444992948484244948894a48494a9290501249932449929224499224248524090925259322228a8824894a922449480a2525512125491292144a4964922449529224499284a4902421a1a4642222514444914822914822914824128944920a8948254192244892044992202249922449489424242422a2248984942249242211521252922429499292242949929224254992484a449284494a2461a424692224a42449492a49922449522549242991248922a2284ac492442649211185101249484992925492244992a44a924852224996244912851012ab8a2449294924551402211042082111821084104204a224440929a38448128948460a49429292244914118948442422242549442422121192492429154952322549122a49a62499a4a4a848528a240949129224495052320949522649442422121189884484244992a4a442498904094985a44249921492495292aa2049424292945252529244912949295592922525459224496a92a450a99412491212492224214952490892001222c8922449924492a4242529492491244952921a4952495292264905495244912549525212a2a4104414222914899424a924252549024892243525819294242521214952494a4a9224495253122849495212129224a59492829224a92a949224a9244992644aa1242549852421499224a904a9828254d024494a924a25a490945492949092a4aa6a9254d11249415441092955a510404a0929552999aa11024825492a99a41a2180545232490a098944425242229190844422218948aaa02449008d242a2a55349124894892a4d424492091a46422499520495292140114552c498a0429249424214992442525228924499224499224499292524a49922449922449922425a5829294244992545a92149224954892488904902492d494044a5292948484244912959444122549922449922449929494524a92244992244992242929159454922449b2242924492a9124911209204924a9290994a4242909094992a452d52229882a28024865a94aca24292489248490a4249190481221499292542d4951105590a4aa9422802ac924298924849024a4249190481221898828419482d49024499224499224499224499224499224499224499224499224494992244992244992241224122412241224122412549224490a92244949922449898828490a4248924842084992484844122149c4942249a422a290548990842222444444248248aa111129094942a224892492844492484a4152480949952449922449524a525059428b88a824494a484911538a249192902449228994249124924849248924914446624a914429952449092929624a912452111111114400112121242424242484848484844892244922492289449224492291244992248988888824494a0a4aca44442212894824124912914812492249a22049522449922441089224499224092184888842088984908414125248482185145248212342088924494a92a48444540992949244c99424498a24294924492249922429494a924909124a922489244a1249922291242912494a8884949024494a888424294b92492292484946495291241555a54a2549524852424b925248454252918888144492440248c824498810899044249228491049922489480009a94992102112218948249124882449484289948800324912224422241189249204495219115144459624494912a2a410441422a512115112894812894812894812894844442222121191888844442422221111898848444412892412492492482491482291442289441289241249249248249148229144a22449499224499224912491249224924492449248924812499224499224499224499224499224499224499224494a9224498a82488d10111122442421228888282222222244882889109124a94853921453925a41294a242492249248922491904892448224519254a429498a2949ada014251212491249244992484824492241922801a49224213224a51089480410012994242949c448244922912449a2446249524a129124894a52499212312545521a91242508a092a850922462922412492249249144924889486292942451042949129149924a52926422914444128d882449529224a192a422492552848810441252a996248998442249444412294952422443520a91489224222909499244820229a4922411b524292929494a2291249188244694242349224922090229a49224113549494952922425914492442292484a4611a424494426492a49499289441211494c923449228a284951926a254992244952c99294944a4a2b4992549224a524492489a4244b52922495882495b424492a8992542b49922411492a599292524949d292949264922529a59524492a4992a428492d49922480244b925452925249924a", "03000000000000000000000000000000000000000000000000000000000000": "01b5af8edad70d962097eefa2cef92c8284cf0a7578b70a6b7554cf53ae6d5122200000000000000000000000000000000000000000000000000000000000000000000", - "02000000000000000000000000000000000000000000000000000000000000": "675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0675cff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4698135a0165be0", + "0e000000000000000000000000000000000000000000000000000000000000": "000000000000000000000000", + "02000000000000000000000000000000000000000000000000000000000000": "a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8", + "09000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "0d000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "00fe00ff00ff00ff4e4f63481b4b2b4726611dee56d0d8d1a3e163808b3ef6": "4e00136a616d2d6e756c6c2d617574686f72697a657206302e312e32320a4170616368652d322e30012550617269747920546563686e6f6c6f67696573203c61646d696e407061726974792e696f3ee82b0044000000000020002f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a777269746520726571756972657320746861742074686520706f696e74657220617267756d656e7420697320616c69676e656420616e64206e6f6e2d6e756c6c756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77617373657274696f6e206661696c65643a206e65775f6c656e203c3d2073656c662e636170616369747928292f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7665632f6d6f642e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d415860756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274735f6d75742072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d4158602f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6d706163742e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207573697a653a3a756e636865636b65645f6d756c2063616e6e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a204c61796f75743a3a66726f6d5f73697a655f616c69676e5f756e636865636b6564207265717569726573207468617420616c69676e206973206120706f776572206f66203220616e642074686520726f756e6465642d757020616c6c6f636174696f6e2073697a6520646f6573206e6f7420657863656564206973697a653a3a4d4158606e65775f6c61796f75742e73697a65282960206d7573742062652067726561746572207468616e206f7220657175616c20746f20606f6c645f6c61796f75742e73697a652829602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e727363616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c75652f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f6d656d2e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a636f70795f6e6f6e6f7665726c617070696e67207265717569726573207468617420626f746820706f696e74657220617267756d656e74732061726520616c69676e656420616e64206e6f6e2d6e756c6c20616e642074686520737065636966696564206d656d6f72792072616e67657320646f206e6f74206f7665726c6170756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d4158602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f736c6963652e72734572726f724e756c6c20417574686f72697a65722c205b5d2c20206761732c2020706172616d2c2020746f6b656e417574686f72697a6174696f6e206661696c6564417574683d3c3e73657276696365732f6e756c6c2d617574686f72697a65722f7372632f6c69622e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a636f70795f6e6f6e6f7665726c617070696e67207265717569726573207468617420626f746820706f696e74657220617267756d656e74732061726520616c69676e656420616e64206e6f6e2d6e756c6c20616e642074686520737065636966696564206d656d6f72792072616e67657320646f206e6f74206f7665726c6170756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a204c61796f75743a3a66726f6d5f73697a655f616c69676e5f756e636865636b6564207265717569726573207468617420616c69676e206973206120706f776572206f66203220616e642074686520726f756e6465642d757020616c6c6f636174696f6e2073697a6520646f6573206e6f7420657863656564206973697a653a3a4d41584c61796f75744572726f722f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f706f6c6b61766d2d6465726976652d302e32342e302f7372632f6c69622e727363616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c75656372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f6d656d2e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d4158606372617465732f6a616d2d74797065732f7372632f73696d706c652e72732230782e2e2e2b2a30756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f772e2e2e0001000000000000000000000000000000756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a636f70795f6e6f6e6f7665726c617070696e67207265717569726573207468617420626f746820706f696e74657220617267756d656e74732061726520616c69676e656420616e64206e6f6e2d6e756c6c20616e642074686520737065636966696564206d656d6f72792072616e67657320646f206e6f74206f7665726c6170756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77efbfbd2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f737472696e672e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f774572726f722f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f697465722f7472616974732f616363756d2e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72736d656d6f727920616c6c6f636174696f6e206f6620206279746573206661696c65642f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f737472696e672e72736120666f726d617474696e6720747261697420696d706c656d656e746174696f6e2072657475726e656420616e206572726f72207768656e2074686520756e6465726c79696e672073747265616d20646964206e6f742f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f666d742e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207573697a653a3a756e636865636b65645f6d756c2063616e6e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a204c61796f75743a3a66726f6d5f73697a655f616c69676e5f756e636865636b6564207265717569726573207468617420616c69676e206973206120706f776572206f66203220616e642074686520726f756e6465642d757020616c6c6f636174696f6e2073697a6520646f6573206e6f7420657863656564206973697a653a3a4d41586361706163697479206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e7273617373657274696f6e206661696c65643a206164646974696f6e616c203e2030606e65775f6c61796f75742e73697a65282960206d7573742062652067726561746572207468616e206f7220657175616c20746f20606f6c645f6c61796f75742e73697a652829602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a636f70795f6e6f6e6f7665726c617070696e67207265717569726573207468617420626f746820706f696e74657220617267756d656e74732061726520616c69676e656420616e64206e6f6e2d6e756c6c20616e642074686520737065636966696564206d656d6f72792072616e67657320646f206e6f74206f7665726c6170756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6e756d2e727330783030303130323033303430353036303730383039313031313132313331343135313631373138313932303231323232333234323532363237323832393330333133323333333433353336333733383339343034313432343334343435343634373438343935303531353235333534353535363537353835393630363136323633363436353636363736383639373037313732373337343735373637373738373938303831383238333834383538363837383838393930393139323933393439353936393739383939756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6d6f642e7273617373657274696f6e206661696c65643a206172672e706f736974696f6e203c20617267732e6c656e2829617373657274696f6e206661696c65643a2069203c20617267732e6c656e28292f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f697465722e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a2068696e743a3a756e726561636861626c655f756e636865636b6564206d757374206e657665722062652072656163686564756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a7375625f707472207265717569726573206073656c66203e3d206f726967696e60756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20696e76616c69642076616c756520666f7220606368617260756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a6765745f756e636865636b656420726571756972657320746861742074686520696e6465782069732077697468696e2074686520736c696365756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207374723a3a6765745f756e636865636b65642072657175697265732074686174207468652072616e67652069732077697468696e2074686520737472696e6720736c696365756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a636f70795f6e6f6e6f7665726c617070696e67207265717569726573207468617420626f746820706f696e74657220617267756d656e74732061726520616c69676e656420616e64206e6f6e2d6e756c6c20616e642074686520737065636966696564206d656d6f72792072616e67657320646f206e6f74206f7665726c6170756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f6c6f7373792e72730001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030304040404040000000000000000000000756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d415860756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a73706c69745f61745f756e636865636b65642072657175697265732074686520696e64657820746f2062652077697468696e2074686520736c696365756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f70616e69636b696e672e7273000000617373657274696f6e20606c6566742020726967687460206661696c65643a200a20206c6566743a200a2072696768743a2020726967687460206661696c65640a20206c6566743a203d3d2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f636f756e742e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f76616c69646174696f6e732e727301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030304040404040000000000000000000000756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f697465722f7472616974732f616363756d2e7273756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d41586063616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c75653a2072616e676520737461727420696e64657820206f7574206f662072616e676520666f7220736c696365206f66206c656e67746820617474656d707420746f206164642077697468206f766572666c6f77617474656d707420746f2073756274726163742077697468206f766572666c6f77617474656d707420746f206d756c7469706c792077697468206f766572666c6f77617474656d707420746f206e65676174652077697468206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a207074723a3a61646420726571756972657320746861742074686520616464726573732063616c63756c6174696f6e20646f6573206e6f74206f766572666c6f77756e7361666520707265636f6e646974696f6e2873292076696f6c617465643a20736c6963653a3a66726f6d5f7261775f70617274732072657175697265732074686520706f696e74657220746f20626520616c69676e656420616e64206e6f6e2d6e756c6c2c20616e642074686520746f74616c2073697a65206f662074686520736c696365206e6f7420746f2065786365656420606973697a653a3a4d415860000000000000000100000000005e000000000000008e0400001800000051010100000000007900000000000000600700000900000000000100000000005e000000000000006c0400000f00000000000100000000005e000000000000009e04000010000000730301000000000060000000000000002d0100003d000000730301000000000060000000000000003101000005000000bc04010000000000480000000000000004050100000000007700000000000000d1000000090000007b05010000000000790000000000000004030000090000007b050100000000007900000000000000900200001c000000000000000000000000000000000000000100000000000000bc000000000000001f0601000000000054000000000000002a00000009000000000000000000000008000000000000000800000000000000ae00000000000000bb0701000000000077000000000000009f0000001900000037080100000000001200000000000000490801000000000003000000000000004c080100000000000600000000000000520801000000000008000000000000005a08010000000000060000000000000060080100000000001400000000000000740801000000000006000000000000007a0801000000000001000000000000007b080100000000002300000000000000140000000d000000540a010000000000620000000000000052000000170000000000000000000000000000000000000001000000000000004c01000000000000e10a01000000000020000000000000001700000059000000220c0100000000000100000000000000220c0100000000000100000000000000230c0100000000000200000000000000250c0100000000000300000000000000230c0100000000000200000000000000040c0100000000001e00000000000000e500000001000000280c0100000000000100000000000000290c01000000000002000000000000007601000000000000180000000000000008000000000000008c0100000000000090010000000000007401000000000000aa0d01000000000078000000000000008102000011000000aa0d0100000000007800000000000000ea01000017000000aa0d01000000000078000000000000006704000012000000aa0d01000000000078000000000000008d0500001b000000880e01000000000082000000000000009500000001000000960100000000000018000000000000000800000000000000a401000000000000a8010000000000007401000000000000810f0100000000001500000000000000960f0100000000000d000000000000000a0f0100000000007700000000000000b60100000d0000000a0f0100000000007700000000000000b40100000d000000a30f0100000000007800000000000000ea010000170000000000000000000000000000000000000001000000000000009a01000000000000711001000000000075000000000000007e0200000e000000a30f01000000000078000000000000008d0500001b000000cf110100000000001100000000000000e01101000000000079000000000000002b02000011000000e01101000000000079000000000000008302000009000000e0110100000000007900000000000000900200001c000000e0110100000000007900000000000000040300000900000079120100000000004800000000000000c1120100000000007700000000000000d100000009000000000000000000000008000000000000000800000000000000bc010000000000003f1401000000000078000000000000005b00000017000000e2150100000000007800000000000000d104000005000000e2150100000000007800000000000000e00400000d000000e2150100000000007800000000000000490500000d000000e21501000000000078000000000000004d0500000d000000e2150100000000007800000000000000e105000030000000a5160100000000007900000000000000c0000000110000001f1a0100000000007a00000000000000e3000000190000001f1a0100000000007a00000000000000ed000000190000001f1a0100000000007a00000000000000f1000000190000001f1a0100000000007a00000000000000fa000000190000001f1a0100000000007a00000000000000fe000000190000001f1a0100000000007a000000000000000201000019000000c31d0100000000007a00000000000000e300000005000000401e0100000000001000000000000000501e0100000000001000000000000000601e0100000000000900000000000000691e0100000000000900000000000000401e0100000000001000000000000000721e0100000000001700000000000000691e01000000000009000000000000008b1e0100000000007a0000000000000048000000150000008b1e0100000000007a000000000000005b000000090000008b1e0100000000007a00000000000000670000000d0000008b1e0100000000007a0000000000000065000000110000008b1e0100000000007a000000000000005500000011000000051f0100000000008000000000000000b100000018000000e62001000000000082000000000000009500000001000000010000000000000000000000000000003522010000000000020000000000000037220100000000001200000000000000492201000000000022000000000000006b220100000000001c0000000000000087220100000000002100000000000000a8220100000000002100000000000000c9220100000000001f0000000000000006000000000000000400000000000000500000000000000080700000000000001000000000000000000c0000000c0000800000000000000008000000000000000000c000315d0000810102c008516500940004013501e70118024d02a2027c03ac0379048404ad04db040a0522053e055b05c7059706c806fd0652079907270857080a0929098709ad09d909040a700abf0ae10a3c0b670b850bb00bce0bfd0b480c5e0cb80cf20c110db70dd70d3e0e8e0ea30e090f300f870fb00fd70fea0f131045105f108710ca10fb101b114d116e11a311c411ed11ff113612fe1214138d13a913c513e11348149714c6146215bd15eb157516fb168117d817ee171f1833184e1864188b18cc18d91834195d1984199b19ef190c1a1e1a461a781aba1a021b1a1b231b461b5c1bb61bcc1b1b1c3f1c671c7d1cae1cde1c2b1d6e1db11df41d2a1e561e891ebd1ece1edf1ef01e061f151f3f1f651f8a1fc51f60208120a820cf20372164218621aa21e4211b22362248226f229422c222d4220223182346235c238a23aa23d823e22302243024442472248624bc24ee25fb2509265026b126e626f3268c2745289428d3281d29782988298d29402ae92a072b8e2b102c3e2c4a2c692cb52cd32ceb2c352d9d2dbb2dd02d272e712ed52ee02ee92e112fae2fc92fe82f343018312f3151318931d231dd31e6310e32ab32e23208337433de330a34a634bc343c357535cd35f6357236d8363e3753370238a83826393a398c39983aee3a3f3b613bb73be43bfa3b243c3d3cb83c523d5f3dd83d503e6a3e973e2f40394045406f414542f545b149280e209511807b10787b15707b16687b1818828808ae98133308ff140a000000000000008028bc00491140951a4849114801491150510991007b17330533072024017b1710017b192833070040e67906951740330a01330b0164586469821c105010029d13821850c86805ac85e7008217407b1708ac57e5007b16207b15505705c700821648c98509c868088217185010044b2b52075e821928821720c979095209a695184882870882887b1738140900000000000000807b18308217821a08aa9a58ff281082a80882a9330a7b18387b1930018218388219307b78107b7908017b7a82107882157082166895118000320064658216085106243308016467501006ab4e5107664911600195185882177b868219605209142800ff95186082177b868219605109f3fe8218585108ecfe501008722582173308ff140a0000000000000080289d3307cd02013308a60028c9473307f0230128744e3307250101330908240133082c288f4733071804013308a40028a547951120ff7b10d8007b15d0007b16c800491150951a58330b047b1b58491160510986017b18207b1733072024017b170833073824017b171828198216288466015206a201821910821730c97909510967018218607b19103307c701e67905951750330a04330b246459821c0850100a1b12330c017b1530330701017b17287b1c384921c0004921b8004921b0004921a8009518a800330920821620646750100cc7295207988217c0008218b8008219b000821aa8007b17a0007b1898007b1990007b1a88004821a8009518a800330904646750100e9229520763ff821788007b176882179000821898008219a000821660821a508112a8007b17707b18787b198000aba62e9517508218187b12283300109511f07b10087b15827b6485330904330a2464b833009a0028871282122801330824d6860752072d0196672457072601821858c88707d8870852081901847803821c3852081a018118840081198000811a7c811b787a781c7a79187a7a147a7b10811874811970811a6c811b687a780c7a79087a7a047a7b7a72209566017b1660821730d87c079555ff95cc015205ddfe28a2fe82a80882a9330a7b18487b194028229517588278821a508279087b1840140800000000000000807b19488217aa8a738218488219407b78107b7908286f82175082155851073514081dc7711cc7711c07ae8784009676243308046467501012334c51077e4921a80004951788007b768219a800520912281b9517a80049078219a80051090f82188800510808501014fb228217140a0000000000000080280d140a0000000000000080017b7a8210d8008215d0008216c8009511e00032003307c4000133086128374533075e0001330866282c453307d3030133084528214533071804013308a400281545951180fe7b1078017b1570017b16680164957b18304911509519584911580849116051054a017b1733072024017b1708140700000000000000807b172833073824017b1720281082171801821510c9750551052a0182186033078000e67506951750330a08330b80006469821c08501016890f9517688218305010188e14821768821828aa87ac007b151033057b16189566ff7b1638019517e8009518683309800050101aa24b821660821750ab762895175082182033001c9511f07b10087b15827b6485330908330a800064b833009400284e109867395207160197670757070f01821858c88707d8870852080201847807520806019518e8003309800050101e454b9566017b1660821738aa573aff951768821830501020f013821768955501821828ab8772ff821718ae751fff951750501022b50582165082155851066b9867395207c0009766073308086467501024544a5107ba004921e800089517687b768219e80052094f28578298088299330a7b18487b194028229517588278821a508279087b1840140800000000000000807b19488217aa8a378218488219407b78107b790828339517e8007b768219e80051090e821868510808501026e0208217140a0000000000000080280d140a0000000000000080017b7a8210780182157001821668019511800132003307c40001330861281c4333075e00013308662811433307d3030133084528064333071804013308a40028fa42951118ff7b10e0007b15d8007b16d000649a6486491158951960491160024911687b17510aa00133072024017b170833073824017b17187b1620281c951960821a1082172884770152079b01821730c97a0a510a54018218687b1a103307c701e67a05951758330a02330b246459821c085010286b0d3307017b1530330801017b18287b15387b17404921c8004921c0004921b8004921b0009518b000330920646750102a17255207958217c8008218c0008219b800821ab0007b17a8007b18a0007b1998007b1a90004721b0009518b000330902646750102ce224520760ff821790007b1770821798008218a0008219a800821668821a587e1bb0007b17787b1880007b198800aba62c95175882181864b533002e9511f07b10087b15827b6485330902330a2464b83300960028d70d645b01330824d6860752071d0196672457071601821860c88707d887085208090184780182154052080a0198b80f97b9319899317979787802957703951870330920501030b6489566017b1668821730d875088219389599ff95570182162064955209eefe951960821a10821730c97a0a520ab4fe82988217588299087b1848140800000000000000807b1950ab87132875829808829933077b18507b194801821850821948821a7ba81064a87ba908286282175882156051073414081dc7711cc7711c07ae8777967624330802646750103288475107724921b00002951790007b768219b000520912281b9517b00049078219b00051090f82189000510808501034501e140700000000000000808218017b878210e0008215d8008216d0009511e80032003307c4000133086128984033075e0001330866288d403307d3030133084528824033071804013308a400287640951148ff7b10b0007b15a8007b16a00064957b182849114895195049115001491158510554017b1733072024017b170833073824017b1720281082171801821510c975055105410182185833070002e67506951748330a01330b206469821c08501036f80a4921980049219000492188004921800095188000330920821728501038b6225207be007b1510646733067b17189577ff7b173001821798008218900082198800821a8000821558821b487b17787b18707b19687b1a60abb52795174882182033003a9511f07b10087b15827b6485330901330a2064b83300980028a20b98573b5207130197570557070c01821850c88707d887085208ff0095186033092050103ca2469555017b1558821730aa6729ff492198004921900049218800492180009518800033092082172850103e0622956601510762ff821718ae7600ff82164882155051066b98673b5207b5009766053308016467501040ab455107af0049218000019517607b768219800052094f28578298088299330a7b18407b193828229517508278821a488279087b1838140800000000000000807b19408217aa8a378218408219387b78107b79082833951780007b768219800051090e821860510808501042371c8217140a0000000000000080280d140a0000000000000080017b7a8210b0008215a8008216a0009511b80032003307c4000133086128733e3307d3030133084528683e33071804013308a400285c3e8377510711140700000000000000807b572811978820988920645764685010469af6821010821508821695111832009511c07b10387b15307b16288278105108030182770895752014071dc7711cc7711c077b170828108218109588ff955580005108e0007b18108256e08257e85106247b17330801646750104873445107f500491120019518187b8682192052091028189518207b8682192051090e82181851080850104a401b8259f8825751092e7b17821808ae89a600969624330802646750104c2a445107a000491120029518187b868219205209102818951820490882192051090e82181851080850104ef71a82591082571851092f7b17821808ae897f9696243308046467501050e1435107404911200495181882177b868219205209122834ff9518204908821920510929ff821818510822ff501052aa1a2819ff821038821530821628951140320033071804013308a40028013d3307d3030133084528f63c33071804013308a40028ea3c33071804013308a40028de3c3307d3030133084528d33c83775207dd0097882098892095172864685010564cfc82182814070000000000000080aa78bc007b181882193898973a5207f2008218307b18085109537b157b1910979505c85807ac87e6009555e0140700000000000000807b172064870151059b009576203309206468501058784383789555e064675708e882158217207b1720821618520611951728282b4911208216185106f598673b5207a000976605330801646750105abd4251079a0049112801951740017b7682192851090e82184051080850105c961949111033080114070000000000000080821920ab7939140700000000000000807b570182105882155082164895116032008217187b1720821582180814070000000000000080821920aa79cd7b597b58088217107b571028cc33072b02013308a20028a53b3307ca0101330861289a3b3307d30301330845288f3b33071804013308a40028833b9511d87b10207b15187b1610647546110895180833090133060150105e281e5207af007c18085108a2005228ff00234911089518083309086457501060081e52076a8218089887208e762884006c885708073306284184874033060151073984872033060251073084871033060351072784870833060451071e84870433060551071584870233060651070c8487013306075207547b1849110895180864576469501062a11d51070833060128228567079b77ff8219088218e07807976603cf6708c89808ac98199887208e7601646782102082151882161095112832003307682401285041330950240133070a220133082b286b3a8377520723978820988920951760646850106665f182176014050000000000000080ab5725140700000000000000808218507b87018210900182158801821680019511980132007b17408217687b17388217707b174895176064683300689511b0fd7b1048027b1540027b1638026486647564873300de002892fe821760ab5734140700000000000000808218507b878217405107f90233080133050150106a7f405207d30233071804013308a40028db397b17308217687b17108217787b1728821780007b17088215b0008217b8007b17821750017b1718821758017b1720821760017b17584721780195187801330902646750106c581c510776140700000000000000808218507b8782173095187801510722645633080133050150106eff3f5107c3027b157801951870016465821730017b878219780151090f82187001510808501070d0168217285107516456330801330501501072c53f510795027b15780195187001646582172828357f157801821650956718951860330910015010743c408217407b678217387b67088217487b671079652801289bfe95187801017b878219780151090f82187001510808501076621651052898573b520740029755053308013306016457501078503f510737027b16780195177001280795177801017b758219780151090f8218700151080850107a201682175851071c0182172095752014071dc7711cc7711c077b175028138217589577ff955580007b17585107f6008256e08257e85106287b1748330801646750107ce53e510792014921780101951870017b8682197801520912281b951878017b868219780151090f8218700151080850107eac158259f882575109337b1748821850ae893c01969624330802646750208000953e510735014921780102951870017b8682197801520912281c9518780149088219780151091082187001510809502082005b158259108257185109347b1748821850ae890c01969624330804646750208400423e5107cb004921780104951870017b8682197801520914281fff95187801490882197801510912ff8218700151080aff5020860005152800ff8215185105299857395207f300975507330808330608645750208800ec3d5107e9007b16780195177001280795177801017b75821978015109108218700151080950208a00bb1482174051072a33080133050150208c00b13d5107ba007b157801951870018217407b878219780152091428acfc951878017b878219780151099ffc82187001510897fc50208e007214288dfc33071804013308a40028d6363307d3030133084528cb3633071804013308a40028bf3633071804013308a40028b3363307d3030133084528a83633071804013308a400289c3633071804013308a4002890363307d3030133084528853633071804013308a4002879363307d30301330845286e3633071804013308a40028623633071804013308a4002856369511a07b10587b15507b164882ab08649c64856476510b2682a782a8107b1b087b1510ab5b4fac8c8100510816645964ca7b1c50209000cf132816510c182805510c1364c764587b1c502092002d13821c2805645701887801db77057b65087b6c107b688210588215508216489511603200491118330ba82401951808951910951a189511e864ac7b18087b19103309102501951808951a107b1b330b1025013300b00028d43533078024017b171849112001491138491128084911303308902401951718282b3514090100000000000080ab970d821008821595111032006459286d2114090100000000000080ab970d82100882159511103200645928512114090100000000000080ab970d82100882159511103200645928352114090100000000000080ab970d8210088215951110320064592819219511807b10787b15707b1668827564b6330bffdb650bc98b0bae9bc2007b1c5106cb007b15207b1728c889097b1930ac89bb00c86a079577ff9aa5d2750564577b1a1864a850209c00683b5107a600821930d695083307821a205208ca00ca950914080000000000000080821b18c9b80cac9c9700821528510a3b7b1910d6a60752078c008257087b1708ca6a06646764b864b550209e00193b51077e9517608218087b1850645b7b155882152882191028083306951758017b76951738951a5064b85020a00005fe8217385207318217407b57088217307b57018210788215708216689511800032003307821928282033071804013308a40028213482174082184882192811208219280c203307d3030133084528063433071804013308a40028fa33821928f01f9511887b10707b15687b1660330bffaab89c007b171882777b17205707e10064857b1a10c8a9079577ff9a96d2760664677b192864985020a2004e3a5107c8003307955801821b2097b901d8950adba908330904e4980ad6a608caa60552088f0014090000000000000080821828c98909ac597b7b1a08510b38821910d6b907520790008217188277087b17ca9b0664675020a400f3395107840095175882187b18488218287b1850280c3307283f3306951750017b76951730951a4864595020a600e0fc82173051070b821738821540281b8217388218187b87088217087b871407010000000000008001645882107082156882166095117832003307c0240128c33933071804013308a40028e5323307d3030133084528da3233071804013308a40028ce329511d87b10207b15187b1610827682750851062a98673b52075197660533080164675020a8003b3951074b4911080164177b7682190852091028219517087b76821908510917821851081282102082151882161095112828fe0f82102082151882161095112832003307d30301330845285b3233071804013308a400284f329511d87b10207b15187b1610827882750851083114071dc7711cc7711c07ae785196862433080264675020aa00b53851074b4911080264177b7682190852091028219517084907821908510917821851081282102082151882161095112828780f82102082151882161095112832003307d3030133084528d53133071804013308a40028c9319511d87b10207b15187b1610827882750851083114071dc7711cc7711c07ae785196862433080464675020ac002f3851074b4911080464177b7682190852091028219517084907821908510917821851081282102082151882161095112828f20e82102082151882161095112832003307d30301330845284f3133071804013308a4002843318089348277849a10520aa71e8499205209061f285a2a009511d07b10287b15207b1618828a08828918330b01aaba26520a265209237b17103307013308013306015020b2009137510786003305330801285951090b5020b400811828647b1710828782787b18088275083307013308015020b60062375107575705605105173308017b1864575020b800dd0d5107506476280633060101821808d86807c9860ac96809db7a09ac5942646764595020ba00c5378217107b757b76087b751001821028821520821618951130320033071804013308a400286a3049010133093025018217645828561c33077306013308a600284f3033090564873308320801285626827a140b00000000000000806489abba0e827a0882781064a728452682781057080982770828392633071907013308a200280e30951140ff7b10b8007b15b0007b16a80064866475482188009518880033090464675020c000ac1252077b811788007b17304921a0004921980049219000492188009518880033092064675020c20083125207528217a0008218980082199000821a88007b17507b18487b19407b1a3864675020c4001af483775207299788209889209517880064685020c6006ee682188800140700000000000000807b1828ab7822140700000000000000807b57018210b8008215b0008216a8009511c0003200821790007b1720821798007b1718492188009518880033090864675020c800f111520768821788007b1710492188009518880033090864675020ca00d41152074b821788007b170864675020cc0080f383775207377b18390728000301821983778398ac87269799209899209517880064685020ce00e1eb8217880014080000000000000080ab8732140700000000000000807b578216285106c40033080133050164675020d0003835510732017b15880095177028ac007b1882189800821990007b18687b19607b17589517880064683300d2009511e87b10107b15087b1664866475648733004428e1f2821788008218ab8719140700000000000000807b579517583300d80028f6fb8217980082189000821988007b1780007b18787b1970472188009518880033090264675020d400de10510747140700000000000000807b579517705020d6003efc9517585020d800affb821628520644ff95178800017b7682198800510987fe821870510880fe5020da00620b2876fe7f1688009557489518383309205020dc00f4348217588218607b57187b5820821768821870821978821a80007b57287b58307b59387b5a408217287b578217207b57088217187b57108217107b57688217087b57708217307a577879567c2815fe33071804013308a40028652d837752072797882098892095176864685020e0003de4821868140700000000000000807b1840ab7822140700000000000000807b57018210480282154002821638029511500232008217707b17388217787b173048116895186833090464675020e200c50f5207728117687b17284921800049117849117049116895186833092064675020e400a10f52074e82178000821878821970821a687b1730027b1828027b1920027b1a180264675020e60037f1837752072497882098892095176864685020e8008ce382186814070000000000000080ab7857140700000000000000807b578216400151062733080133050164675020ea000233510748037b1568951718027b76821968520912282bff9517687b76821968510920ff82181802510818ff5020ec00ca09280eff7b182082171802821820027b1748821728027b1850821830028219707b19187b17588217787b17107b1860492130024921280249212002492118029518180233092064675020ee00b50e52073c01821730028218280282192002821a18027b17b0017b18a8017b19a0017b1a9801492130024921280249212002492118029518180233092064675020f000720e5207f900821730028218280282192002821a18027b17d0017b18c8017b19c0017b1ab801492130024921280249212002492118029518180233092064675020f2002f0e5207b600821730028218280282192002821a18027b17f0017b18e8017b19e0017b1ad801492130024921280249212002492118029518180233092064675020f400ec0d520773821730028218280282192002821a18027b1710027b1808027b1900027b1af801482118029518180233090464675020f600b60d52073d811718027b17089517180264683300f8009511a07b10587b15507b16486486647564873300542845ef8218180214070000000000000080ab7836140700000000000000807b578216408217205107f6003308013305015020fa002731510779017b15689518180282172028dc00821728027b18821820027b1710017b18080195171801951898013309205020fc009331951738019518b8013309205020fe008231951758019518d801330920502000017131951778019518f80133092050200201603182177b17689517709518080133099000502004014a318217087a17000164675020060189ee83775207397b18083a072000030182190883778398ac872697992098992095171802646850200801cde48216180214070000000000000080ab7640140700000000000000807b5795176850200a01eef6821640821720520712ff951868017b87821968510925fd8218180251081dfd50200c011e072813fd82172802821820027b5700017b58f800821760821858821950821a487b57487b58407b59387b5a309557509518683309a00050200e018b308217407b578217387b57088217307b57108217207b57188217187b57208217107b57287b56f0008217287a57080128f9fb33071804013308a40028192933071804013308a400280d29951100fd7b10f8027b15f0027b16e80283755105d30464869777209877209788209888207b17307b18389517580195183033001001951168fe7b1090017b1588017b16800164867b17506487330064283bed8368330012019511e87b10107b1508978820988520330804645733004e01282c2f8217580114080000000000000080aa87700495173095185801330928019515580150201401a82f8217308218388219407b17187f1680027b18207b1928951848951758013309100150201601812f79168e020a7b17d80295178e023308fa017b17303307fe017b18389518d8027b18407b174895171833085a0133095a017b17507b18587b15607b196833074825017b17900249219802054921b0029517307b17a0024921a802049517c0029518900250201801bef6821bd002570bdc038215c80233070233083309645a0a64018216c00295173051061b330801646750201a014c2e5107fb034911300195179002017b768219305109108218900251080950201c012205821568015705990382172857079e03ab753d03821660018218206467645950201e01872e8377520726039517c0026468645933002001951150ff7b10a8007b15a0007b16980064967b17087b18187b192095172895181833007a0128c0249517c0027b17d8023307be007b17e0023307a825017b1730491138024911509517d8027b1740491148019517900295183050202201daf58215a00257051c03821698023307013308017b1710502024017a2d51071103510515330801645750202601f9037b171051072003821710d8760ac96708c97609dba809ac59f50264687b1508645950202801e12d8215c00214070000000000000080aa75458217c8025105267b17330801645750202a011c2d51074e03491130019518d8027b85821930520910281a9518307b858219305109108218d80251080950202c01e60382159002510524330801645750202e01dc2c5107a402491130019517c0027b75821930520910281a9517307b758219305109108218c00251080950203001a6038215580182166001510524330801645750203201982c51076c0249113001951790027b75821930520910281a9517307b75821930510910821890025108095020340162038215700182167801510524330801645750203601542c5107340249113001951790027b75821930520910281a9517307b7582193051091082189002510809502038011e038215a8018216b00151052e98573b52070402975505330801645750203a01062c5107fd0149113001951790027b75821930520910281a9517307b758219305109108218900251080950203c01d0029517480250203e0112e7821548028216500251052e9857395207c301975507330808645750204001ae2b5107bc0149113008951790027b75821930520910281a9517307b75821930510910821890025108095020420178028215188216205105243308016457502044016c2b5107860149113001951790027b75821930520910281a9517307b758219305109108218900251080950204601360282150851052c3308016457330501502048012a2b82161051074d017b1530951790028215087b75821930520913281d9517308216107b758219305109108218900251080950204a01ec01646764588210f8028215f0028216e80295110003320033079825017b173049113801491150491140084911483308c8250195173028c52333071907013308a200281c243307f40501330ad82401330bf8240133082b951930280b2a33071907013308a20028f82333071907013308a20028ec2333071907013308a20028e02333071907013308a20028d42333071804013308a40028c82333077306013308a60028bc2333071804013308a40028b02333093025013307016458289e0f33071804013308a40028972333071804013308a400288b2333071804013308a400287f233307d3030133084528742333071804013308a4002868233307d30301330845285d2333071804013308a40028512333071804013308a40028452333071804013308a40028392333071804013308a400282d2333090b64873308490a0128341951071c330804645750205001a72951072682101082150895111832003307b60a01330af82501330b18260133082b95190728f3283307a509013308a40028e0229511e87b10107b15087b16648664755020520160295107463307657814070000000000000080aa78a8299566ff9a87d26707c875056559510914c88707ae87113307440901330861289522330701821010821508821695111832003307a509013308a4002879229511f87b106487649850205401ff2851070a821095110832003307a509013308a4002854229511d87b10207b15187b161064a664957b17087b186487649850205601ca285107776467645850205801bd285107763307657814070000000000000080aa7805299555ff9a87d25707c867096599510938c88707ac876651082f8218e66809821808520806520949d8780cc9870ac9780bdbca0bac9b3a821020821518821610951128280029330782102082151882161095112832003307a509013308a40028b2213307a509013308a40028a62133079e08013308a600289a213307440901330861288f21951168ff7b1090007b1588007b16800082791057099501827608c8960bac6b9701330751091e9a9a95bcff01c87c0b7cbb520b159577ffab7af57b191064972810491110280b9a777b1710ac797f017b1708c97905c85607ac67670133071f645b646901510b467d9a95990195bbffada7f5541520547b1618491120089517187b1740330768017b174833075026017b1750491158024911709519408287208285287b1960330601284f7b189517186468645950205c01cb21821718510792008218561520b47b16187b15209517187b1740330768017b174833077026017b17503306019517407b17608287208285287b165849117001491168019519507b17645850205e01d20e52078c0082170851073b3307fe019518107b18187b172033079826017b1750491158024911709517187b17603306017b16689519508217645850206001930e52074d330628488217208218287b17307b18389517307b1740330764017b174833073026017b17504911580249117095194082188287208288287b19604911680195195050206201490e64760164678210900082158800821680009511980032003307620b013308a20028db1f3307010b0133086128d01f3307010b0133086128c51f9511d033088026013307b82b017b174911080149112049111008491118641728401f8279827a08648b649764a864b928b215827728970c951128ff7b10d0007b15c8007b16c00082750882797b181055250304ab00c85907ac970b025105eb018217108278207b18388277287b1730330766017b1728140720000000080000007b17203307900c017b1718017b19409517407b17788217287b1780004921880002492198004921a000024921a8008217207b17b0004621b800038217187b174849115001951888007b1868491170019518787b185849116001649695194882173882183050206a01260d520761019555ff9569015205982850017b15085629fffd71018217108278207b18288277287b172033060002330766017b1738140720000000080000007b17183307900c017b17307b196495017b15409517407b17788217387b1780004921880002492198004921a000024921a8008217187b17b0004621b800038217307b174849115001951888007b1868491170019518787b18584911600195194882172882182050206c017d0c5207b8009566ff95550152069a33088c0c0133090382171050206e012b1433080152079b008217088218c88707957500fe5625fffdb50033060002015105787b15409517407b17788217387b1780004921880002492198004921a000024921a800140720000000080000007b17b0004621b800038217307b174849115001951788007b1768821810828720828828491170019519787b19584911600195194850207001d80b3308015207149566ff95550152068b330828063308010164878210d0008215c8008216c0009511d800320033072b0c0133086128571d33072b0c01330861284c1d33072b0c0133086128411d9511d87b10207b15187b1610827a086496ac9a367b1a087b178275d88507c95809c9850adb790aac6a31648764586469502072014024821a08c96a07c8650582187b857b870801d86a0782102082151882161095112832003307a00c013308a60028dd1c648a3308b8260164a928190b9511d87b10207b15187b1610827682750851062133080164675020780147235107404911080164177b7682190852091028219517087b768219085109178218510812821020821518821610951128280afa821020821518821610951128320033072b11013308a40028661c8215285105398217408218305107377b181033070133080150207c01dd225107bc015706c40151063f3308017b18646750207e0156f952073328b0013306330501280c7b18487b1650ab68c3018218087b85087b8610140700000000000000807b87283601330701018219107b16287b173049113895172864585020800147083305a70d015515fd7001821838821728c98707541702170157082b01821930c88907d8970952091e01d87509c9570ac9750bdb9a0b541b024b01461702bd00461701bf004607ef8217188219209588037b18387b1788007b19900095175895188800502082010a198218585108980033071827017b171028159517589518880050208401ec1882185851087a82196082167095172850208601ac075106de821838821728c987075417023957084d821930c88907d89709520941d87509c9570ac9750bdb9a0b541b028800461702bd00461701bf004607ef9588037b1838289b951728330903330a01330b0150208801dd048218385908b93307de130133086128e31a821738821830821928821a087ba7107ba8087ba9018210a8008215a000821698009511b0003200951728330903330a01330b0150208a0193048218385908ddfe3307de130133086128981a33073813013308a600288c1a33072b11013308a40028801a490101330900270182176468286c063307de130133086128661a33073813013308a600285a1a491158330be82601951848951950951a582880069511f87b1050208e019c063307821095110832009511d87b10207b15187b1610552880003c8276108279ab961764757b18330830270150209201bb0382186457015706a800827a08c86a09d8a90a520a9b0078989566017b7610287f8b890b48110c520915951a0d9889068699c00078190c33090228528b8910520922951a0e98890c8699e00078190c97893498993a9599800078190d330903282d951a0f9889128699f00078190c97892e98993a9599800078190d97893498993a9599800078190e3309040184883f8688800078a895180c50209401ce05330782102082151882161095112832003307460d013308612852199511d87b10207b15187b16108276827508510621330801646750209801c81f5107404911080164177b7682190852091028219517087b768219085109178218510812821020821518821610951128288bf6821020821518821610951128320033072b11013308a40028e71833090564873308830e0128ee0e9511b834084400037b175208333307fe0164187b18387b174033079027017b1708491110024911289517387b1718491120013309b027019517082847183307fe0164187b18387b174033079027017b1708491110024911289517387b1718491120013308c82701951708280a189511b87b10407b15387b1630648682880898893c5209de009789045709d700826cc8c909d8c9095209cb007b170851082d330595c90801829a645bc8a505acb5ac009588ff9599105208ef82681851081056150f7782c80852087033050133070133080150209c01981e510774570590005105153308017b18645750209e0112f5520709287c330701017b15107b1718491120330860270195171064695020a0011506520769821720821818821910821a087ba7107ba8087ba98210408215388216309511483200895797550194753307013308015020a201271e52079233072b11013308a4002883173307482701282e1e3307220e013308612870174901013309e0270182176458285c0333071b1001330af82701330b18280133085695192f28501d9511f87b105020a6019f033307821095110832009511d87b10207b15187b1610552880003c8276108279ab961764757b1833083028015020aa01be0082186457015706a800827a08c86a09d8a90a520a9b0078989566017b7610287f8b890b48110c520915951a0d9889068699c00078190c33090228528b8910520922951a0e98890c8699e00078190c97893498993a9599800078190d330903282d951a0f9889128699f00078190c97892e98993a9599800078190d97893498993a9599800078190e3309040184883f8688800078a895180c5020ac01d102330782102082151882161095112832003307220e013308612855169511f07b10087b15827c6485330901330a01330b0164c85020ae014714090100000000000080ab970d8210088215951110320064592818029511f87b105020b0012114090100000000000080ab970a82109511083200330958280128f2019511887b10707b15687b166051090301c89805ae8516330701645882107082156882166095117832007b170882777b17285707ee0049112001330b013306ff3307017b17187b171064a85020b2013c1c5107d7003307821228972901821620886a0104330c0493ac01956aff330b08dbac0be45b0ae49a0a821818d6a809caa80552099814090000000000000080821810c98909ac59857b1a510238d62607520794008217088277087b1718ca62077b172864865020b401d21b510784009517588218187b18487b1650646882192828083309951750017b79951730951a4864595020b6016982173051070c8217388215402827ff8217388218087b870882177b8714070100000000000080280dff3307591201330970280133082028b614330788280128a21b33072b11013308a40028c4143307e6100133084528b91433072b11013308a40028ad149511a07b10587b15507b164882ab08649c64856476510b2582a782a8107b1b087b1510ab5b4eac8c5e510816645964ca7b1c5020b80127f22816510c182805510c1364c764587b1c5020ba0185f1821c2805645701887801db77057b65087b6c107b688210588215508216489511603200491118330ba02801951808951910951a1828623307b828017b171849112001491138491128084911303308c8280195171828a51352072464979511d0647833074828017b174911080149112049111008491118641728811364872808fb8089348277849a10520a43018499205209a20128f60c9511e864ac7b18087b19103309e02801951808951a107b1b330be028015020be01ce13009511d87b10207b15187b161064955709860064866479c85807d887075207788298108297c987077b1908ac574657085c829a08c88a07d8a70a520a507b18d87608c96709c9760adb890aac5a55646864595020c001831a8217c875058217087b75108210208215188216109511283200330a01330b01649764595020c2010efd8219088298105908aa3307de13013308612811133307de130133086128061333073813013308a60028fa12951178ff7b108000641a551a805164827c77330c951b80000184780f88890a868a30958857db9a0878b8ff95bbff98770495cc015207e59ac78000562780000b193309b71401330a0264275020c4019703821080009511880032003307811501330861289412951178ff7b108000641a551a805164828277330c951b80000184780f88890a868a30958857db9a0878b8ff95bbff98770495cc015207e59ac7800056278000a5183309b71401330a0264275020c6013103821080009511880032003307811501330861282e12951178ff7b108000641a551a805164828277330c951b80000184780f88890a868a30958837db9a0878b8ff95bbff98770495cc015207e59ac78000562780003f183309b71401330a0264275020c801cb0282108000951188003200330781150133086128c811828920828828647a649764a901951178ff7b1080007b15787b167049116020330a03781a688296204911304911407b17507b18587b190851063b01829728330838d6870852085602977803977a06c98a0a570a4902c8a608d8680852083f025107a7017b1a288298088295829a107b1a108299187b19189577ff9777039877039577017b17955508958801956618019588ff7b18205108bf018259510918821a588217508258f882aa18b4a002cc0152078d018267107b17607c69188268f8826778196851082852180119821818ae87ac01977704821810c8870782785108073308280e8277083308012806330801018269e87b18307b1738330702aa791f8267f052190125821818ae878101977704821810c8870782785108073308280e8277083308012806330801018269087b18407b1748821718ae792801979804821710c87808828751078b07828908951830b49002ce015207e7009555108217289577c89566387b1728821820520724ff288a0082971898783c52082d0197780457082601821908829510c885097b1920d859095209130151077a82190882970882969588f09888049588017b18956608957801019588ff7b18285108b6008269510917821a588217508268f882aa18b4a002d00152076982575107f906825908951830b49002d201520755955510956610821720821828ab75bd8219088218829708ac781a283d3308829708560710283333088219088297085407298217088277978804c88709821a58821750829882990882aa18b4a002d401510708330701280533070182108000821578821670951188003200330751180133086128690f33075a1601330918290133082b28370f3307511801330861284e0f33078516013309302901330820281c0f33078516013309302901330820280c0f3307b7170133086128230f3307b7170133086128180f9511987b10607b15587b165064727b1c307b1b280180263484670152075a49113800001164c5846704510743570a4502c8a907d8970752073b027b1a187b1920551a2048510a1f0164a9330a821820017d878977c0857701c8a707aca70e029599ff958801647a5209e9282f330c822752073d286195c501510504024911382b8467045107e928a6649764a87b12405020d601761182124001c85707ac57cd006475821a18821c20822751072a822908ae95248466085206557c2738c959097b1a187b1c205a1701ac005207bd007b191028bc008225208226286457646882193864ab64ca5020d801a7013308015207cd00826c186457821828821930821060821558821650951168320c7b19108127307b17087c28387b18822720822628481230307b1240461238017b1748646882193864ab64ca5020da01560152077b821710c95705955501019555ff5105dd00826920330830821748b49002dc015107eb28566457ae573bff3307602901285a145217021695980151080c019897019888017b181028086497491110018228207b18488226288128307b1840957501019555ff510528826920821748821840b49002de015107ec330801016487821060821558821650951168320082154864576468821938821a20821b185020e001b1003308015207d7826a186457821828821930b4a002e2013308015207c18217109a777b17383305ff01821738c857075117ff45826920821748821840b49002e4019555015107e52839826a18821748821828821930b4a002e60133080152077eff33088217408219087a79308219787938286aff821510821710d87508285eff821710d875082855ff3307502b01285a133307b71701330861289c0c33074829012847133307782901283f139511d87b10207b15187b161064a6647c5139000011286485828a2064c764987b1b087b1cb4a002e801821c6458821b08647933070152092051061a828a1864c7646864b9821020821518821610951128320a33070182102082151882161095112832009511a87b10507b15487b16406476827a827710847701510a08520709286a5107b6017b1a205709ec01c89807d8870a520ae2017b19107b16188265184911387b18087b18287b17309555013306000011019517289555ff5105105020ea01f6018388ab68ef28115020ec01e901838852380000118c00821910821808821c20821618510c520157099601c89807d8870752078c018265087b19107b18085519203f51090c01330a017d878977c0857701c8a707aca756019599ff958801647a5209e9ae57277c69383308330a01c97505ad9a4f51096164584901285d648764985020ee01360eac57df826828826720828c1882180882191028e600821910821808821c205107b200ae97b200649ac878097d9b591bc0ad00330b64a928ab0052190215955701510701019858019877017b1728057b15018267207b17208265288167307b1718958601019566ff51061a825920821720821818b49002f0015107ec330701289700825a18821720821808821910b4a002f201647833070152087e82179a777b17103306ff01821710c867073308ffaa8758825920821720821818b49002f4019566015107e3284a3307560516ff2836ff648b280f649aab975cff648b64a9ac7a6bdbb709dbbb08821618520cb6fe826a2882672082ac1801821050821548821640951158320c82168217d8760728088217d876070182105082154882164095115832003307502b0128cb103307b71701330861280d0a3307b7170133086128020a330778290128ad103307b2180133086628ef09827a2882772082ac18320c648a6478649764a928b3fd9511f87b15827508827aaca5af00aa5a607ca895ab016c8c7b7b590c71aa5bc50084831f7cac0195ab027b7b84cc3f5328e0004daa5bae007ca20295ab0397c406842c3fd4c4027b7b5328f0003baa5b940095ab047b7b7ca80397333d983c2b97220684883fd4c808d48208281f3308000011649782159511083200973806d4c808280997330cd4320801858c00d80033020000efc82c0c543cff07ef2cacb531827910c9ab0ac89a0aac9a307b7a1064978215951108320033077017013308472815093307181801330839280a09330770170133084728ff08330790290128aa0f33071e170133085228ec088279088277829c18320c827a827908648764a828b0fc7e776489330801019511e87b10107b1508987a046492542a7002890096a94703989313963a1027bfa70b97b730987732967a7b1498a7108479fe073304b91401c8490cac9c4b01951904d89c07c9c905c99c09db7509541901430198aa1196a764bf7b0b7cc70197bb317cc998bb30781705c84b07781904acb72d01951906d8970ac9790bc99709dbab09330b01ae9b22017c79017c77781907781706285c330b0564735417635198790296997b14989311963a64bfa707977731987a303307b91401c8a707aca7f600951b03551bfdfa00951a06d8a70bc97a0cc9a70adbbc0a541a01fc007c7a017c77781a07781706330b030155130a4095bbff951703c8b70aac7a8100d87a0786393078a98477015207679abc05951703c87b0b3309016427330a5020fc019af882101082150895111832009739319899303307b91401c89707ac978f0095bbfe951303c8b309ac399900d89704c9790ac9970cdb4a0c541c0194007c7a017c7cd83907789a01789c84770151079f3307be19013308612853073307be19013308612848073307be1901330861283d0733071819013308a6002831073307be190133086128260733071819013308a600281a073307be1901330861280f073307be19013308612804073307be190133086128f90633071819013308a60028ed063307be190133086128e20633071819013308a60028d60682776489330801019511d07b10287b15207b1618649298790433051453297102c30214094b598638d6c56d34d6970c98c40b96491027bf970b97b930989932969a7b1498a910849cfe073303b91401c83c09acc977039515045515f07b03951c14d8c905c99c06c9c90cdb560c541c01720398aa1196aa64bfab0b7c9a0197bb317c9c98bb30781a15c83b09781c14acb95c03951b04551bee6003951a16d8a90bc99a0cc9a90adbbc0a541a0157037c9a017c99781a17781916330510534700e1f505160214092d431cebe2361a00d6940998990296991027c9940b96ba7b1498a912849cfeff00c83c09acc9de02951c04561cf3e202951c10d8c906c99c05c9c90cdb650c330602ac6cd60298aa1396aa64bfab0b7c9a0197bb317c9c98bb30781a11c83b09781c10acb9c102951a04561af1c502951a12d8a90bc99a0cc9a90adbbc0a531a02bc02140afdce61841177ccab7c9b017c9cd6a70998991a781b13781c12140a0010a5d4e800000033050caca76a01140acc10c7bab88d0600d6a90a96aa1027c9a90b96ba7b1498a912849cfeff00c83c09acc93002951c04561cf73402951c0cd8c906c99c05c9c90cdb650c330602ac6c280298aa1396aa64bfab0b7c9a0197bb317c9c98bb30781a0dc83b09781c0cacb91302951a04561af51702951a0ed8a90bc99a0cc9a90adbbc0a531a020e02140a3322d45b02332f237c9b017c9cd6a709989925781b0f781c0e140a0000c16ff2862300330508aca75101969abbb88d0698aa2896aa1027bfa90b97b920140a000000007b140000d6a90a98a912849cfeff00c83c09acc97a01951c04561cfb7e01951c08d8c906c99c05c9c90cdb650c330602ac6c720198aa1396aa64bfab0b7c9a0197bb317c9998bb30781a09c83b0a781908acba5d019519045619f9610195190ad89a0bc9a90cc99a09dbbc0953190258011409577813b12f65a5397cab017caad69707987733781b0b781a0a3305045617631828a80064475614630f289f00649701541763990097793098993296997b14989911969a64bfa707977731987a303307b91401c8a707aca708019555fe951c04c85c0aacca0601d8a703c97a04c9a70cdb340c541c0117017c7b017c7778ab0178a753190a4d9799013307b91401c89707ac97e300955bfe951304c8b309ac39f800d8970cc97905c9970adbc50a541a01f3007c7a017c7cd83907789a01789c2825649756176371ff647955190abb955bff951704c8b70aac7aa700d87a0786993078a9018477015207279abc14951704c87b0b3309016427330a5020000214f482102882152082161895113032003307be1901330861280d033307be19013308612802033307be190133086128f70233071819013308a60028eb023307be190133086128e0023307be190133086128d50233071819013308a60028c9023307be190133086128be023307be190133086128b3023307be190133086128a8023307be1901330861289d0233071819013308a6002891023307be190133086128860233071819013308a600287a029511e07b10187b15107b160882890851098b01330b7b18828833029a1a013304991a01330380003306c0280864cbae9c3701c8b80c7dc595bc015905f28450ff00c802057c5551150448511503255215021701d89c05c8c80ada540a7caa84aac000ab3a030195bc02aebcc3283f01d89c0ac8c805daa4057c555120e000346c555220ed004958159fb80028da00d89c0ac8c805daa4057c555120f000245220f400406c5a33058fafa54728ba00845ae0003305a000aa5a880028ab00955a70ff330530ac5a2b289e00950a1fff33030cae3a5a33038000ad6566288a00950a0fff561a0283006c5a3306c0af6a7995bc02acbcc900d89c0ac8c805daa4057c5a84aac000ab3a6095cc01510cb800d89c0ac8c805daa4057c5a84aac000ab3a4795cc01520c08ff28a300840afe00522aee0036330380003306c0af652a95bc02acbc72d89c0ac8c805daa4057c5a84aac000ab3a1295cc01520cd3fe3307d8290128c807acc96e570c76c8c806ac867cc9c90957098100821a7ba67ba908acbc8100c8b809ac898400c9bc0c7b787b7b087b79107b7c18280549070182101882151082160895112032003307a82901287a073307c029012872073307f02901286a073307082a012862073307202a01285a0733079d1c01330864289c003307fb1b013308a20028900033079a1b013308612885003307fb1b013308a200287933079d1c01330864286f33079a1b0133086128659511e87b177b180847111001009511b8827a28827b207b1a287b1b20827a18827b10827c0882777b1a187b1b107b1c087b1764177b17307b1938461140461141009511c07b17307b18389517307b17491108014911204911100849111864176498289f9511c07b17307b18389517307b1749110801491120491110084911183309382a0164172887951118ff7b10e0007b15d8007b16d0007b187b19087b1a107b1b183307891e017b17200182c88215e800491128025208359517207b17603307f8017b176864177b17703307f6017b17789518107b1880007b1788003307902a017b17a0003307032850951730330930c8910664c8502002029f069517207b17603307f8017b17687b16703307ca017b177864177b1780003307f6017b1788009518107b1890007b1798003307502a017b17a000330704017b17a8004921c0009518607b18b0007b17b8009517a000645828aefe9511d87b10207b15187b16107b17510985013303330a0fe4a90b95bbf1958a0784aaf8c98a079a9c330a851f017b1a083300f82809953301ae935b01c8380a7daa570a5bbf370a84aa07520aebaeb33357039b01c8380ad88a0652069101ae0a820182a682aa0814058080808080808080d46a0ad25a0a520a09953310acb3d3ae9397009a36c8380a017da5570588009566ff95aa01ab6cf428fa0084a6ff00821a08c86a0a7ca55115044d51150327521502f30095350151053e01ae95e300330201c858067d663304015816bfc10028f100953501ae95c900c885057c553302015126e0003b5226ed004c6c5a581a9f5c28b900953501ae95a700c885057c555126f000286c555226f4005558158f5c2892009a6301ac9325ff2878845ae000512aa00029288500955a70ff531a303c2872956a1fff6c54531a0c0d846afe00522aee006a5914c065953502ae9552c8580a7daa3306bfafa634285d956a0fff561a02425915c03e953602ae9633c886067d6a5a1abf3c953503ae9524330201c8580a7daa3306bfada634955301ac93adfe330a82177b78087b791028333302282033020133040128183304012813330201330402280b33040228063304030182177b7308787210787411330a01017b7a821020821518821610951128320033078520013308612826fd3307852001330861281bfd3307382b0128c6039511c87b10307b15287b16206479957c0784ccf8c97c07ac7832c8790aac9aba02c978075707be02140bf8ffffffffffff7fd2b705c85c0baccbb60298720351020aab9c3d3306285857088302c88907d8970752077902510823330a017d978977c0857701c8a707aca70f029588ff959901647a5208e928d101330728cc013308c9c90c017d9b89bbc085b601c88606ac86f70195cc019599016468520ce9847707c85a0bc87b08acb851025107aa013308017db98999c0859901c88909ac89c1019577ff95bb0164985207e9c89607ac678c01140801010101010101011409ff00ff00ff00ff007b1910140b0000000001000100140901000100010001007b19083306df01510242013309c000e69204974303c8a303aca37801c9420b98b93c520979018449fc00979903c89a0cacac7701984902979905c89a00aca075017b1b187b125512040833022872330201aca61c0182a98595ff985507989906d45909d28909c82909ac29fc0082a508855bff98bb07985506d45b0bd28b0bc8b905ac95e30082a910859bff98bb07989906d4b909d28909c85909ac59ca0082ab1885b5ff98550798bb06d45b0bd28b0bc8b902ac92b10095aa20ab0a96821b10d2b209982a08d2ba0ac8a909821a08caa909989930c87909ac79e400844503643a6497821218510520ff975503c8c505acc51401330a3307c000821be67b078477039777030182cb85b6ff98660798bb06d46b0bd28b0bc8ab0bacabaa009577f895cc0864ba5207e0821810d28b0798bb08d2b808c88707821808ca8707987730c89707ac97cb0082103082152882162095113832006467ae677cfe3307c02a012862013307202b01285a013307621d01330861289cfa3307502b012847013307502b01283f013307502b0128370133078520013308612879fa33076821013308a200286dfa33078520013308612862fa3307011d013308612857fa3307d82a012802013307082b0128fa003307852001330861283cfa3307e822013308612831fa33074923013308a2002825fa3307e82201330861281afa3307852001330861280ffa3307011d013308612804fa3307f02a0128af009511907b177b18087b19107b1a1864177b17503307f8017b17589517107b17603307f6017b17683307682b017b1720491128024911409518507b18304911380295172064b82851f99511a07b17491108800064177b17403307fe017b17489518087b18507b17583307882b017b1710491118024911309518407b1820491128029517103308002901280ef96689330a01aba91814090000000000000080c98909d879078577013200330732009511d064783307a82b017b174911080149112049111008491118641728cef89511d064783307c82b017b174911080149112049111008491118641728aff89511d03308e025013307d82b017b1749110801491120491110084911186417288df85109167c7a7c8babba139599ff9588019577015209f033073200c9ba0732009511e87b10107b15087b165319106c8d7a84a207c82706ae671a642c647a648b017cb578a595aa0195ccff95bb01520cf3c82804c929028423f8844807c8360a51085caea66c9748038480385100718445f8825b8d88848c38955808018285d00b0bcfc509d4b9097b69956608958808645baca6eb283a647ac8970baeb7137c8b78ab95aa019599ff9588015209f382101082150882169511183200aea61364480182897b69956608958808aca6f6842907c83408c89a0bacbac728d400499224092049242a6422492a2189249248929024494a0029512a99244b1295a4904825492512a10450888484908448444492245248484924892492a48424938888480a8988888888482122119144124992a44a8a6412892491444492244992a42449124925a52401a42449490288a49010458a2291500005b0888842129290844844444922514800092993482489129290444a3212214992a44a1449249244222224892424894442924452889022a5925292005292a4289284022880454414929084244422222a89142121294922892492a48424934444440a8988888888482122119144229294a44a8a3489449248224949129224498a2489920490944a26a59424802485842852148984005a444421094948422422a224912824a44c22912425444444122295644444922449925449914422492421494444441212892489a41021454a25a52401a424495124090550008b8828242109911240251592529224920052129124a5109192529284941249212225a52409494a924222952425122149528884244442242445924202882491a4249100d2480aa594242949222944644912920092009a24252580a424854848421222495292844884248524a55492244992244992a4a442529292246949524808494952480009208d88284992121151459200522209112249924844914824228504901229091151168984a42444442992444849128945222189248588281689844412404a22912429448822459148484922298810458a22912049124941842892282222489248521011c52291204982884891441111414224242112222109911009498884244442244952a5248954412955504d9292a4842429094921121289842480a45402484a2580a45402484a25492a491449124929059194480049244924a98224294965494192a449a24a8824a58424444a922452a224a51444499224890092244924a5205292aa2c29489292a4009624858448484224494a92a420514929294992242924219224250124294854524a4a9224490a498824494900490a12959492922449924212222991c8244992244982282528952441925490ca24492a48c92429441a2a2192420aa0242549422422a2225290484444a42011114952509228880092001a11512412918244225290484129ac9244410490004a240511491149a24852954409a04422229288142480122448c422124122499024492249529224499284484952900092001a11519224294812499282449288888292a420012480324941244a92121141129148244912111129888888888888884841444444444444440a2222222222222252908888888888142412459254490490004a220922914411119120224144828804251141a2a0244c2589820820019420892c11112422224992248920254992249148888448444451922491888828254592244522804844044912492282181189449288240911221122828848d5485210114b2241442222151451111151922412111112894424888824412405892449948208209152101125a524124452101125a524124424051151524a22414452101125a524124424911444444929890411442491144444492989044952101125a524122429412291929292485011118544222109911042921009911009911009911009292112222112921009498884488884480a490a92a410429210495205a9002225250949935288a40a520a9124a954412a4805102929494a92244992a42429444224444212119148a29429492a954812a9294924924848244952a920512449242449928c54104924098924492a482549120989a42491a04544142221094942281229a52a252222254844922412404226498408910849442289920491240992241109202135492244884448221249240922494882444a04314912214204102189489248224192a4b288884212929024494a2a495205492a93248548a12449490a1295949292244992422449922488482a484a492409a06492a420842421924492841092241189201152224892044912499224092124294982242449923422a22409122109911069a884244412922405959224a454086a2489929224919424912492924812492299284149524892a42405894a4a4949922449219242124a948444920849948444920849929444a22492b2124992489a0489a4829449420a922449495229091222212169a8841092a4a0529284940a418d24515292249292249244521249224924132528490a495225290490944a0a01a414922491b424292522495210258924499224012429892449412449a92c292449925400092109099190844892a44a499254412955504d9292a48424854422214929148994921289a424248560921495242549929424a9a024495252419290842444228a2a2649922411841444149288a28a49922449042105118524a2a86292244912414841442149aa1149244992449248124944529224494926519224449294442249a9a44c922412494a2565924492220991244924894492249294a464122549428a24244992924a2a494a929290d448a29084908424842484242421499292498292482289a82c4924a54a8948aa049928294992242192a452412225495292944422a5822499481252111289249549924c92846449522a4990a4242429994492909424215192922449121292909024a9a0540a9592a44a929a24254925499148224992244c129482024912914492889624929294245505499224494429542291549324992409499224945432499284a42255549244924952a532490a094948424292a4aaa414299548524224490a91922449a152523284044992540a494842121292944aa532490a2291241221912409499292249224899494229124098984244948494692484a92a48224252191249224a4a42424210949888424444212929084484842a432494a82009248128990484892842449491209499290920404904422219190244924494a92484892840052920490082091484824244912499292241292242180942401248244124012120949924492a424898424490820254922451a924892844492489290522424924492849454a14812499324a920490a494842122221094988842424210949888424449224518510a94849a48890a48824490a859024855092884824892492082589244512498a24122192244991444892242a9148496a921412121212129210094988420a292526499294a4926492148954495224122a22a224218b48122991448448490a92482245442242644422a2244951922485a4444a92441251024892445924492445844424252491124291440a8548a30889288a0891a4a4884852244949a2922a2925a52c499624852424214952922412012491a42289b424919494b224919424112d492425114000092080644492482292481229aa5192249124492249924492248924499224914a245194644992445292244912495211121292909090908448484212121292908448484212929094a444924848248994144a244948248984940490a44aa148a4a4502452124291484925492a25294a52ad244992244952c99294944a2a4992525a4992a4", - "05000000000000000000000000000000000000000000000000000000000000": "00000000", - "00bb008900000000ff27ba2d5df53f556a03c9824f6fee1e52d0ca53b19dc4": "0100000000" - } + "0c000000000000000000000000000000000000000000000000000000000000": "00000000000000000000000000", + "003f00b000000000322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea": "0100000000", + "0b000000000000000000000000000000000000000000000000000000000000": "00000000" + }, + "genesis_header": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25bb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aaff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499acedee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d9339326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e3410746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a2105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c060000ffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "id": "dev", + "protocol_parameters": "0a00000000000000010000000000000064000000000000000200004b00000c000000809698000000000080f0fa020000000000ca9a3b00000000002d3101000000000800100008000300403800000300080006005000040080000500060000fa0000017cd20000093d0004000000000c00000204000000c0000080000000000c00000a000000" } \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/message_types.py b/pyjamaz/transport/jamnp_s/message_types.py index c44cfb4b..83d82daa 100644 --- a/pyjamaz/transport/jamnp_s/message_types.py +++ b/pyjamaz/transport/jamnp_s/message_types.py @@ -2,13 +2,45 @@ from enum import Enum from typing import List, Optional, Dict +from jamcodec.base import JamCodecTypeDef, JamBytes, JamCodecType from jamcodec.mixins import Serializable -from jamcodec.types import U32, H256, U8, VarInt64, Vec, Option, Array, Map, H512 +from jamcodec.types import U32, H256, U8, VarInt64, Vec, Option, Array, Map, H512, VecType from pyjamaz.models.block import Header, Guarantee, Block from pyjamaz.models.common import WorkPackage + +class ImplicitVec(Vec): + + def encode(self, value: list) -> JamBytes: + # Encode length of Vec + data = JamBytes(bytes()) + + for idx, item in enumerate(value): + if type(item) is JamBytes: + data += item + else: + data += self.type_def.encode(item) + if item and issubclass(item.__class__, JamCodecType): + value[idx] = item.serialize() + + return data + + def decode(self, data: JamBytes) -> list: + value = [] + + while True: + obj = self.type_def.new() + obj.decode(data) + value.append(obj) + + if data.get_remaining_length() == 0: + break + + return value + + @dataclass class MsgUP0Leaf(Serializable): header_hash: bytes = field(metadata={'codec': H256}) @@ -43,7 +75,7 @@ class MsgCE128BlockRequest(Serializable): @dataclass class MsgCE128BlockRequestResponse(Serializable): - blocks: List[Block] = field(metadata={'codec': Vec(Block.to_codec_def())}) + blocks: List[Block] = field(metadata={'codec': ImplicitVec(Block.to_codec_def())}) @dataclass @@ -118,7 +150,7 @@ class MsgCE135GuaranteedWorkReport(Guarantee): # # @dataclass # class Preimage(Serializable): -# bytes_: bytes = field(metadata={'codec': Vec(U8)}) +# bytes_: bytes = field(metadata={'codec': ImplicitVec(U8)}) # # # ───────────────────────── CE-137 / 138 / 139 / 140 ─────────── # @dataclass @@ -131,12 +163,12 @@ class MsgCE135GuaranteedWorkReport(Guarantee): # erasure_root: bytes = field(metadata={'codec': H256}) # shard_index: int = field(metadata={'codec': U32}) # segment_indices: List[int] = field( -# metadata={'codec': Vec(U16)} # u16 each +# metadata={'codec': ImplicitVec(U16)} # u16 each # ) # # @dataclass # class JustificationNode(Serializable): # 0,1,2 tagged union – bytestream -# raw: bytes = field(metadata={'codec': Vec(U8)}) +# raw: bytes = field(metadata={'codec': ImplicitVec(U8)}) # # # reply payloads for 137-140 are raw blobs; dataclasses optional # @@ -144,7 +176,7 @@ class MsgCE135GuaranteedWorkReport(Guarantee): # @dataclass # class Assurance(Serializable): # header_hash: bytes = field(metadata={'codec': H256}) # anchor -# bitfield: bytes = field(metadata={'codec': Vec(U8)}) # len = ceil(C/8) +# bitfield: bytes = field(metadata={'codec': ImplicitVec(U8)}) # len = ceil(C/8) # signature: bytes = field(metadata={'codec': Array(U8, 64)}) # # # ───────────────────────── CE-142 ────────────────────────────── @@ -163,7 +195,7 @@ class MsgCE135GuaranteedWorkReport(Guarantee): # @dataclass # class NoShow(Serializable): # validator_index: int = field(metadata={'codec': U32}) -# announcement: bytes = field(metadata={'codec': Vec(U8)}) # raw bytes +# announcement: bytes = field(metadata={'codec': ImplicitVec(U8)}) # raw bytes # # @dataclass # class TrancheEvidenceFirst(Serializable): @@ -172,15 +204,15 @@ class MsgCE135GuaranteedWorkReport(Guarantee): # @dataclass # class TrancheEvidenceSubsequent(Serializable): # signature: bytes = field(metadata={'codec': Array(U8, 96)}) -# no_shows: List[NoShow] = field(metadata={'codec': Vec(NoShow.to_codec_def())}) +# no_shows: List[NoShow] = field(metadata={'codec': ImplicitVec(NoShow.to_codec_def())}) # # @dataclass # class AuditAnnouncement(Serializable): # header_hash: bytes = field(metadata={'codec': H256}) # tranche: int = field(metadata={'codec': U8}) -# announcement: List[CoreWRPair] = field(metadata={'codec': Vec(CoreWRPair.to_codec_def())}) +# announcement: List[CoreWRPair] = field(metadata={'codec': ImplicitVec(CoreWRPair.to_codec_def())}) # evidence: bytes = field( # discriminated union; raw bytes easiest -# metadata={'codec': Vec(U8)} +# metadata={'codec': ImplicitVec(U8)} # ) # # # ───────────────────────── CE-145 ────────────────────────────── diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index ef3e605d..b0a382b4 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -27,7 +27,7 @@ #TODO: typings -def wrap_protocol(protocol, quic_connection, direction, host, port): +def create_jam_connection(protocol, quic_connection, direction, host, port): def create_connection(*args, **kwargs): conn = quic_connection(*args, **kwargs) conn.protocol = protocol @@ -79,6 +79,11 @@ def __init__(self, host, port, certificate, private_key, app): if bl_hash.startswith('0x'): bl_hash = bl_hash[2:] bl_hash = bl_hash[:8] + #TODO: + # callbacks like ce128_processed_block_request could be created using closure functions + # (def create_ce128_processed_block_request returning the callback), + # to create a local state which holds context like which connection/node initiated this event, so we can mark + # malicious nodes etc... self.pubsub.subscribe(MESSAGE_TYPES.CE128_SUCCESS, self.ce128_processed_block_request) self.pubsub.subscribe(MESSAGE_TYPES.CE128_FAILURE, self.ce128_processed_block_request) @@ -115,7 +120,7 @@ async def listen(self): self.host, self.port, configuration=server_conf, - create_protocol=wrap_protocol(self, JAMConnection, StreamDirection.acceptor, self.host, self.port), + create_protocol=create_jam_connection(self, JAMConnection, StreamDirection.acceptor, self.host, self.port), #session_ticket_fetcher=self.session_ticket_store.pop, #session_ticket_handler=self.session_ticket_store.add, retry=True, @@ -139,7 +144,7 @@ async def connect(self, host, port): port, configuration=configuration, # session_ticket_handler=save_session_ticket, - create_protocol=wrap_protocol(self, JAMConnection, StreamDirection.initiator, host, port), + create_protocol=create_jam_connection(self, JAMConnection, StreamDirection.initiator, host, port), ) as client: client = cast(JAMConnection, client) await client.wait_closed() @@ -188,15 +193,13 @@ def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): if not block: logger.debug(f"Received newer block from handshake: {msg.header_hash} -> initiate CE128RequestBlocks") self.state_requesting_blocks = True - #self.ce128_initiate_block_request(conn, MsgCE128BlockRequest(msg.header_hash, MsgCE128BlockRequestDirection.DESC.value, 1)) #TODO: max_blocks=1 for now, >1 results in error from node? - #slot = self.app.state.timeslot.number - #bl_hash = self.app.retrieve_block_hash(slot) + curr_hash = self.app.retrieve_block_hash(self.app.state.timeslot.number) # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? self.ce128_initiate_block_request( conn, MsgCE128BlockRequest( - msg.header_hash, - MsgCE128BlockRequestDirection.DESC.value, + curr_hash, #msg.header_hash, + MsgCE128BlockRequestDirection.ASC.value, #MsgCE128BlockRequestDirection.DESC.value, 10 ) ) @@ -221,12 +224,13 @@ def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement if not block: logger.debug(f"Received new block announcement from up0: {msg.header.hash}") self.state_requesting_blocks = True + curr_hash = self.app.retrieve_block_hash(self.app.state.timeslot.number) # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? self.ce128_initiate_block_request( conn, MsgCE128BlockRequest( - msg.header.hash, - MsgCE128BlockRequestDirection.DESC.value, + curr_hash, #msg.header.hash, + MsgCE128BlockRequestDirection.ASC.value, #MsgCE128BlockRequestDirection.DESC.value, 10 ) ) @@ -291,20 +295,23 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 if block_req.direction == MsgCE128BlockRequestDirection.ASC.value: # Note: exclusive request block hash block_child_hash: bytes = self.app.retrieve_block_child_hash(next_hash) - block = self.app.retrieve_block_by_hash(block_child_hash) - if block: next_hash = block.header.hash + if block_child_hash: + block = self.app.retrieve_block_by_hash(block_child_hash) + next_hash = block.header.hash + else: + break elif block_req.direction == MsgCE128BlockRequestDirection.DESC.value: # Note: inclusive request block hash block = self.app.retrieve_block_by_hash(next_hash) - if block: next_hash = block.header.parent + if not block or block.header.timeslot == 0 or block.header.hash == bytes(32): + break + + next_hash = block.header.parent else: # TODO: error raise Exception("HMmmmmmz?????") - if block and block.header.timeslot > 0: - blocks.append(block) - else: - break + blocks.append(block) logger.debug(f"CE128 sending {len(blocks)} blocks (direction={block_req.direction} max blocks={block_req.max_blocks})") From 56df363e314debc49f90d9b41ed1f8b751ef8535 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Wed, 16 Jul 2025 16:18:15 +0200 Subject: [PATCH 21/51] - JAMNP wip --- pyjamaz/cli.py | 1 + pyjamaz/transport/jamnp_s/connection.py | 4 +- pyjamaz/transport/jamnp_s/message_types.py | 50 +++- pyjamaz/transport/jamnp_s/protocol.py | 226 +++++++++++++++++- pyjamaz/transport/jamnp_s/stream_base.py | 29 ++- pyjamaz/transport/jamnp_s/stream_map.py | 25 +- pyjamaz/transport/jamnp_s/streams/stream_0.py | 13 + .../transport/jamnp_s/streams/stream_128.py | 14 +- .../transport/jamnp_s/streams/stream_131.py | 30 ++- .../transport/jamnp_s/streams/stream_132.py | 30 ++- .../transport/jamnp_s/streams/stream_133.py | 38 ++- .../transport/jamnp_s/streams/stream_134.py | 51 +++- .../transport/jamnp_s/streams/stream_135.py | 30 ++- .../transport/jamnp_s/streams/stream_136.py | 28 ++- .../transport/jamnp_s/streams/stream_141.py | 31 ++- .../transport/jamnp_s/streams/stream_142.py | 40 ++++ .../transport/jamnp_s/streams/stream_143.py | 35 +++ 17 files changed, 616 insertions(+), 59 deletions(-) create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_142.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_143.py diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index ad189de7..e7ab1559 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -309,6 +309,7 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, # # logging.debug(f'Connecting to node {validator_address}:{validator_port}') # tg.start_soon(nps_protocol.connect, validator_address, validator_port) + tg.start_soon(nps_protocol.connect, "127.0.0.1", 40000) pass await anyio.sleep(ts - time.time()) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 03226401..a7467fb8 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -122,7 +122,7 @@ def quic_event_received(self, event: QuicEvent) -> None: stream_type = int(data[0]) stream_cls = StreamLookup.get(stream_type) if stream_cls is None: - raise Exception(f"QUIC stream {stream_id} is not mapped") + raise Exception(f"QUIC stream {stream_id} is not mapped (type {stream_type})") stream_obj = self.open_jam_stream(stream_cls, direction=self.direction, stream_id=stream_id) @@ -138,7 +138,7 @@ def quic_event_received(self, event: QuicEvent) -> None: #TODO: of idem? kan een accepting connection bv ook een block request terug sturen? raise Exception(f"Received data from unknown stream id: {stream_id}") - self.streams[stream_id].receive_data(data) + self.streams[stream_id].receive_data(data, event.end_stream) # try: # self.streams[stream_id].receive_data(data) # except Exception as e: diff --git a/pyjamaz/transport/jamnp_s/message_types.py b/pyjamaz/transport/jamnp_s/message_types.py index 83d82daa..150fd603 100644 --- a/pyjamaz/transport/jamnp_s/message_types.py +++ b/pyjamaz/transport/jamnp_s/message_types.py @@ -233,4 +233,52 @@ class MsgCE135GuaranteedWorkReport(Guarantee): # @dataclass # class ValidatorSig(Serializable): # validator_index: int = field(metadata={'codec': U32}) -# signature: bytes = field(metadata={'codec': Array(U8, 64)}) \ No newline at end of file +# signature: bytes = field(metadata={'codec': Array(U8, 64)}) + + +# ───────────────────────── CE-143 ────────────────────────────── +@dataclass +class MsgCE143HashRequest(Serializable): + hash: bytes = field(metadata={'codec': H256}) + +@dataclass +class MsgCE143Preimage(Serializable): + bytes_: bytes = field(metadata={'codec': Vec(U8)}) + +# ───────────────────────── CE-141 ────────────────────────────── +@dataclass +class MsgCE141Assurance(Serializable): + header_hash: bytes = field(metadata={'codec': H256}) # anchor + bitfield: bytes = field(metadata={'codec': Vec(U8)}) # len = ceil(C/8) + signature: bytes = field(metadata={'codec': H512}) + +# ───────────────────────── CE-142 ────────────────────────────── +@dataclass +class MsgCE142PreimageAnnouncement(Serializable): + service_id: int = field(metadata={'codec': U32}) + hash: bytes = field(metadata={'codec': H256}) + preimage_length: int = field(metadata={'codec': U32}) + +# ───────────────────────── CE-133 ────────────────────────────── +@dataclass +class MsgCE133WorkPackageSubmission(Serializable): + core_index: int = field(metadata={'codec': U32}) + work_package: WorkPackage = field(metadata={'codec': WorkPackage.to_codec_def()}) + +@dataclass +class MsgCE133Extrinsic(Serializable): + bytes_: bytes = field(metadata={'codec': Vec(U8)}) + +# ───────────────────────── CE-135 ────────────────────────────── +@dataclass +class MsgCE135GuaranteedWorkReport(Guarantee): + pass + +# ───────────────────────── CE-136 ────────────────────────────── +@dataclass +class MsgCE136HashRequest(Serializable): + hash: bytes = field(metadata={'codec': H256}) + +@dataclass +class MsgCE136WorkReport(Serializable): + report: bytes = field(metadata={'codec': Vec(U8)}) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index b0a382b4..14063d66 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -18,8 +18,23 @@ from pyjamaz.transport.types import ProtocolType from pyjamaz.transport.jamnp_s.message_types import MsgCE128BlockRequestDirection, MsgCE128BlockRequest, \ - MsgUP0Handshake, MsgUP0Announcement, MsgCE128BlockRequestResponse + MsgUP0Handshake, MsgUP0Announcement, MsgCE128BlockRequestResponse, \ + MsgCE131SafroleTicketDistribution, MsgCE132SafroleTicketDistribution, \ + MsgCE134WorkPackageSharing, MsgCE134WorkPackageBundle, MsgCE134RefineResponse, \ + MsgCE141Assurance, MsgCE142PreimageAnnouncement, MsgCE143HashRequest, MsgCE143Preimage, \ + MsgCE133WorkPackageSubmission, MsgCE133Extrinsic, MsgCE135GuaranteedWorkReport, \ + MsgCE136HashRequest, MsgCE136WorkReport from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest +from pyjamaz.transport.jamnp_s.streams.stream_131 import StreamSafroleTicketDistributionStep1 +from pyjamaz.transport.jamnp_s.streams.stream_132 import StreamSafroleTicketDistributionStep2 +from pyjamaz.transport.jamnp_s.streams.stream_134 import StreamWorkPackageSharing +from pyjamaz.transport.jamnp_s.streams.stream_141 import StreamAssuranceDistribution +from pyjamaz.transport.jamnp_s.streams.stream_142 import StreamPreimageAnnouncement +from pyjamaz.transport.jamnp_s.streams.stream_143 import StreamPreimageRequest +from pyjamaz.transport.jamnp_s.streams.stream_133 import StreamWorkPackageSubmission +from pyjamaz.transport.jamnp_s.streams.stream_135 import StreamWorkReportDistribution +from pyjamaz.transport.jamnp_s.streams.stream_136 import StreamWorkReportRequest + from pyjamaz.transport.jamnp_s.connection import JAMConnection @@ -166,7 +181,7 @@ def up0_send_handshake(self, conn: JAMConnection): # Create a presistent UP0 stream for this connection slot = self.app.state.timeslot.number header_hash = self.app.retrieve_block_hash(slot) - leafs = [] #TODO + leafs = [] #TODO: For now, we only work with finalized blocks handshake = MsgUP0Handshake( header_hash=header_hash, timeslot=slot, @@ -327,7 +342,8 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 #TODO: PolkaJAM lijkt in dit geval een reset te sturen???? #TODO: ook op max_blocks checken -> bijhouden op de stream? logger.debug(f"CE128 block request finished") - stream.acceptor_reset(1) #TODO: REVERSE ENGINEER DEZE CODES + # stream.acceptor_reset(1) # Replaced with FIN + stream.conn.send(stream.stream_id, b'', end_stream=True) def ce128_abort_block_request(self): @@ -342,4 +358,206 @@ def ce128_abort_block_request(self): async def ce128_processed_block_request(self, *args, **kwargs): - self.state_requesting_blocks = False \ No newline at end of file + #TODO: add reason, origin/connection etc + self.state_requesting_blocks = False + + def up0_failure(self, reset_code: int): + logger.error(f"UP0 failure with code {reset_code}") + # TODO: handle UP0 reset, e.g., reconnect + + def ce128_block_request_failure(self, reset_code: int): + logger.error(f"CE128 block request failed with code {reset_code}") + self.state_requesting_blocks = False # Reset state on failure + + def ce131_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE131SafroleTicketDistribution): + stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep1, direction=StreamDirection.initiator) + logger.debug(f"CE131 initiating ticket distribution on stream id: {stream.stream_id}") + conn.send( + stream.stream_id, + stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True + ) + + def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, msg: MsgCE131SafroleTicketDistribution): + logger.debug(f"CE131 received ticket for epoch {msg.epoch_index}") + # TODO: verify proof, check if proxy, forward via CE132 if valid + # stream.acceptor_reset(0) + stream.conn.send(stream.stream_id, b'', end_stream=True) + + def ce131_ticket_distribution_success(self, reset_code: int): + logger.debug(f"CE131 ticket distribution successful with code {reset_code}") + # TODO: handle success, perhaps update state + + def ce131_ticket_distribution_failure(self, reset_code: int): + logger.error(f"CE131 ticket distribution failed with code {reset_code}") + # TODO: handle failure, e.g., retry or log + + def ce132_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE132SafroleTicketDistribution): + stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep2, direction=StreamDirection.initiator) + logger.debug(f"CE132 initiating ticket distribution on stream id: {stream.stream_id}") + conn.send( + stream.stream_id, + stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True + ) + + def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, msg: MsgCE132SafroleTicketDistribution): + logger.debug(f"CE132 received ticket for epoch {msg.epoch_index}") + # TODO: verify and process ticket + # stream.acceptor_reset(0) + stream.conn.send(stream.stream_id, b'', end_stream=True) + + def ce132_ticket_distribution_success(self, reset_code: int): + logger.debug(f"CE132 ticket distribution successful with code {reset_code}") + # TODO: handle success, perhaps update state + + def ce132_ticket_distribution_failure(self, reset_code: int): + logger.error(f"CE132 ticket distribution failed with code {reset_code}") + # TODO: handle failure + + def ce134_initiate_sharing(self, conn: JAMConnection, sharing: MsgCE134WorkPackageSharing, bundle: MsgCE134WorkPackageBundle): + stream = conn.open_jam_stream(StreamWorkPackageSharing, direction=StreamDirection.initiator) + logger.debug(f"CE134 initiating sharing on stream id: {stream.stream_id}") + conn.send(stream.stream_id, stream.create_message(sharing.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) + conn.send(stream.stream_id, stream.create_message(bundle.to_jam_bytes().to_bytes()), end_stream=True) + + def ce134_received_sharing(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageSharing): + logger.debug(f"CE134 received sharing for core {msg.core_index}") + # TODO: process mappings + + def ce134_received_bundle(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageBundle): + logger.debug(f"CE134 received bundle") + # TODO: verify, refine, respond with MsgCE134RefineResponse + response = MsgCE134RefineResponse(report_hash=bytes(32), signature=bytes(64)) + stream.conn.send(stream.stream_id, stream.create_message(response.to_jam_bytes().to_bytes()), end_stream=True) + + def ce134_received_refine_response(self, stream: StreamWorkPackageSharing, msg: MsgCE134RefineResponse): + logger.debug(f"CE134 received refine response") + # TODO: process response + # stream.initiator_reset(0) + stream.conn.send(stream.stream_id, b'', end_stream=True) + + def ce134_sharing_success(self, reset_code: int): + logger.debug(f"CE134 sharing successful with code {reset_code}") + + def ce134_sharing_failure(self, reset_code: int): + logger.error(f"CE134 sharing failed with code {reset_code}") + + def ce141_initiate_distribution(self, conn: JAMConnection, msg: MsgCE141Assurance): + stream = conn.open_jam_stream(StreamAssuranceDistribution, direction=StreamDirection.initiator) + logger.debug(f"CE141 initiating distribution on stream id: {stream.stream_id}") + conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + def ce141_received_assurance(self, stream: StreamAssuranceDistribution, msg: MsgCE141Assurance): + logger.debug(f"CE141 received assurance") + # TODO: process assurance + # stream.acceptor_reset(0) + stream.conn.send(stream.stream_id, b'', end_stream=True) + + def ce141_distribution_success(self, reset_code: int): + logger.debug(f"CE141 distribution successful with code {reset_code}") + + def ce141_distribution_failure(self, reset_code: int): + logger.error(f"CE141 distribution failed with code {reset_code}") + + def ce142_initiate_announcement(self, conn: JAMConnection, msg: MsgCE142PreimageAnnouncement): + stream = conn.open_jam_stream(StreamPreimageAnnouncement, direction=StreamDirection.initiator) + logger.debug(f"CE142 initiating announcement on stream id: {stream.stream_id}") + conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + def ce142_received_announcement(self, stream: StreamPreimageAnnouncement, msg: MsgCE142PreimageAnnouncement): + logger.debug(f"CE142 received announcement for hash {msg.hash.hex()}") + # TODO: check if needed, request via CE143 if so + # stream.acceptor_reset(0) + stream.conn.send(stream.stream_id, b'', end_stream=True) + + def ce142_announcement_success(self, reset_code: int): + logger.debug(f"CE142 announcement successful with code {reset_code}") + + def ce142_announcement_failure(self, reset_code: int): + logger.error(f"CE142 announcement failed with code {reset_code}") + + def ce143_initiate_request(self, conn: JAMConnection, msg: MsgCE143HashRequest): + stream = conn.open_jam_stream(StreamPreimageRequest, direction=StreamDirection.initiator) + logger.debug(f"CE143 initiating request on stream id: {stream.stream_id}") + conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + def ce143_received_request(self, stream: StreamPreimageRequest, msg: MsgCE143HashRequest): + logger.debug(f"CE143 received request for hash {msg.hash.hex()}") + # TODO: lookup preimage, send if available + preimage = MsgCE143Preimage(bytes_=b'') # placeholder + stream.conn.send(stream.stream_id, stream.create_message(preimage.to_jam_bytes().to_bytes()), end_stream=True) + + def ce143_received_preimage(self, stream: StreamPreimageRequest, msg: MsgCE143Preimage): + logger.debug(f"CE143 received preimage of length {len(msg.bytes_)}") + # TODO: process preimage + # stream.initiator_reset(0) + stream.conn.send(stream.stream_id, b'', end_stream=True) + + def ce143_request_success(self, reset_code: int): + logger.debug(f"CE143 request successful with code {reset_code}") + + def ce143_request_failure(self, reset_code: int): + logger.error(f"CE143 request failed with code {reset_code}") + + def ce133_initiate_submission(self, conn: JAMConnection, submission: MsgCE133WorkPackageSubmission, extrinsic: MsgCE133Extrinsic): + stream = conn.open_jam_stream(StreamWorkPackageSubmission, direction=StreamDirection.initiator) + logger.debug(f"CE133 initiating submission on stream id: {stream.stream_id}") + conn.send(stream.stream_id, stream.create_message(submission.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) + conn.send(stream.stream_id, stream.create_message(extrinsic.to_jam_bytes().to_bytes()), end_stream=True) + + def ce133_received_submission(self, stream: StreamWorkPackageSubmission, msg: MsgCE133WorkPackageSubmission): + logger.debug(f"CE133 received submission for core {msg.core_index}") + # TODO: process work package + + def ce133_received_extrinsic(self, stream: StreamWorkPackageSubmission, msg: MsgCE133Extrinsic): + logger.debug(f"CE133 received extrinsic data of length {len(msg.bytes_)}") + # TODO: process extrinsic, close stream if successful + # stream.acceptor_reset(0) + stream.conn.send(stream.stream_id, b'', end_stream=True) + + def ce133_submission_success(self, reset_code: int): + logger.debug(f"CE133 submission successful with code {reset_code}") + + def ce133_submission_failure(self, reset_code: int): + logger.error(f"CE133 submission failed with code {reset_code}") + + def ce135_initiate_distribution(self, conn: JAMConnection, msg: MsgCE135GuaranteedWorkReport): + stream = conn.open_jam_stream(StreamWorkReportDistribution, direction=StreamDirection.initiator) + logger.debug(f"CE135 initiating distribution on stream id: {stream.stream_id}") + conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + def ce135_received_report(self, stream: StreamWorkReportDistribution, msg: MsgCE135GuaranteedWorkReport): + logger.debug(f"CE135 received work report") + # TODO: process report + # stream.acceptor_reset(0) + stream.conn.send(stream.stream_id, b'', end_stream=True) + + def ce135_distribution_success(self, reset_code: int): + logger.debug(f"CE135 distribution successful with code {reset_code}") + + def ce135_distribution_failure(self, reset_code: int): + logger.error(f"CE135 distribution failed with code {reset_code}") + + def ce136_initiate_request(self, conn: JAMConnection, msg: MsgCE136HashRequest): + stream = conn.open_jam_stream(StreamWorkReportRequest, direction=StreamDirection.initiator) + logger.debug(f"CE136 initiating request on stream id: {stream.stream_id}") + conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + def ce136_received_request(self, stream: StreamWorkReportRequest, msg: MsgCE136HashRequest): + logger.debug(f"CE136 received request for hash {msg.hash.hex()}") + # TODO: lookup work report, send if available + report = MsgCE136WorkReport(report=b'') # placeholder + stream.conn.send(stream.stream_id, stream.create_message(report.to_jam_bytes().to_bytes()), end_stream=True) + + def ce136_received_report(self, stream: StreamWorkReportRequest, msg: MsgCE136WorkReport): + logger.debug(f"CE136 received work report of length {len(msg.report)}") + # TODO: process report + # stream.initiator_reset(0) + stream.conn.send(stream.stream_id, b'', end_stream=True) + + def ce136_request_success(self, reset_code: int): + logger.debug(f"CE136 request successful with code {reset_code}") + + def ce136_request_failure(self, reset_code: int): + logger.error(f"CE136 request failed with code {reset_code}") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py index 46e03d24..bb1a8fb6 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -20,8 +20,8 @@ class StreamType(Enum): CE139_SegmentShardRequest: int = 139 CE140_SegmentShardRequestJustification: int = 140 CE141_AssuranceDistribution: int = 141 - # 142 - # 143 + CE142_PreimageAnnouncement: int = 142 + CE143_PreimageRequest: int = 143 # 144 # 145 @@ -67,7 +67,7 @@ def create_message(self, payload: bytes, add_stream_type:bool=False): return (len(payload)).to_bytes(length=4, byteorder='little') + payload - def receive_data(self, data: bytes): + def receive_data(self, data: bytes, end_stream: bool = False): # Note: Parse bytes until stream data is empty: https://github.com/microsoft/msquic/discussions/2037 while len(data) > 0: @@ -78,6 +78,11 @@ def receive_data(self, data: bytes): data = data[4:] logger.debug(f"Received new message for stream {self.stream_id} with a msg length: {self._msg_len} ({len(data)} received)") # TODO: check message length > 0???!!!! + # Add max size check + if self._msg_len > 10000000: # Example max 10MB + logger.error(f"Message too large for stream {self.stream_id}") + self.reset(2) + return msg_complete = False if len(self._msg_buffer) + len(data) >= self._msg_len: @@ -100,9 +105,27 @@ def receive_data(self, data: bytes): self.initiator_message(self._msg_buffer) else: self.acceptor_message(self._msg_buffer) + except Exception as e: + logger.error(f"Error processing message on stream {self.stream_id}: {e}") + self.handle_error(str(e), 1) finally: self._reset_msg() + if end_stream: + logger.debug(f"Received FIN on stream {self.stream_id}") + self.peer_fin_received() + + def handle_error(self, error_msg: str, reset_code: int = 1): + logger.error(f"Stream {self.stream_id} error: {error_msg}") + self.reset(reset_code) + + def peer_fin_received(self): + """Handle peer's FIN. Override in subclass if needed.""" + # Default: send FIN back if not already closed + self.conn.send(self.stream_id, b'', end_stream=True) + # Call protocol success if applicable (override in streams) + pass + def reset(self, reset_code: int): if self.direction == StreamDirection.initiator: diff --git a/pyjamaz/transport/jamnp_s/stream_map.py b/pyjamaz/transport/jamnp_s/stream_map.py index 6a71b2c6..c249d099 100644 --- a/pyjamaz/transport/jamnp_s/stream_map.py +++ b/pyjamaz/transport/jamnp_s/stream_map.py @@ -1,17 +1,28 @@ from pyjamaz.transport.jamnp_s.stream_base import StreamType from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest +from pyjamaz.transport.jamnp_s.streams.stream_131 import StreamSafroleTicketDistributionStep1 +from pyjamaz.transport.jamnp_s.streams.stream_132 import StreamSafroleTicketDistributionStep2 +from pyjamaz.transport.jamnp_s.streams.stream_134 import StreamWorkPackageSharing +from pyjamaz.transport.jamnp_s.streams.stream_141 import StreamAssuranceDistribution +from pyjamaz.transport.jamnp_s.streams.stream_142 import StreamPreimageAnnouncement +from pyjamaz.transport.jamnp_s.streams.stream_143 import StreamPreimageRequest +from pyjamaz.transport.jamnp_s.streams.stream_133 import StreamWorkPackageSubmission +from pyjamaz.transport.jamnp_s.streams.stream_135 import StreamWorkReportDistribution +from pyjamaz.transport.jamnp_s.streams.stream_136 import StreamWorkReportRequest StreamLookup = { StreamType.UP0_BlockAnnouncement.value: StreamUP, StreamType.CE128_BlockRequest.value: StreamBlockRequest, - # StreamType.CE129_StateRequest.value: , - # StreamType.CE131_SafroleTicketDistributionStep1.value: , - # StreamType.CE132_SafroleTicketDistributionStep2.value: , - # StreamType.CE133_WorkPackageSubmission.value: , - # StreamType.CE134_WorkPackageSharing.value: , - # StreamType.CE135_WorkReportDistribution.value: , - # StreamType.CE136_WorkReportRequest.value: , + StreamType.CE131_SafroleTicketDistributionStep1.value: StreamSafroleTicketDistributionStep1, + StreamType.CE132_SafroleTicketDistributionStep2.value: StreamSafroleTicketDistributionStep2, + StreamType.CE134_WorkPackageSharing.value: StreamWorkPackageSharing, + StreamType.CE141_AssuranceDistribution.value: StreamAssuranceDistribution, + StreamType.CE142_PreimageAnnouncement.value: StreamPreimageAnnouncement, + StreamType.CE143_PreimageRequest.value: StreamPreimageRequest, + StreamType.CE133_WorkPackageSubmission.value: StreamWorkPackageSubmission, + StreamType.CE135_WorkReportDistribution.value: StreamWorkReportDistribution, + StreamType.CE136_WorkReportRequest.value: StreamWorkReportRequest, # StreamType.CE137_ShardDistribution.value: , # StreamType.CE138_AuditShardRequest.value: , # StreamType.CE139_SegmentShardRequest.value: , diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py index fb03b94a..247ccea7 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -44,6 +44,19 @@ def initiator_message(self, data: bytes): raise RuntimeError(f"Unexpected state {self.state}") + def initiator_reset(self, reset_code: int): + logger.debug(f"UP0 received reset code: {reset_code}") + self.protocol.up0_failure(reset_code) + super().initiator_reset(reset_code) + + def acceptor_reset(self, reset_code: int): + self.protocol.up0_failure(reset_code) + super().reset(reset_code) + def acceptor_message(self, data: bytes): # Note: in case of UP0, initiator and acceptor have symmetrical message flow self.initiator_message(data) + + def peer_fin_received(self): + logger.warning(f"Unexpected FIN on persistent UP0 stream {self.stream_id}") # UP streams shouldn't FIN + # Optionally reset or ignore diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py index 8ce964d4..a5c5126b 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -18,7 +18,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE128 received reset code: {reset_code}") - self.protocol.ce128_abort_block_request() + self.protocol.ce128_block_request_failure(reset_code) super().initiator_reset(reset_code) @@ -26,13 +26,23 @@ def initiator_message(self, data: bytes): if len(data) == 0: #int.from_bytes(b'\x00\x00\x00\x00') #TODO: send FIN? Let protocol know were finished? print(f'!!!!!!!!!ConnectionInitiator StreamBlockRequest.initiator_message received empty data') - self.initiator_reset(reset_code=0) + # self.initiator_reset(reset_code=0) # Only reset on error + self.handle_error("Empty response", 3) return logger.debug(f"CE128 initiated stream {self.stream_id} received block request response: {len(data)} bytes") req = MsgCE128BlockRequestResponse.from_jam_bytes(JamBytes(data)) self.protocol.ce128_received_block_request(self, req) + def peer_fin_received(self): + super().peer_fin_received() + self.protocol.ce128_abort_block_request() # Or success callback + + + def acceptor_reset(self, reset_code: int): + self.protocol.ce128_block_request_failure(reset_code) + super().reset(reset_code) + def acceptor_message(self, data: bytes): logger.debug(f"CE128 acceptor stream {self.stream_id} received block request") diff --git a/pyjamaz/transport/jamnp_s/streams/stream_131.py b/pyjamaz/transport/jamnp_s/streams/stream_131.py index 6ffe7ead..2a2c6bfe 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_131.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_131.py @@ -1,5 +1,12 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE131SafroleTicketDistribution from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + class StreamSafroleTicketDistributionStep1(Stream): @@ -8,14 +15,27 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type = StreamType.CE131_SafroleTicketDistributionStep1.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - def initiate_state_request(self, header_hash, direction, max_blocks): - pass + + def initiator_reset(self, reset_code: int): + logger.debug(f"CE131 received reset code: {reset_code}") + self.protocol.ce131_ticket_distribution_failure(reset_code) + super().initiator_reset(reset_code) def initiator_message(self, data: bytes): - # Note: Sends message to one specific deterministic validator - pass + if len(data) == 0: + logger.debug(f"CE131 initiator received empty data") + return + + logger.warning(f"Unexpected data in CE131 initiator: {len(data)} bytes") + self.handle_error("Unexpected data", 1) def acceptor_message(self, data: bytes): - pass \ No newline at end of file + logger.debug(f"CE131 acceptor stream {self.stream_id} received ticket") + msg = MsgCE131SafroleTicketDistribution.from_jam_bytes(JamBytes(data)) + self.protocol.ce131_received_ticket(self, msg) + + def peer_fin_received(self): + super().peer_fin_received() + self.protocol.ce131_ticket_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_132.py b/pyjamaz/transport/jamnp_s/streams/stream_132.py index d87bd79c..014446fc 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_132.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_132.py @@ -1,5 +1,12 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE132SafroleTicketDistribution from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + class StreamSafroleTicketDistributionStep2(Stream): @@ -8,14 +15,27 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type = StreamType.CE132_SafroleTicketDistributionStep2.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - def initiate_state_request(self, header_hash, direction, max_blocks): - # Note: Sends message to all current validators - pass + + def initiator_reset(self, reset_code: int): + logger.debug(f"CE132 received reset code: {reset_code}") + self.protocol.ce132_ticket_distribution_failure(reset_code) + super().initiator_reset(reset_code) def initiator_message(self, data: bytes): - pass + if len(data) == 0: + logger.debug(f"CE132 initiator received empty data") + return + + logger.warning(f"Unexpected data in CE132 initiator: {len(data)} bytes") + self.handle_error("Unexpected data", 1) def acceptor_message(self, data: bytes): - pass \ No newline at end of file + logger.debug(f"CE132 acceptor stream {self.stream_id} received ticket") + msg = MsgCE132SafroleTicketDistribution.from_jam_bytes(JamBytes(data)) + self.protocol.ce132_received_ticket(self, msg) + + def peer_fin_received(self): + super().peer_fin_received() + self.protocol.ce132_ticket_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_133.py b/pyjamaz/transport/jamnp_s/streams/stream_133.py index 05628295..613b8f3d 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_133.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_133.py @@ -1,5 +1,12 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE133WorkPackageSubmission, MsgCE133Extrinsic from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + class StreamWorkPackageSubmission(Stream): @@ -7,15 +14,34 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) self.stream_type = StreamType.CE133_WorkPackageSubmission.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') + self.received_wp = False - - def initiate_state_request(self, header_hash, direction, max_blocks): - pass - + def initiator_reset(self, reset_code: int): + logger.debug(f"CE133 received reset code: {reset_code}") + self.protocol.ce133_submission_failure(reset_code) + super().initiator_reset(reset_code) def initiator_message(self, data: bytes): - pass + if len(data) == 0: + return + logger.warning(f"Unexpected data in CE133 initiator: {len(data)} bytes") + self.handle_error("Unexpected data", 1) + def acceptor_reset(self, reset_code: int): + self.protocol.ce133_submission_failure(reset_code) + super().reset(reset_code) def acceptor_message(self, data: bytes): - pass \ No newline at end of file + if not self.received_wp: + logger.debug(f"CE133 acceptor received work package") + msg = MsgCE133WorkPackageSubmission.from_jam_bytes(JamBytes(data)) + self.protocol.ce133_received_submission(self, msg) + self.received_wp = True + else: + logger.debug(f"CE133 acceptor received extrinsic data") + msg = MsgCE133Extrinsic.from_jam_bytes(JamBytes(data)) + self.protocol.ce133_received_extrinsic(self, msg) + + def peer_fin_received(self): + super().peer_fin_received() + self.protocol.ce133_submission_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_134.py b/pyjamaz/transport/jamnp_s/streams/stream_134.py index c2394d38..ed6f5262 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_134.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_134.py @@ -1,5 +1,12 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE134WorkPackageSharing, MsgCE134WorkPackageBundle, MsgCE134RefineResponse from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + class StreamWorkPackageSharing(Stream): @@ -7,15 +14,47 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) self.stream_type = StreamType.CE134_WorkPackageSharing.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') + self.received_mappings = False - - def initiate_state_request(self, header_hash, direction, max_blocks): - pass - + def initiator_reset(self, reset_code: int): + logger.debug(f"CE134 received reset code: {reset_code}") + self.protocol.ce134_sharing_failure(reset_code) + super().initiator_reset(reset_code) def initiator_message(self, data: bytes): - pass + if not self.received_mappings: + logger.debug(f"CE134 initiator received refine response") + msg = MsgCE134RefineResponse.from_jam_bytes(JamBytes(data)) + self.protocol.ce134_received_refine_response(self, msg) + self.received_mappings = True + else: + logger.warning(f"Unexpected data in CE134 initiator after mappings: {len(data)} bytes") + self.handle_error("Unexpected data after mappings", 1) + + def acceptor_reset(self, reset_code: int): + self.protocol.ce134_sharing_failure(reset_code) + super().reset(reset_code) + def initiator_message(self, data: bytes): + if not self.received_mappings: + logger.debug(f"CE134 initiator received refine response") + msg = MsgCE134RefineResponse.from_jam_bytes(JamBytes(data)) + self.protocol.ce134_received_refine_response(self, msg) + self.received_mappings = True + else: + logger.warning(f"Unexpected data in CE134 initiator after mappings: {len(data)} bytes") def acceptor_message(self, data: bytes): - pass \ No newline at end of file + if not self.received_mappings: + logger.debug(f"CE134 acceptor received mappings") + msg = MsgCE134WorkPackageSharing.from_jam_bytes(JamBytes(data)) + self.protocol.ce134_received_sharing(self, msg) + self.received_mappings = True + else: + logger.debug(f"CE134 acceptor received bundle") + msg = MsgCE134WorkPackageBundle.from_jam_bytes(JamBytes(data)) + self.protocol.ce134_received_bundle(self, msg) + + def peer_fin_received(self): + super().peer_fin_received() + self.protocol.ce134_sharing_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_135.py b/pyjamaz/transport/jamnp_s/streams/stream_135.py index 54d9e387..0c36df95 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_135.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_135.py @@ -1,5 +1,12 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE135GuaranteedWorkReport from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + class StreamWorkReportDistribution(Stream): @@ -8,13 +15,26 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type = StreamType.CE135_WorkReportDistribution.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - def initiate_state_request(self, header_hash, direction, max_blocks): - pass - + def initiator_reset(self, reset_code: int): + logger.debug(f"CE135 received reset code: {reset_code}") + self.protocol.ce135_distribution_failure(reset_code) + super().initiator_reset(reset_code) def initiator_message(self, data: bytes): - pass + if len(data) == 0: + return + logger.warning(f"Unexpected data in CE135 initiator: {len(data)} bytes") + self.handle_error("Unexpected data", 1) + def acceptor_reset(self, reset_code: int): + self.protocol.ce135_distribution_failure(reset_code) + super().reset(reset_code) def acceptor_message(self, data: bytes): - pass \ No newline at end of file + logger.debug(f"CE135 acceptor received work report") + msg = MsgCE135GuaranteedWorkReport.from_jam_bytes(JamBytes(data)) + self.protocol.ce135_received_report(self, msg) + + def peer_fin_received(self): + super().peer_fin_received() + self.protocol.ce135_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_136.py b/pyjamaz/transport/jamnp_s/streams/stream_136.py index e5c3d0de..add309fc 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_136.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_136.py @@ -1,5 +1,12 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE136HashRequest, MsgCE136WorkReport from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + class StreamWorkReportRequest(Stream): @@ -8,14 +15,21 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type = StreamType.CE136_WorkReportRequest.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - def initiate_state_request(self, header_hash, direction, max_blocks): - pass - + def initiator_reset(self, reset_code: int): + logger.debug(f"CE136 received reset code: {reset_code}") + self.protocol.ce136_request_failure(reset_code) + super().initiator_reset(reset_code) def initiator_message(self, data: bytes): - pass - + logger.debug(f"CE136 initiator received work report") + msg = MsgCE136WorkReport.from_jam_bytes(JamBytes(data)) + self.protocol.ce136_received_report(self, msg) def acceptor_message(self, data: bytes): - pass \ No newline at end of file + logger.debug(f"CE136 acceptor received request") + msg = MsgCE136HashRequest.from_jam_bytes(JamBytes(data)) + self.protocol.ce136_received_request(self, msg) + + def peer_fin_received(self): + super().peer_fin_received() + self.protocol.ce136_request_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_141.py b/pyjamaz/transport/jamnp_s/streams/stream_141.py index 268374df..7035933e 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_141.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_141.py @@ -1,5 +1,12 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE141Assurance from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + class StreamAssuranceDistribution(Stream): @@ -8,14 +15,26 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type = StreamType.CE141_AssuranceDistribution.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - def initiate_state_request(self, header_hash, direction, max_blocks): - pass - + def initiator_reset(self, reset_code: int): + logger.debug(f"CE141 received reset code: {reset_code}") + self.protocol.ce141_distribution_failure(reset_code) + super().initiator_reset(reset_code) def initiator_message(self, data: bytes): - pass + if len(data) == 0: + return + logger.warning(f"Unexpected data in CE141 initiator: {len(data)} bytes") + self.handle_error("Unexpected data", 1) + def acceptor_reset(self, reset_code: int): + self.protocol.ce141_distribution_failure(reset_code) + super().reset(reset_code) def acceptor_message(self, data: bytes): - pass \ No newline at end of file + logger.debug(f"CE141 acceptor received assurance") + msg = MsgCE141Assurance.from_jam_bytes(JamBytes(data)) + self.protocol.ce141_received_assurance(self, msg) + + def peer_fin_received(self): + super().peer_fin_received() + self.protocol.ce141_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_142.py b/pyjamaz/transport/jamnp_s/streams/stream_142.py new file mode 100644 index 00000000..16a35e8b --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_142.py @@ -0,0 +1,40 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE142PreimageAnnouncement +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class StreamPreimageAnnouncement(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE142_PreimageAnnouncement.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') + + def initiator_reset(self, reset_code: int): + logger.debug(f"CE142 received reset code: {reset_code}") + self.protocol.ce142_announcement_failure(reset_code) + super().initiator_reset(reset_code) + + def initiator_message(self, data: bytes): + if len(data) == 0: + return + logger.warning(f"Unexpected data in CE142 initiator: {len(data)} bytes") + self.handle_error("Unexpected data", 1) + + def acceptor_reset(self, reset_code: int): + self.protocol.ce142_announcement_failure(reset_code) + super().reset(reset_code) + + def acceptor_message(self, data: bytes): + logger.debug(f"CE142 acceptor received preimage announcement") + msg = MsgCE142PreimageAnnouncement.from_jam_bytes(JamBytes(data)) + self.protocol.ce142_received_announcement(self, msg) + + def peer_fin_received(self): + super().peer_fin_received() + self.protocol.ce142_announcement_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_143.py b/pyjamaz/transport/jamnp_s/streams/stream_143.py new file mode 100644 index 00000000..da77c8a7 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_143.py @@ -0,0 +1,35 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE143HashRequest, MsgCE143Preimage +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class StreamPreimageRequest(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE143_PreimageRequest.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') + + def initiator_reset(self, reset_code: int): + logger.debug(f"CE143 received reset code: {reset_code}") + self.protocol.ce143_request_failure(reset_code) + super().initiator_reset(reset_code) + + def initiator_message(self, data: bytes): + logger.debug(f"CE143 initiator received preimage") + msg = MsgCE143Preimage.from_jam_bytes(JamBytes(data)) + self.protocol.ce143_received_preimage(self, msg) + + def acceptor_message(self, data: bytes): + logger.debug(f"CE143 acceptor received preimage request") + msg = MsgCE143HashRequest.from_jam_bytes(JamBytes(data)) + self.protocol.ce143_received_request(self, msg) + + def peer_fin_received(self): + super().peer_fin_received() + self.protocol.ce143_request_success(0) \ No newline at end of file From 9aef941b8404ffaeb85429fb92b2dd44179992bc Mon Sep 17 00:00:00 2001 From: matthijsb Date: Wed, 16 Jul 2025 17:28:51 +0200 Subject: [PATCH 22/51] - JAMNP wip --- pyjamaz/transport/jamnp_s/connection.py | 12 +- pyjamaz/transport/jamnp_s/message_types.py | 152 +++--------------- pyjamaz/transport/jamnp_s/protocol.py | 66 +++++++- pyjamaz/transport/jamnp_s/stream_base.py | 132 ++++++++------- pyjamaz/transport/jamnp_s/streams/stream_0.py | 6 +- .../transport/jamnp_s/streams/stream_128.py | 12 +- .../transport/jamnp_s/streams/stream_131.py | 9 +- .../transport/jamnp_s/streams/stream_132.py | 9 +- .../transport/jamnp_s/streams/stream_133.py | 11 +- .../transport/jamnp_s/streams/stream_134.py | 16 +- .../transport/jamnp_s/streams/stream_135.py | 11 +- .../transport/jamnp_s/streams/stream_136.py | 8 +- .../transport/jamnp_s/streams/stream_141.py | 11 +- .../transport/jamnp_s/streams/stream_142.py | 9 +- .../transport/jamnp_s/streams/stream_143.py | 8 +- 15 files changed, 227 insertions(+), 245 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index a7467fb8..2483808b 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -39,10 +39,14 @@ def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_i return self.streams[stream_id] - def close_jam_stream(self, stream: Stream, reason:int=0): - logger.info(f"QUIC closing JAM stream {stream.stream_type} {stream.stream_id} for {stream.direction}") - self._quic.reset_stream(stream.stream_id, error_code=reason) - del self.streams[stream.stream_id] + def close_jam_stream(self, stream: Stream, reason:int=0, clean_close: bool = False): + logger.info(f"QUIC closing JAM stream {stream.stream_type} {stream.stream_id} for {stream.direction} (clean: {clean_close})") + if clean_close: + self.send(stream.stream_id, b'', end_stream=True) + else: + self._quic.reset_stream(stream.stream_id, error_code=reason) + if stream.stream_id in self.streams: + del self.streams[stream.stream_id] def send(self, stream_id: int, data: bytes, end_stream=False): diff --git a/pyjamaz/transport/jamnp_s/message_types.py b/pyjamaz/transport/jamnp_s/message_types.py index 150fd603..2b3d8c01 100644 --- a/pyjamaz/transport/jamnp_s/message_types.py +++ b/pyjamaz/transport/jamnp_s/message_types.py @@ -46,14 +46,12 @@ class MsgUP0Leaf(Serializable): header_hash: bytes = field(metadata={'codec': H256}) timeslot: int = field(metadata={'codec': U32}) - @dataclass class MsgUP0Handshake(Serializable): header_hash: bytes = field(metadata={'codec': H256}) timeslot: int = field(metadata={'codec': U32}) leafs: List[MsgUP0Leaf] = field(metadata={'codec': Vec(MsgUP0Leaf.to_codec_def())}) - @dataclass class MsgUP0Announcement(Serializable): header: Header = field(metadata={'codec': Header.to_codec_def()}) @@ -65,14 +63,12 @@ class MsgCE128BlockRequestDirection(Enum): ASC = 0 DESC = 1 - @dataclass class MsgCE128BlockRequest(Serializable): header_hash: bytes = field(metadata={'codec': H256}) direction: int = field(metadata={'codec': U8}) max_blocks: int = field(metadata={'codec': U32}) - @dataclass class MsgCE128BlockRequestResponse(Serializable): blocks: List[Block] = field(metadata={'codec': ImplicitVec(Block.to_codec_def())}) @@ -103,7 +99,6 @@ class MsgCE131SafroleTicketDistribution(Serializable): class MsgCE132SafroleTicket(MsgCE131SafroleTicket): pass - @dataclass class MsgCE132SafroleTicketDistribution(MsgCE131SafroleTicketDistribution): pass @@ -114,6 +109,15 @@ class MsgCE133WorkPackageSubmission(Serializable): core_index: int = field(metadata={'codec': U32}) work_package: WorkPackage = field(metadata={'codec': WorkPackage.to_codec_def()}) +@dataclass +class MsgCE133WorkPackageSubmission(Serializable): + core_index: int = field(metadata={'codec': U32}) + work_package: WorkPackage = field(metadata={'codec': WorkPackage.to_codec_def()}) + +@dataclass +class MsgCE133Extrinsic(Serializable): + bytes_: bytes = field(metadata={'codec': Vec(U8)}) + @dataclass class MsgCE134WorkPackageSharing(Serializable): @@ -137,148 +141,40 @@ class MsgCE135GuaranteedWorkReport(Guarantee): pass -# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -# -# # ───────────────────────── CE-136 / 143 ──────────────────────── -# @dataclass -# class HashRequest(Serializable): # CE-136 (work-report) & CE-143 (preimage) -# hash: bytes = field(metadata={'codec': H256}) -# -# @dataclass -# class WorkReportEnvelope(Serializable): -# work_report: 'WorkReport' = field(metadata={'codec': WorkReport.to_codec_def()}) -# -# @dataclass -# class Preimage(Serializable): -# bytes_: bytes = field(metadata={'codec': ImplicitVec(U8)}) -# -# # ───────────────────────── CE-137 / 138 / 139 / 140 ─────────── -# @dataclass -# class ShardRequestHead(Serializable): # CE-137 & CE-138 share first message -# erasure_root: bytes = field(metadata={'codec': H256}) -# shard_index: int = field(metadata={'codec': U32}) # spec uses u32 -# -# @dataclass -# class SegmentShardRequestHead(Serializable): # CE-139 / 140 -# erasure_root: bytes = field(metadata={'codec': H256}) -# shard_index: int = field(metadata={'codec': U32}) -# segment_indices: List[int] = field( -# metadata={'codec': ImplicitVec(U16)} # u16 each -# ) -# -# @dataclass -# class JustificationNode(Serializable): # 0,1,2 tagged union – bytestream -# raw: bytes = field(metadata={'codec': ImplicitVec(U8)}) -# -# # reply payloads for 137-140 are raw blobs; dataclasses optional -# -# # ───────────────────────── CE-141 ────────────────────────────── -# @dataclass -# class Assurance(Serializable): -# header_hash: bytes = field(metadata={'codec': H256}) # anchor -# bitfield: bytes = field(metadata={'codec': ImplicitVec(U8)}) # len = ceil(C/8) -# signature: bytes = field(metadata={'codec': Array(U8, 64)}) -# -# # ───────────────────────── CE-142 ────────────────────────────── -# @dataclass -# class PreimageAnnouncement(Serializable): -# service_id: int = field(metadata={'codec': U32}) -# hash: bytes = field(metadata={'codec': H256}) -# preimage_length: int = field(metadata={'codec': U32}) -# -# # ───────────────────────── CE-144 ────────────────────────────── -# @dataclass -# class CoreWRPair(Serializable): -# core_index: int = field(metadata={'codec': U32}) -# wr_hash: bytes = field(metadata={'codec': H256}) -# -# @dataclass -# class NoShow(Serializable): -# validator_index: int = field(metadata={'codec': U32}) -# announcement: bytes = field(metadata={'codec': ImplicitVec(U8)}) # raw bytes -# -# @dataclass -# class TrancheEvidenceFirst(Serializable): -# signature: bytes = field(metadata={'codec': Array(U8, 96)}) # Bandersnatch -# -# @dataclass -# class TrancheEvidenceSubsequent(Serializable): -# signature: bytes = field(metadata={'codec': Array(U8, 96)}) -# no_shows: List[NoShow] = field(metadata={'codec': ImplicitVec(NoShow.to_codec_def())}) -# -# @dataclass -# class AuditAnnouncement(Serializable): -# header_hash: bytes = field(metadata={'codec': H256}) -# tranche: int = field(metadata={'codec': U8}) -# announcement: List[CoreWRPair] = field(metadata={'codec': ImplicitVec(CoreWRPair.to_codec_def())}) -# evidence: bytes = field( # discriminated union; raw bytes easiest -# metadata={'codec': ImplicitVec(U8)} -# ) -# -# # ───────────────────────── CE-145 ────────────────────────────── -# class JudgmentValidity(Enum): -# INVALID = 0 -# VALID = 1 -# -# @dataclass -# class JudgmentPublication(Serializable): -# epoch_index: int = field(metadata={'codec': U32}) -# validator_index: int = field(metadata={'codec': U32}) -# validity: int = field(metadata={'codec': U8}) # enum above -# wr_hash: bytes = field(metadata={'codec': H256}) -# signature: bytes = field(metadata={'codec': Array(U8, 64)}) -# -# # ----------------------------------------------------------------- -# # Helper type used above (validator sig in CE-135) -# @dataclass -# class ValidatorSig(Serializable): -# validator_index: int = field(metadata={'codec': U32}) -# signature: bytes = field(metadata={'codec': Array(U8, 64)}) - - -# ───────────────────────── CE-143 ────────────────────────────── @dataclass -class MsgCE143HashRequest(Serializable): +class MsgCE135GuaranteedWorkReport(Guarantee): + pass + + +@dataclass +class MsgCE136HashRequest(Serializable): hash: bytes = field(metadata={'codec': H256}) + @dataclass -class MsgCE143Preimage(Serializable): - bytes_: bytes = field(metadata={'codec': Vec(U8)}) +class MsgCE136WorkReport(Serializable): + report: bytes = field(metadata={'codec': Vec(U8)}) + -# ───────────────────────── CE-141 ────────────────────────────── @dataclass class MsgCE141Assurance(Serializable): header_hash: bytes = field(metadata={'codec': H256}) # anchor bitfield: bytes = field(metadata={'codec': Vec(U8)}) # len = ceil(C/8) signature: bytes = field(metadata={'codec': H512}) -# ───────────────────────── CE-142 ────────────────────────────── + @dataclass class MsgCE142PreimageAnnouncement(Serializable): service_id: int = field(metadata={'codec': U32}) hash: bytes = field(metadata={'codec': H256}) preimage_length: int = field(metadata={'codec': U32}) -# ───────────────────────── CE-133 ────────────────────────────── -@dataclass -class MsgCE133WorkPackageSubmission(Serializable): - core_index: int = field(metadata={'codec': U32}) - work_package: WorkPackage = field(metadata={'codec': WorkPackage.to_codec_def()}) - -@dataclass -class MsgCE133Extrinsic(Serializable): - bytes_: bytes = field(metadata={'codec': Vec(U8)}) - -# ───────────────────────── CE-135 ────────────────────────────── -@dataclass -class MsgCE135GuaranteedWorkReport(Guarantee): - pass -# ───────────────────────── CE-136 ────────────────────────────── @dataclass -class MsgCE136HashRequest(Serializable): +class MsgCE143HashRequest(Serializable): hash: bytes = field(metadata={'codec': H256}) + @dataclass -class MsgCE136WorkReport(Serializable): - report: bytes = field(metadata={'codec': Vec(U8)}) \ No newline at end of file +class MsgCE143Preimage(Serializable): + bytes_: bytes = field(metadata={'codec': Vec(U8)}) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 14063d66..c3b8c36e 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -78,6 +78,7 @@ def pop(self, label: bytes) -> Optional[SessionTicket]: class JAMNPS(ProtocolType): + MAX_MESSAGE_SIZE = 10000000 PROTOCOL_NAME = "jamnp-s/0/{}" @@ -349,6 +350,7 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 def ce128_abort_block_request(self): logger.debug(f"Finished block request, start parsing import queue {len(self.app._import_queue)}") # Note: could happen during a block_request "session" + #TODO: or do we want to clear the queue? asyncio.create_task( self.app.process_import_queue( on_success=MESSAGE_TYPES.CE128_SUCCESS, @@ -361,14 +363,17 @@ async def ce128_processed_block_request(self, *args, **kwargs): #TODO: add reason, origin/connection etc self.state_requesting_blocks = False + def up0_failure(self, reset_code: int): logger.error(f"UP0 failure with code {reset_code}") # TODO: handle UP0 reset, e.g., reconnect + def ce128_block_request_failure(self, reset_code: int): logger.error(f"CE128 block request failed with code {reset_code}") self.state_requesting_blocks = False # Reset state on failure + def ce131_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE131SafroleTicketDistribution): stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep1, direction=StreamDirection.initiator) logger.debug(f"CE131 initiating ticket distribution on stream id: {stream.stream_id}") @@ -378,20 +383,26 @@ def ce131_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE131S end_stream=True ) + def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, msg: MsgCE131SafroleTicketDistribution): logger.debug(f"CE131 received ticket for epoch {msg.epoch_index}") - # TODO: verify proof, check if proxy, forward via CE132 if valid - # stream.acceptor_reset(0) + # TODO: verify proof, check if proxy + if not self.verify_ticket_proof(msg): # Example validation + raise ValueError("Invalid ticket proof") + # forward via CE132 if valid stream.conn.send(stream.stream_id, b'', end_stream=True) + def ce131_ticket_distribution_success(self, reset_code: int): logger.debug(f"CE131 ticket distribution successful with code {reset_code}") # TODO: handle success, perhaps update state + def ce131_ticket_distribution_failure(self, reset_code: int): logger.error(f"CE131 ticket distribution failed with code {reset_code}") # TODO: handle failure, e.g., retry or log + def ce132_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE132SafroleTicketDistribution): stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep2, direction=StreamDirection.initiator) logger.debug(f"CE132 initiating ticket distribution on stream id: {stream.stream_id}") @@ -401,163 +412,210 @@ def ce132_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE132S end_stream=True ) + def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, msg: MsgCE132SafroleTicketDistribution): logger.debug(f"CE132 received ticket for epoch {msg.epoch_index}") # TODO: verify and process ticket # stream.acceptor_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) + def ce132_ticket_distribution_success(self, reset_code: int): logger.debug(f"CE132 ticket distribution successful with code {reset_code}") # TODO: handle success, perhaps update state + def ce132_ticket_distribution_failure(self, reset_code: int): logger.error(f"CE132 ticket distribution failed with code {reset_code}") # TODO: handle failure + def ce134_initiate_sharing(self, conn: JAMConnection, sharing: MsgCE134WorkPackageSharing, bundle: MsgCE134WorkPackageBundle): stream = conn.open_jam_stream(StreamWorkPackageSharing, direction=StreamDirection.initiator) logger.debug(f"CE134 initiating sharing on stream id: {stream.stream_id}") conn.send(stream.stream_id, stream.create_message(sharing.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) conn.send(stream.stream_id, stream.create_message(bundle.to_jam_bytes().to_bytes()), end_stream=True) + def ce134_received_sharing(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageSharing): logger.debug(f"CE134 received sharing for core {msg.core_index}") # TODO: process mappings + def ce134_received_bundle(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageBundle): logger.debug(f"CE134 received bundle") - # TODO: verify, refine, respond with MsgCE134RefineResponse + # TODO: verify, refine + if not self.verify_bundle(msg): + raise ValueError("Invalid bundle") response = MsgCE134RefineResponse(report_hash=bytes(32), signature=bytes(64)) stream.conn.send(stream.stream_id, stream.create_message(response.to_jam_bytes().to_bytes()), end_stream=True) + def ce134_received_refine_response(self, stream: StreamWorkPackageSharing, msg: MsgCE134RefineResponse): logger.debug(f"CE134 received refine response") # TODO: process response # stream.initiator_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) + def ce134_sharing_success(self, reset_code: int): logger.debug(f"CE134 sharing successful with code {reset_code}") + def ce134_sharing_failure(self, reset_code: int): logger.error(f"CE134 sharing failed with code {reset_code}") + def ce141_initiate_distribution(self, conn: JAMConnection, msg: MsgCE141Assurance): stream = conn.open_jam_stream(StreamAssuranceDistribution, direction=StreamDirection.initiator) logger.debug(f"CE141 initiating distribution on stream id: {stream.stream_id}") conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + def ce141_received_assurance(self, stream: StreamAssuranceDistribution, msg: MsgCE141Assurance): logger.debug(f"CE141 received assurance") # TODO: process assurance # stream.acceptor_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) + def ce141_distribution_success(self, reset_code: int): logger.debug(f"CE141 distribution successful with code {reset_code}") + def ce141_distribution_failure(self, reset_code: int): logger.error(f"CE141 distribution failed with code {reset_code}") + def ce142_initiate_announcement(self, conn: JAMConnection, msg: MsgCE142PreimageAnnouncement): stream = conn.open_jam_stream(StreamPreimageAnnouncement, direction=StreamDirection.initiator) logger.debug(f"CE142 initiating announcement on stream id: {stream.stream_id}") conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + def ce142_received_announcement(self, stream: StreamPreimageAnnouncement, msg: MsgCE142PreimageAnnouncement): logger.debug(f"CE142 received announcement for hash {msg.hash.hex()}") # TODO: check if needed, request via CE143 if so # stream.acceptor_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) + def ce142_announcement_success(self, reset_code: int): logger.debug(f"CE142 announcement successful with code {reset_code}") + def ce142_announcement_failure(self, reset_code: int): logger.error(f"CE142 announcement failed with code {reset_code}") + def ce143_initiate_request(self, conn: JAMConnection, msg: MsgCE143HashRequest): stream = conn.open_jam_stream(StreamPreimageRequest, direction=StreamDirection.initiator) logger.debug(f"CE143 initiating request on stream id: {stream.stream_id}") conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + def ce143_received_request(self, stream: StreamPreimageRequest, msg: MsgCE143HashRequest): logger.debug(f"CE143 received request for hash {msg.hash.hex()}") # TODO: lookup preimage, send if available preimage = MsgCE143Preimage(bytes_=b'') # placeholder stream.conn.send(stream.stream_id, stream.create_message(preimage.to_jam_bytes().to_bytes()), end_stream=True) + def ce143_received_preimage(self, stream: StreamPreimageRequest, msg: MsgCE143Preimage): logger.debug(f"CE143 received preimage of length {len(msg.bytes_)}") # TODO: process preimage # stream.initiator_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) + def ce143_request_success(self, reset_code: int): logger.debug(f"CE143 request successful with code {reset_code}") + def ce143_request_failure(self, reset_code: int): logger.error(f"CE143 request failed with code {reset_code}") + def ce133_initiate_submission(self, conn: JAMConnection, submission: MsgCE133WorkPackageSubmission, extrinsic: MsgCE133Extrinsic): stream = conn.open_jam_stream(StreamWorkPackageSubmission, direction=StreamDirection.initiator) logger.debug(f"CE133 initiating submission on stream id: {stream.stream_id}") conn.send(stream.stream_id, stream.create_message(submission.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) conn.send(stream.stream_id, stream.create_message(extrinsic.to_jam_bytes().to_bytes()), end_stream=True) + def ce133_received_submission(self, stream: StreamWorkPackageSubmission, msg: MsgCE133WorkPackageSubmission): logger.debug(f"CE133 received submission for core {msg.core_index}") # TODO: process work package + def ce133_received_extrinsic(self, stream: StreamWorkPackageSubmission, msg: MsgCE133Extrinsic): logger.debug(f"CE133 received extrinsic data of length {len(msg.bytes_)}") # TODO: process extrinsic, close stream if successful # stream.acceptor_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) + def ce133_submission_success(self, reset_code: int): logger.debug(f"CE133 submission successful with code {reset_code}") + def ce133_submission_failure(self, reset_code: int): logger.error(f"CE133 submission failed with code {reset_code}") + def ce135_initiate_distribution(self, conn: JAMConnection, msg: MsgCE135GuaranteedWorkReport): stream = conn.open_jam_stream(StreamWorkReportDistribution, direction=StreamDirection.initiator) logger.debug(f"CE135 initiating distribution on stream id: {stream.stream_id}") conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + def ce135_received_report(self, stream: StreamWorkReportDistribution, msg: MsgCE135GuaranteedWorkReport): logger.debug(f"CE135 received work report") # TODO: process report # stream.acceptor_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) + def ce135_distribution_success(self, reset_code: int): logger.debug(f"CE135 distribution successful with code {reset_code}") + def ce135_distribution_failure(self, reset_code: int): logger.error(f"CE135 distribution failed with code {reset_code}") + def ce136_initiate_request(self, conn: JAMConnection, msg: MsgCE136HashRequest): stream = conn.open_jam_stream(StreamWorkReportRequest, direction=StreamDirection.initiator) logger.debug(f"CE136 initiating request on stream id: {stream.stream_id}") conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + def ce136_received_request(self, stream: StreamWorkReportRequest, msg: MsgCE136HashRequest): logger.debug(f"CE136 received request for hash {msg.hash.hex()}") # TODO: lookup work report, send if available report = MsgCE136WorkReport(report=b'') # placeholder stream.conn.send(stream.stream_id, stream.create_message(report.to_jam_bytes().to_bytes()), end_stream=True) + def ce136_received_report(self, stream: StreamWorkReportRequest, msg: MsgCE136WorkReport): logger.debug(f"CE136 received work report of length {len(msg.report)}") # TODO: process report # stream.initiator_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) + def ce136_request_success(self, reset_code: int): logger.debug(f"CE136 request successful with code {reset_code}") + def ce136_request_failure(self, reset_code: int): - logger.error(f"CE136 request failed with code {reset_code}") \ No newline at end of file + logger.error(f"CE136 request failed with code {reset_code}") + + + # Add placeholder verify methods (implement as needed) + def verify_ticket_proof(self, msg): + return True # TODO: actual verification + + + def verify_bundle(self, msg): + return True # TODO: actual verification \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py index bb1a8fb6..bfc8e895 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -22,7 +22,7 @@ class StreamType(Enum): CE141_AssuranceDistribution: int = 141 CE142_PreimageAnnouncement: int = 142 CE143_PreimageRequest: int = 143 - # 144 + CE144_PreimageAnnouncement: int = 144 # 145 @@ -33,6 +33,13 @@ class StreamDirection(Enum): class Stream: + # Standard reset codes + ERROR_GENERAL = 1 + ERROR_INVALID_MESSAGE = 2 + ERROR_INVALID_SIZE = 3 + ERROR_TOO_LARGE = 4 + ERROR_VALIDATION_FAILED = 5 + #TODO: typings on connection def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_id = stream_id @@ -68,63 +75,73 @@ def create_message(self, payload: bytes, add_stream_type:bool=False): def receive_data(self, data: bytes, end_stream: bool = False): + try: + # Note: Parse bytes until stream data is empty: https://github.com/microsoft/msquic/discussions/2037 + while len(data) > 0: + + if self._msg_len == -1: + # byte_data = bytes(event.data) + self._msg_len = int.from_bytes(data[0:4], byteorder='little') + data = data[4:] + logger.debug(f"Received new message for stream {self.stream_id} with a msg length: {self._msg_len} ({len(data)} received)") + # TODO: check message length > 0???!!!! + if self._msg_len <= 0: + logger.error(f"Message size <= 0 for stream {self.stream_id} {self._msg_len}") + self.reset(self.ERROR_INVALID_SIZE) + + # Add max size check + if self._msg_len > self.protocol.MAX_MESSAGE_SIZE: + logger.error(f"Message too large for stream {self.stream_id}") + self.reset(self.ERROR_TOO_LARGE) + return + + msg_complete = False + if len(self._msg_buffer) + len(data) >= self._msg_len: + # If we received a full message (or more than 1 message) + end_offset = self._msg_len - len(self._msg_buffer) + self._msg_buffer += data[:end_offset] + data = data[end_offset:] # Note: this packet could contain start of a new message + msg_complete = True + else: + # Otherwise append only, we expect more data to finish this message + self._msg_buffer += data + logger.debug(f"Appending to message for stream {self.stream_id} ({self._msg_buffer} bytes of {self._msg_len})") + data = [] # Note: no new packet can be present in this data + + # If we assembled a new message, parse it + if msg_complete: + logger.debug(f"Message complete for stream {self.stream_id}") + try: + if self.direction == StreamDirection.initiator: + self.initiator_message(self._msg_buffer) + else: + self.acceptor_message(self._msg_buffer) + except Exception as e: + self.handle_error(e) + finally: + self._reset_msg() + + if end_stream: + logger.debug(f"Received FIN on stream {self.stream_id}") + self.handle_fin() + + except Exception as e: + self.handle_error(e) + + + def handle_error(self, exc: Exception): + if isinstance(exc, ValueError): + code = self.ERROR_INVALID_MESSAGE + elif isinstance(exc, MemoryError): + code = self.ERROR_TOO_LARGE + else: + code = self.ERROR_GENERAL + logger.error(f"Stream {self.stream_id} error: {exc}") + self.reset(code) + - # Note: Parse bytes until stream data is empty: https://github.com/microsoft/msquic/discussions/2037 - while len(data) > 0: - - if self._msg_len == -1: - # byte_data = bytes(event.data) - self._msg_len = int.from_bytes(data[0:4], byteorder='little') - data = data[4:] - logger.debug(f"Received new message for stream {self.stream_id} with a msg length: {self._msg_len} ({len(data)} received)") - # TODO: check message length > 0???!!!! - # Add max size check - if self._msg_len > 10000000: # Example max 10MB - logger.error(f"Message too large for stream {self.stream_id}") - self.reset(2) - return - - msg_complete = False - if len(self._msg_buffer) + len(data) >= self._msg_len: - # If we received a full message (or more than 1 message) - end_offset = self._msg_len - len(self._msg_buffer) - self._msg_buffer += data[:end_offset] - data = data[end_offset:] # Note: this packet could contain start of a new message - msg_complete = True - else: - # Otherwise append only, we expect more data to finish this message - self._msg_buffer += data - logger.debug(f"Appending to message for stream {self.stream_id} ({self._msg_buffer} bytes of {self._msg_len})") - data = [] # Note: no new packet can be present in this data - - # If we assembled a new message, parse it - if msg_complete: - logger.debug(f"Message complete for stream {self.stream_id}") - try: - if self.direction == StreamDirection.initiator: - self.initiator_message(self._msg_buffer) - else: - self.acceptor_message(self._msg_buffer) - except Exception as e: - logger.error(f"Error processing message on stream {self.stream_id}: {e}") - self.handle_error(str(e), 1) - finally: - self._reset_msg() - - if end_stream: - logger.debug(f"Received FIN on stream {self.stream_id}") - self.peer_fin_received() - - def handle_error(self, error_msg: str, reset_code: int = 1): - logger.error(f"Stream {self.stream_id} error: {error_msg}") - self.reset(reset_code) - - def peer_fin_received(self): - """Handle peer's FIN. Override in subclass if needed.""" - # Default: send FIN back if not already closed - self.conn.send(self.stream_id, b'', end_stream=True) - # Call protocol success if applicable (override in streams) - pass + def handle_fin(self): + self.conn.close_jam_stream(self, clean_close=True) def reset(self, reset_code: int): @@ -132,6 +149,7 @@ def reset(self, reset_code: int): self.initiator_reset(reset_code) else: self.acceptor_reset(reset_code) + self.conn.close_jam_stream(self, reason=reset_code, clean_close=False) def initiator_reset(self, reset_code: int): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py index 247ccea7..36d0c4b2 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -57,6 +57,6 @@ def acceptor_message(self, data: bytes): # Note: in case of UP0, initiator and acceptor have symmetrical message flow self.initiator_message(data) - def peer_fin_received(self): - logger.warning(f"Unexpected FIN on persistent UP0 stream {self.stream_id}") # UP streams shouldn't FIN - # Optionally reset or ignore + def handle_fin(self): + logger.warning(f"Unexpected FIN on persistent UP0 stream {self.stream_id}") + # TODO: reset or ignore? diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py index a5c5126b..f64786bf 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -23,19 +23,13 @@ def initiator_reset(self, reset_code: int): def initiator_message(self, data: bytes): - if len(data) == 0: #int.from_bytes(b'\x00\x00\x00\x00') - #TODO: send FIN? Let protocol know were finished? - print(f'!!!!!!!!!ConnectionInitiator StreamBlockRequest.initiator_message received empty data') - # self.initiator_reset(reset_code=0) # Only reset on error - self.handle_error("Empty response", 3) - return - logger.debug(f"CE128 initiated stream {self.stream_id} received block request response: {len(data)} bytes") req = MsgCE128BlockRequestResponse.from_jam_bytes(JamBytes(data)) self.protocol.ce128_received_block_request(self, req) - def peer_fin_received(self): - super().peer_fin_received() + + def handle_fin(self): + super().handle_fin() self.protocol.ce128_abort_block_request() # Or success callback diff --git a/pyjamaz/transport/jamnp_s/streams/stream_131.py b/pyjamaz/transport/jamnp_s/streams/stream_131.py index 2a2c6bfe..a5579ac2 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_131.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_131.py @@ -23,10 +23,6 @@ def initiator_reset(self, reset_code: int): def initiator_message(self, data: bytes): - if len(data) == 0: - logger.debug(f"CE131 initiator received empty data") - return - logger.warning(f"Unexpected data in CE131 initiator: {len(data)} bytes") self.handle_error("Unexpected data", 1) @@ -36,6 +32,7 @@ def acceptor_message(self, data: bytes): msg = MsgCE131SafroleTicketDistribution.from_jam_bytes(JamBytes(data)) self.protocol.ce131_received_ticket(self, msg) - def peer_fin_received(self): - super().peer_fin_received() + + def handle_fin(self): + super().handle_fin() self.protocol.ce131_ticket_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_132.py b/pyjamaz/transport/jamnp_s/streams/stream_132.py index 014446fc..c9fd2291 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_132.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_132.py @@ -23,10 +23,6 @@ def initiator_reset(self, reset_code: int): def initiator_message(self, data: bytes): - if len(data) == 0: - logger.debug(f"CE132 initiator received empty data") - return - logger.warning(f"Unexpected data in CE132 initiator: {len(data)} bytes") self.handle_error("Unexpected data", 1) @@ -36,6 +32,7 @@ def acceptor_message(self, data: bytes): msg = MsgCE132SafroleTicketDistribution.from_jam_bytes(JamBytes(data)) self.protocol.ce132_received_ticket(self, msg) - def peer_fin_received(self): - super().peer_fin_received() + + def handle_fin(self): + super().handle_fin() self.protocol.ce132_ticket_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_133.py b/pyjamaz/transport/jamnp_s/streams/stream_133.py index 613b8f3d..cfc549bf 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_133.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_133.py @@ -16,21 +16,23 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') self.received_wp = False + def initiator_reset(self, reset_code: int): logger.debug(f"CE133 received reset code: {reset_code}") self.protocol.ce133_submission_failure(reset_code) super().initiator_reset(reset_code) + def initiator_message(self, data: bytes): - if len(data) == 0: - return logger.warning(f"Unexpected data in CE133 initiator: {len(data)} bytes") self.handle_error("Unexpected data", 1) + def acceptor_reset(self, reset_code: int): self.protocol.ce133_submission_failure(reset_code) super().reset(reset_code) + def acceptor_message(self, data: bytes): if not self.received_wp: logger.debug(f"CE133 acceptor received work package") @@ -42,6 +44,7 @@ def acceptor_message(self, data: bytes): msg = MsgCE133Extrinsic.from_jam_bytes(JamBytes(data)) self.protocol.ce133_received_extrinsic(self, msg) - def peer_fin_received(self): - super().peer_fin_received() + + def handle_fin(self): + super().handle_fin() self.protocol.ce133_submission_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_134.py b/pyjamaz/transport/jamnp_s/streams/stream_134.py index ed6f5262..95899e97 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_134.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_134.py @@ -16,11 +16,13 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') self.received_mappings = False + def initiator_reset(self, reset_code: int): logger.debug(f"CE134 received reset code: {reset_code}") self.protocol.ce134_sharing_failure(reset_code) super().initiator_reset(reset_code) + def initiator_message(self, data: bytes): if not self.received_mappings: logger.debug(f"CE134 initiator received refine response") @@ -31,18 +33,11 @@ def initiator_message(self, data: bytes): logger.warning(f"Unexpected data in CE134 initiator after mappings: {len(data)} bytes") self.handle_error("Unexpected data after mappings", 1) + def acceptor_reset(self, reset_code: int): self.protocol.ce134_sharing_failure(reset_code) super().reset(reset_code) - def initiator_message(self, data: bytes): - if not self.received_mappings: - logger.debug(f"CE134 initiator received refine response") - msg = MsgCE134RefineResponse.from_jam_bytes(JamBytes(data)) - self.protocol.ce134_received_refine_response(self, msg) - self.received_mappings = True - else: - logger.warning(f"Unexpected data in CE134 initiator after mappings: {len(data)} bytes") def acceptor_message(self, data: bytes): if not self.received_mappings: @@ -55,6 +50,7 @@ def acceptor_message(self, data: bytes): msg = MsgCE134WorkPackageBundle.from_jam_bytes(JamBytes(data)) self.protocol.ce134_received_bundle(self, msg) - def peer_fin_received(self): - super().peer_fin_received() + + def handle_fin(self): + super().handle_fin() self.protocol.ce134_sharing_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_135.py b/pyjamaz/transport/jamnp_s/streams/stream_135.py index 0c36df95..587630f0 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_135.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_135.py @@ -15,26 +15,29 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type = StreamType.CE135_WorkReportDistribution.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') + def initiator_reset(self, reset_code: int): logger.debug(f"CE135 received reset code: {reset_code}") self.protocol.ce135_distribution_failure(reset_code) super().initiator_reset(reset_code) + def initiator_message(self, data: bytes): - if len(data) == 0: - return logger.warning(f"Unexpected data in CE135 initiator: {len(data)} bytes") self.handle_error("Unexpected data", 1) + def acceptor_reset(self, reset_code: int): self.protocol.ce135_distribution_failure(reset_code) super().reset(reset_code) + def acceptor_message(self, data: bytes): logger.debug(f"CE135 acceptor received work report") msg = MsgCE135GuaranteedWorkReport.from_jam_bytes(JamBytes(data)) self.protocol.ce135_received_report(self, msg) - def peer_fin_received(self): - super().peer_fin_received() + + def handle_fin(self): + super().handle_fin() self.protocol.ce135_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_136.py b/pyjamaz/transport/jamnp_s/streams/stream_136.py index add309fc..2168ff3c 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_136.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_136.py @@ -15,21 +15,25 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type = StreamType.CE136_WorkReportRequest.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') + def initiator_reset(self, reset_code: int): logger.debug(f"CE136 received reset code: {reset_code}") self.protocol.ce136_request_failure(reset_code) super().initiator_reset(reset_code) + def initiator_message(self, data: bytes): logger.debug(f"CE136 initiator received work report") msg = MsgCE136WorkReport.from_jam_bytes(JamBytes(data)) self.protocol.ce136_received_report(self, msg) + def acceptor_message(self, data: bytes): logger.debug(f"CE136 acceptor received request") msg = MsgCE136HashRequest.from_jam_bytes(JamBytes(data)) self.protocol.ce136_received_request(self, msg) - def peer_fin_received(self): - super().peer_fin_received() + + def handle_fin(self): + super().handle_fin() self.protocol.ce136_request_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_141.py b/pyjamaz/transport/jamnp_s/streams/stream_141.py index 7035933e..36c04dd2 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_141.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_141.py @@ -15,26 +15,29 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type = StreamType.CE141_AssuranceDistribution.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') + def initiator_reset(self, reset_code: int): logger.debug(f"CE141 received reset code: {reset_code}") self.protocol.ce141_distribution_failure(reset_code) super().initiator_reset(reset_code) + def initiator_message(self, data: bytes): - if len(data) == 0: - return logger.warning(f"Unexpected data in CE141 initiator: {len(data)} bytes") self.handle_error("Unexpected data", 1) + def acceptor_reset(self, reset_code: int): self.protocol.ce141_distribution_failure(reset_code) super().reset(reset_code) + def acceptor_message(self, data: bytes): logger.debug(f"CE141 acceptor received assurance") msg = MsgCE141Assurance.from_jam_bytes(JamBytes(data)) self.protocol.ce141_received_assurance(self, msg) - def peer_fin_received(self): - super().peer_fin_received() + + def handle_fin(self): + super().handle_fin() self.protocol.ce141_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_142.py b/pyjamaz/transport/jamnp_s/streams/stream_142.py index 16a35e8b..2f0fb656 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_142.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_142.py @@ -15,26 +15,31 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type = StreamType.CE142_PreimageAnnouncement.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') + def initiator_reset(self, reset_code: int): logger.debug(f"CE142 received reset code: {reset_code}") self.protocol.ce142_announcement_failure(reset_code) super().initiator_reset(reset_code) + def initiator_message(self, data: bytes): if len(data) == 0: return logger.warning(f"Unexpected data in CE142 initiator: {len(data)} bytes") self.handle_error("Unexpected data", 1) + def acceptor_reset(self, reset_code: int): self.protocol.ce142_announcement_failure(reset_code) super().reset(reset_code) + def acceptor_message(self, data: bytes): logger.debug(f"CE142 acceptor received preimage announcement") msg = MsgCE142PreimageAnnouncement.from_jam_bytes(JamBytes(data)) self.protocol.ce142_received_announcement(self, msg) - def peer_fin_received(self): - super().peer_fin_received() + + def handle_fin(self): + super().handle_fin() self.protocol.ce142_announcement_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_143.py b/pyjamaz/transport/jamnp_s/streams/stream_143.py index da77c8a7..04c79baa 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_143.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_143.py @@ -15,21 +15,25 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type = StreamType.CE143_PreimageRequest.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') + def initiator_reset(self, reset_code: int): logger.debug(f"CE143 received reset code: {reset_code}") self.protocol.ce143_request_failure(reset_code) super().initiator_reset(reset_code) + def initiator_message(self, data: bytes): logger.debug(f"CE143 initiator received preimage") msg = MsgCE143Preimage.from_jam_bytes(JamBytes(data)) self.protocol.ce143_received_preimage(self, msg) + def acceptor_message(self, data: bytes): logger.debug(f"CE143 acceptor received preimage request") msg = MsgCE143HashRequest.from_jam_bytes(JamBytes(data)) self.protocol.ce143_received_request(self, msg) - def peer_fin_received(self): - super().peer_fin_received() + + def handle_fin(self): + super().handle_fin() self.protocol.ce143_request_success(0) \ No newline at end of file From bd2c1d391c15cb4733ace4bb264c7fc62ea0710d Mon Sep 17 00:00:00 2001 From: matthijsb Date: Wed, 16 Jul 2025 17:52:47 +0200 Subject: [PATCH 23/51] - JAMNP wip --- pyjamaz/transport/jamnp_s/message_types.py | 110 ++++++++++- pyjamaz/transport/jamnp_s/protocol.py | 171 +++++++++++++++++- pyjamaz/transport/jamnp_s/stream_base.py | 4 +- pyjamaz/transport/jamnp_s/stream_map.py | 25 ++- .../transport/jamnp_s/streams/stream_137.py | 35 +++- .../transport/jamnp_s/streams/stream_138.py | 27 ++- .../transport/jamnp_s/streams/stream_139.py | 26 ++- .../transport/jamnp_s/streams/stream_140.py | 26 ++- .../transport/jamnp_s/streams/stream_144.py | 45 +++++ .../transport/jamnp_s/streams/stream_145.py | 38 ++++ 10 files changed, 474 insertions(+), 33 deletions(-) create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_144.py create mode 100644 pyjamaz/transport/jamnp_s/streams/stream_145.py diff --git a/pyjamaz/transport/jamnp_s/message_types.py b/pyjamaz/transport/jamnp_s/message_types.py index 2b3d8c01..eabdbad1 100644 --- a/pyjamaz/transport/jamnp_s/message_types.py +++ b/pyjamaz/transport/jamnp_s/message_types.py @@ -4,8 +4,9 @@ from jamcodec.base import JamCodecTypeDef, JamBytes, JamCodecType from jamcodec.mixins import Serializable -from jamcodec.types import U32, H256, U8, VarInt64, Vec, Option, Array, Map, H512, VecType +from jamcodec.types import U32, H256, U8, VarInt64, Vec, Option, Array, Map, H512, VecType, U16 +from pyjamaz.graypaper_constants import VALIDATOR_COUNT from pyjamaz.models.block import Header, Guarantee, Block from pyjamaz.models.common import WorkPackage @@ -41,6 +42,10 @@ def decode(self, data: JamBytes) -> list: return value +def calculate_r(): + return (VALIDATOR_COUNT // 3) + 1 # Per spec + + @dataclass class MsgUP0Leaf(Serializable): header_hash: bytes = field(metadata={'codec': H256}) @@ -156,6 +161,64 @@ class MsgCE136WorkReport(Serializable): report: bytes = field(metadata={'codec': Vec(U8)}) +@dataclass +class MsgCE137ShardRequest(Serializable): + erasure_root: bytes = field(metadata={'codec': H256}) + shard_index: int = field(metadata={'codec': U16}) + +@dataclass +class MsgCE137BundleShard(Serializable): + bytes_: bytes = field(metadata={'codec': Vec(U8)}) + +@dataclass +class MsgCE137SegmentShard(Serializable): + bytes_: bytes = field(metadata={'codec': Array(U8, 4104 // calculate_r())}) + +@dataclass +class MsgCE137Justification(Serializable): + nodes: List[bytes] = field(metadata={'codec': Vec(Vec(U8))}) # Co-path + + +@dataclass +class MsgCE138ShardRequest(Serializable): + erasure_root: bytes = field(metadata={'codec': H256}) + shard_index: int = field(metadata={'codec': U16}) + +@dataclass +class MsgCE138BundleShard(Serializable): + bytes_: bytes = field(metadata={'codec': Vec(U8)}) + +@dataclass +class MsgCE138Justification(Serializable): + nodes: List[bytes] = field(metadata={'codec': Vec(Vec(U8))}) + + +@dataclass +class MsgCE139SegmentRequest(Serializable): + erasure_root: bytes = field(metadata={'codec': H256}) + shard_index: int = field(metadata={'codec': U16}) + segment_indices: List[int] = field(metadata={'codec': Vec(U16)}) + +@dataclass +class MsgCE139SegmentShard(Serializable): + bytes_: bytes = field(metadata={'codec': Array(U8, 4104 // calculate_r())}) + + +@dataclass +class MsgCE140SegmentRequest(Serializable): + erasure_root: bytes = field(metadata={'codec': H256}) + shard_index: int = field(metadata={'codec': U16}) + segment_indices: List[int] = field(metadata={'codec': Vec(U16)}) + +@dataclass +class MsgCE140SegmentShard(Serializable): + bytes_: bytes = field(metadata={'codec': Array(U8, 4104 // calculate_r())}) + +@dataclass +class MsgCE140Justification(Serializable): + nodes: List[bytes] = field(metadata={'codec': Vec(Vec(U8))}) + + @dataclass class MsgCE141Assurance(Serializable): header_hash: bytes = field(metadata={'codec': H256}) # anchor @@ -174,7 +237,50 @@ class MsgCE142PreimageAnnouncement(Serializable): class MsgCE143HashRequest(Serializable): hash: bytes = field(metadata={'codec': H256}) - @dataclass class MsgCE143Preimage(Serializable): bytes_: bytes = field(metadata={'codec': Vec(U8)}) + + +@dataclass +class MsgCE144CoreWRPair(Serializable): + core_index: int = field(metadata={'codec': U32}) + wr_hash: bytes = field(metadata={'codec': H256}) + +@dataclass +class MsgCE144Announcement(Serializable): + header_hash: bytes = field(metadata={'codec': H256}) + tranche: int = field(metadata={'codec': U8}) + announcement: List[MsgCE144CoreWRPair] = field(metadata={'codec': Vec(MsgCE144CoreWRPair.to_codec_def())}) + signature: bytes = field(metadata={'codec': H512}) + +@dataclass +class MsgCE144NoShow(Serializable): + validator_index: int = field(metadata={'codec': U32}) + announcement: bytes = field(metadata={'codec': Vec(U8)}) + +@dataclass +class MsgCE144TrancheEvidenceFirst(Serializable): + signature: bytes = field(metadata={'codec': Array(U8, 96)}) + +@dataclass +class MsgCE144TrancheEvidenceSubsequent(Serializable): + signature: bytes = field(metadata={'codec': Array(U8, 96)}) + no_shows: List[MsgCE144NoShow] = field(metadata={'codec': Vec(MsgCE144NoShow.to_codec_def())}) + +@dataclass +class MsgCE144Evidence(Serializable): + data: bytes = field(metadata={'codec': Vec(U8)}) + + +class JudgmentValidity(Enum): + INVALID = 0 + VALID = 1 + +@dataclass +class MsgCE145JudgmentPublication(Serializable): + epoch_index: int = field(metadata={'codec': U32}) + validator_index: int = field(metadata={'codec': U32}) + validity: int = field(metadata={'codec': U8}) + wr_hash: bytes = field(metadata={'codec': H256}) + signature: bytes = field(metadata={'codec': H512}) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index c3b8c36e..d7c63797 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -15,6 +15,8 @@ from pyjamaz.constants import MESSAGE_TYPES from pyjamaz.models.block import Block, Header from pyjamaz.transport.jamnp_s.stream_base import StreamDirection +from pyjamaz.transport.jamnp_s.streams.stream_138 import StreamAuditShardRequest +from pyjamaz.transport.jamnp_s.streams.stream_139 import StreamSegmentShardRequest from pyjamaz.transport.types import ProtocolType from pyjamaz.transport.jamnp_s.message_types import MsgCE128BlockRequestDirection, MsgCE128BlockRequest, \ @@ -23,7 +25,13 @@ MsgCE134WorkPackageSharing, MsgCE134WorkPackageBundle, MsgCE134RefineResponse, \ MsgCE141Assurance, MsgCE142PreimageAnnouncement, MsgCE143HashRequest, MsgCE143Preimage, \ MsgCE133WorkPackageSubmission, MsgCE133Extrinsic, MsgCE135GuaranteedWorkReport, \ - MsgCE136HashRequest, MsgCE136WorkReport + MsgCE136HashRequest, MsgCE136WorkReport, \ + MsgCE137ShardRequest, MsgCE137BundleShard, MsgCE137SegmentShard, MsgCE137Justification, \ + MsgCE138ShardRequest, MsgCE138BundleShard, MsgCE138Justification, \ + MsgCE139SegmentRequest, MsgCE139SegmentShard, \ + MsgCE140SegmentRequest, MsgCE140SegmentShard, MsgCE140Justification, \ + MsgCE144Evidence, \ + MsgCE145JudgmentPublication from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest from pyjamaz.transport.jamnp_s.streams.stream_131 import StreamSafroleTicketDistributionStep1 from pyjamaz.transport.jamnp_s.streams.stream_132 import StreamSafroleTicketDistributionStep2 @@ -34,6 +42,10 @@ from pyjamaz.transport.jamnp_s.streams.stream_133 import StreamWorkPackageSubmission from pyjamaz.transport.jamnp_s.streams.stream_135 import StreamWorkReportDistribution from pyjamaz.transport.jamnp_s.streams.stream_136 import StreamWorkReportRequest +from pyjamaz.transport.jamnp_s.streams.stream_137 import StreamShardDistribution +from pyjamaz.transport.jamnp_s.streams.stream_140 import StreamSegmentShardRequestJustification +from pyjamaz.transport.jamnp_s.streams.stream_144 import StreamAuditAnnouncement +from pyjamaz.transport.jamnp_s.streams.stream_145 import StreamJudgmentPublication from pyjamaz.transport.jamnp_s.connection import JAMConnection @@ -612,10 +624,159 @@ def ce136_request_failure(self, reset_code: int): logger.error(f"CE136 request failed with code {reset_code}") - # Add placeholder verify methods (implement as needed) - def verify_ticket_proof(self, msg): - return True # TODO: actual verification + def ce137_initiate_request(self, conn: JAMConnection, req: MsgCE137ShardRequest): + stream = conn.open_jam_stream(StreamShardDistribution, direction=StreamDirection.initiator) + conn.send(stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + + def ce137_received_request(self, stream: StreamShardDistribution, msg: MsgCE137ShardRequest): + # TODO: fetch and send bundle shard, segment shards, justification + bundle = MsgCE137BundleShard(bytes_ = b'') + segments = [MsgCE137SegmentShard(bytes_ = b'') for _ in range(10)] + just = MsgCE137Justification(bytes_ = b'') + stream.conn.send(stream.stream_id, stream.create_message(bundle.to_jam_bytes().to_bytes()), end_stream=False) + for s in segments: + stream.conn.send(stream.stream_id, stream.create_message(s.to_jam_bytes().to_bytes()), end_stream=False) + stream.conn.send(stream.stream_id, stream.create_message(just.to_jam_bytes().to_bytes()), end_stream=True) + + + def ce137_received_shard(self, stream: StreamShardDistribution, bundle, segments, just): + # TODO: process + stream.conn.send(stream.stream_id, b'', end_stream=True) + + + def ce137_distribution_success(self, code: int): + logger.debug(f"CE137 success {code}") + + + def ce137_distribution_failure(self, code: int): + logger.error(f"CE137 failure {code}") + + + # Similar for CE138, CE139, CE140 + def ce138_initiate_request(self, conn: JAMConnection, req: MsgCE138ShardRequest): + stream = conn.open_jam_stream(StreamAuditShardRequest, direction=StreamDirection.initiator) + conn.send(stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + + def ce138_received_request(self, stream: StreamAuditShardRequest, msg: MsgCE138ShardRequest): + # TODO: send bundle shard and justification + bundle = MsgCE138BundleShard(bytes_ = b'') + just = MsgCE138Justification(bytes_ = b'') + stream.conn.send(stream.stream_id, stream.create_message(bundle.to_jam_bytes().to_bytes()), end_stream=False) + stream.conn.send(stream.stream_id, stream.create_message(just.to_jam_bytes().to_bytes()), end_stream=True) + + + def ce138_received_shard(self, stream: StreamAuditShardRequest, bundle, just): + # TODO: process + stream.conn.send(stream.stream_id, b'', end_stream=True) + + + def ce138_request_success(self, code: int): + logger.debug(f"CE138 success {code}") + + + def ce138_request_failure(self, code: int): + logger.error(f"CE138 failure {code}") + + + def ce139_initiate_request(self, conn: JAMConnection, req: MsgCE139SegmentRequest): + stream = conn.open_jam_stream(StreamSegmentShardRequest, direction=StreamDirection.initiator) + conn.send(stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + + def ce139_received_request(self, stream: StreamSegmentShardRequest, msg: MsgCE139SegmentRequest): + # TODO: send segment shards + shards = [MsgCE139SegmentShard(bytes_ = b'') for _ in msg.segment_indices] + for s in shards: + stream.conn.send(stream.stream_id, stream.create_message(s.to_jam_bytes().to_bytes()), end_stream=(s == shards[-1])) + + + def ce139_received_shards(self, stream: StreamSegmentShardRequest, shards): + # TODO: process + stream.conn.send(stream.stream_id, b'', end_stream=True) + + + def ce139_request_success(self, code: int): + logger.debug(f"CE139 success {code}") + + + def ce139_request_failure(self, code: int): + logger.error(f"CE139 failure {code}") + + def ce140_initiate_request(self, conn: JAMConnection, req: MsgCE140SegmentRequest): + stream = conn.open_jam_stream(StreamSegmentShardRequestJustification, direction=StreamDirection.initiator) + conn.send(stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + + def ce140_received_request(self, stream: StreamSegmentShardRequestJustification, msg: MsgCE140SegmentRequest): + # TODO: send shards and justifications + for idx in msg.segment_indices: + shard = MsgCE140SegmentShard(bytes_ = b'') + just = MsgCE140Justification(bytes_ = b'') + stream.conn.send(stream.stream_id, stream.create_message(shard.to_jam_bytes().to_bytes()), end_stream=False) + stream.conn.send(stream.stream_id, stream.create_message(just.to_jam_bytes().to_bytes()), end_stream=(idx == msg.segment_indices[-1])) + + + def ce140_received_shards_justified(self, stream: StreamSegmentShardRequestJustification, data): + # TODO: parse and process + stream.conn.send(stream.stream_id, b'', end_stream=True) + + + def ce140_request_success(self, code: int): + logger.debug(f"CE140 success {code}") + + + def ce140_request_failure(self, code: int): + logger.error(f"CE140 failure {code}") + + + def ce144_initiate_announcement(self, conn: JAMConnection, ann: MsgCE144AuditAnnouncement, evidence: MsgCE144Evidence): + stream = conn.open_jam_stream(StreamAuditAnnouncement, direction=StreamDirection.initiator) + conn.send(stream.stream_id, stream.create_message(ann.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) + conn.send(stream.stream_id, stream.create_message(evidence.to_jam_bytes().to_bytes()), end_stream=True) + + + def ce144_received_announcement(self, stream: StreamAuditAnnouncement, msg: MsgCE144AuditAnnouncement): + # TODO: process announcement + pass + + + def ce144_received_evidence(self, stream: StreamAuditAnnouncement, msg: MsgCE144Evidence): + # TODO: process evidence + stream.conn.send(stream.stream_id, b'', end_stream=True) + + + def ce144_announcement_success(self, code: int): + logger.debug(f"CE144 success {code}") + + + def ce144_announcement_failure(self, code: int): + logger.error(f"CE144 failure {code}") + + + def ce145_initiate_publication(self, conn: JAMConnection, msg: MsgCE145JudgmentPublication): + stream = conn.open_jam_stream(StreamJudgmentPublication, direction=StreamDirection.initiator) + conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + + def ce145_received_judgment(self, stream: StreamJudgmentPublication, msg: MsgCE145JudgmentPublication): + # TODO: process judgment + stream.conn.send(stream.stream_id, b'', end_stream=True) + + + def ce145_publication_success(self, code: int): + logger.debug(f"CE145 success {code}") + + + def ce145_publication_failure(self, code: int): + logger.error(f"CE145 failure {code}") + + + # Temp placeholders + def verify_ticket_proof(self, msg): + return True def verify_bundle(self, msg): - return True # TODO: actual verification \ No newline at end of file + return True \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py index bfc8e895..4409dae7 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -22,8 +22,8 @@ class StreamType(Enum): CE141_AssuranceDistribution: int = 141 CE142_PreimageAnnouncement: int = 142 CE143_PreimageRequest: int = 143 - CE144_PreimageAnnouncement: int = 144 - # 145 + CE144_AuditAnnouncement: int = 144 + CE145_JudgmentPublication: int = 145 class StreamDirection(Enum): diff --git a/pyjamaz/transport/jamnp_s/stream_map.py b/pyjamaz/transport/jamnp_s/stream_map.py index c249d099..27cce470 100644 --- a/pyjamaz/transport/jamnp_s/stream_map.py +++ b/pyjamaz/transport/jamnp_s/stream_map.py @@ -10,22 +10,31 @@ from pyjamaz.transport.jamnp_s.streams.stream_133 import StreamWorkPackageSubmission from pyjamaz.transport.jamnp_s.streams.stream_135 import StreamWorkReportDistribution from pyjamaz.transport.jamnp_s.streams.stream_136 import StreamWorkReportRequest +from pyjamaz.transport.jamnp_s.streams.stream_137 import StreamShardDistribution +from pyjamaz.transport.jamnp_s.streams.stream_138 import StreamAuditShardRequest +from pyjamaz.transport.jamnp_s.streams.stream_139 import StreamSegmentShardRequest +from pyjamaz.transport.jamnp_s.streams.stream_140 import StreamSegmentShardRequestJustification +from pyjamaz.transport.jamnp_s.streams.stream_144 import StreamAuditAnnouncement +from pyjamaz.transport.jamnp_s.streams.stream_145 import StreamJudgmentPublication + StreamLookup = { StreamType.UP0_BlockAnnouncement.value: StreamUP, StreamType.CE128_BlockRequest.value: StreamBlockRequest, + #TODO: StreamType.CE129_:, StreamType.CE131_SafroleTicketDistributionStep1.value: StreamSafroleTicketDistributionStep1, StreamType.CE132_SafroleTicketDistributionStep2.value: StreamSafroleTicketDistributionStep2, StreamType.CE134_WorkPackageSharing.value: StreamWorkPackageSharing, - StreamType.CE141_AssuranceDistribution.value: StreamAssuranceDistribution, - StreamType.CE142_PreimageAnnouncement.value: StreamPreimageAnnouncement, - StreamType.CE143_PreimageRequest.value: StreamPreimageRequest, StreamType.CE133_WorkPackageSubmission.value: StreamWorkPackageSubmission, StreamType.CE135_WorkReportDistribution.value: StreamWorkReportDistribution, StreamType.CE136_WorkReportRequest.value: StreamWorkReportRequest, - # StreamType.CE137_ShardDistribution.value: , - # StreamType.CE138_AuditShardRequest.value: , - # StreamType.CE139_SegmentShardRequest.value: , - # StreamType.CE140_SegmentShardRequestJustification.value: , - # StreamType.CE141_AssuranceDistribution.value: , + StreamType.CE137_ShardDistribution.value: StreamShardDistribution, + StreamType.CE138_AuditShardRequest.value: StreamAuditShardRequest, + StreamType.CE139_SegmentShardRequest.value: StreamSegmentShardRequest, + StreamType.CE140_SegmentShardRequestJustification.value: StreamSegmentShardRequestJustification, + StreamType.CE141_AssuranceDistribution.value: StreamAssuranceDistribution, + StreamType.CE142_PreimageAnnouncement.value: StreamPreimageAnnouncement, + StreamType.CE143_PreimageRequest.value: StreamPreimageRequest, + StreamType.CE144_AuditAnnouncement.value: StreamAuditAnnouncement, + StreamType.CE145_JudgmentPublication.value: StreamJudgmentPublication, } \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_137.py b/pyjamaz/transport/jamnp_s/streams/stream_137.py index 0ef4756c..331429dd 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_137.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_137.py @@ -1,5 +1,12 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE137ShardRequest, MsgCE137BundleShard, MsgCE137SegmentShard, MsgCE137Justification from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + class StreamShardDistribution(Stream): @@ -9,13 +16,33 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - def initiate_state_request(self, header_hash, direction, max_blocks): - pass + def initiator_reset(self, reset_code: int): + logger.debug(f"CE137 received reset code: {reset_code}") + self.protocol.ce137_distribution_failure(reset_code) + super().initiator_reset(reset_code) + + + def _chunks(self, data: bytes, chunk_size: int): + """Yield successive n-sized chunks from data.""" + for i in range(0, len(data), chunk_size): + yield data[i:i + chunk_size] def initiator_message(self, data: bytes): - pass + logger.debug(f"CE137 initiator received response") + # Parse bundle shard, segment shards, justification + bundle = MsgCE137BundleShard.from_jam_bytes(JamBytes(data[:len(data)//3])) # Simplified parsing + segments = [MsgCE137SegmentShard.from_jam_bytes(JamBytes(d)) for d in self._chunks(data, len(data)//3)] + just = MsgCE137Justification.from_jam_bytes(JamBytes(data[-len(data)//3:])) + self.protocol.ce137_received_shard(self, bundle, segments, just) def acceptor_message(self, data: bytes): - pass \ No newline at end of file + logger.debug(f"CE137 acceptor received request") + msg = MsgCE137ShardRequest.from_jam_bytes(JamBytes(data)) + self.protocol.ce137_received_request(self, msg) + + + def handle_fin(self): + super().handle_fin() + self.protocol.ce137_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_138.py b/pyjamaz/transport/jamnp_s/streams/stream_138.py index 3a7b4a66..a6e3c759 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_138.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_138.py @@ -1,5 +1,12 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE138ShardRequest, MsgCE138BundleShard, MsgCE138Justification from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + class StreamAuditShardRequest(Stream): @@ -9,13 +16,25 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - def initiate_state_request(self, header_hash, direction, max_blocks): - pass + def initiator_reset(self, reset_code: int): + logger.debug(f"CE138 received reset code: {reset_code}") + self.protocol.ce138_request_failure(reset_code) + super().initiator_reset(reset_code) def initiator_message(self, data: bytes): - pass + logger.debug(f"CE138 initiator received response") + bundle = MsgCE138BundleShard.from_jam_bytes(JamBytes(data[:len(data)//2])) + just = MsgCE138Justification.from_jam_bytes(JamBytes(data[len(data)//2:])) + self.protocol.ce138_received_shard(self, bundle, just) def acceptor_message(self, data: bytes): - pass \ No newline at end of file + logger.debug(f"CE138 acceptor received request") + msg = MsgCE138ShardRequest.from_jam_bytes(JamBytes(data)) + self.protocol.ce138_received_request(self, msg) + + + def handle_fin(self): + super().handle_fin() + self.protocol.ce138_request_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_139.py b/pyjamaz/transport/jamnp_s/streams/stream_139.py index 722161dc..06748468 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_139.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_139.py @@ -1,5 +1,12 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE139SegmentRequest, MsgCE139SegmentShard from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + class StreamSegmentShardRequest(Stream): @@ -9,13 +16,24 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - def initiate_state_request(self, header_hash, direction, max_blocks): - pass + def initiator_reset(self, reset_code: int): + logger.debug(f"CE139 received reset code: {reset_code}") + self.protocol.ce139_request_failure(reset_code) + super().initiator_reset(reset_code) def initiator_message(self, data: bytes): - pass + logger.debug(f"CE139 initiator received shards") + shards = [MsgCE139SegmentShard.from_jam_bytes(JamBytes(d)) for d in chunks(data, 4104 // R)] # R from spec + self.protocol.ce139_received_shards(self, shards) def acceptor_message(self, data: bytes): - pass \ No newline at end of file + logger.debug(f"CE139 acceptor received request") + msg = MsgCE139SegmentRequest.from_jam_bytes(JamBytes(data)) + self.protocol.ce139_received_request(self, msg) + + + def handle_fin(self): + super().handle_fin() + self.protocol.ce139_request_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_140.py b/pyjamaz/transport/jamnp_s/streams/stream_140.py index 886eb7ac..2d6970ff 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_140.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_140.py @@ -1,5 +1,12 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE140SegmentRequest, MsgCE140SegmentShard, MsgCE140Justification from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + class StreamSegmentShardRequestJustification(Stream): @@ -9,13 +16,24 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - def initiate_state_request(self, header_hash, direction, max_blocks): - pass + def initiator_reset(self, reset_code: int): + logger.debug(f"CE140 received reset code: {reset_code}") + self.protocol.ce140_request_failure(reset_code) + super().initiator_reset(reset_code) def initiator_message(self, data: bytes): - pass + logger.debug(f"CE140 initiator received shards and justifications") + # Parse shards and per-shard justifications + self.protocol.ce140_received_shards_justified(self, data) # Simplified def acceptor_message(self, data: bytes): - pass \ No newline at end of file + logger.debug(f"CE140 acceptor received request") + msg = MsgCE140SegmentRequest.from_jam_bytes(JamBytes(data)) + self.protocol.ce140_received_request(self, msg) + + + def handle_fin(self): + super().handle_fin() + self.protocol.ce140_request_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_144.py b/pyjamaz/transport/jamnp_s/streams/stream_144.py new file mode 100644 index 00000000..fbd3c7cc --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_144.py @@ -0,0 +1,45 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE144AuditAnnouncement, MsgCE144Evidence +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class StreamAuditAnnouncement(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE144_AuditAnnouncement.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') + self.received_announcement = False + + + def initiator_reset(self, reset_code: int): + logger.debug(f"CE144 received reset code: {reset_code}") + self.protocol.ce144_announcement_failure(reset_code) + super().initiator_reset(reset_code) + + + def initiator_message(self, data: bytes): + logger.warning(f"Unexpected data in CE144 initiator: {len(data)} bytes") + self.handle_error("Unexpected data", 1) + + + def acceptor_message(self, data: bytes): + if not self.received_announcement: + logger.debug(f"CE144 acceptor received announcement") + msg = MsgCE144AuditAnnouncement.from_jam_bytes(JamBytes(data)) + self.protocol.ce144_received_announcement(self, msg) + self.received_announcement = True + else: + logger.debug(f"CE144 acceptor received evidence") + msg = MsgCE144Evidence.from_jam_bytes(JamBytes(data)) + self.protocol.ce144_received_evidence(self, msg) + + + def handle_fin(self): + super().handle_fin() + self.protocol.ce144_announcement_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_145.py b/pyjamaz/transport/jamnp_s/streams/stream_145.py new file mode 100644 index 00000000..39ad2176 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/stream_145.py @@ -0,0 +1,38 @@ +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.message_types import MsgCE145JudgmentPublication +from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class StreamJudgmentPublication(Stream): + + def __init__(self, stream_id: int, connection, direction: StreamDirection): + super().__init__(stream_id, connection, direction) + self.stream_type = StreamType.CE145_JudgmentPublication.value + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') + + + def initiator_reset(self, reset_code: int): + logger.debug(f"CE145 received reset code: {reset_code}") + self.protocol.ce145_publication_failure(reset_code) + super().initiator_reset(reset_code) + + + def initiator_message(self, data: bytes): + logger.warning(f"Unexpected data in CE145 initiator: {len(data)} bytes") + self.handle_error("Unexpected data", 1) + + + def acceptor_message(self, data: bytes): + logger.debug(f"CE145 acceptor received judgment") + msg = MsgCE145JudgmentPublication.from_jam_bytes(JamBytes(data)) + self.protocol.ce145_received_judgment(self, msg) + + + def handle_fin(self): + super().handle_fin() + self.protocol.ce145_publication_success(0) \ No newline at end of file From a8a4734c53bd0cf78706191b7c080a4a89143d5e Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 17 Jul 2025 19:25:32 +0200 Subject: [PATCH 24/51] - JAMNP wip --- pyjamaz/app.py | 16 +- pyjamaz/cli.py | 3 +- pyjamaz/constants.py | 1 + pyjamaz/extrinsic.py | 11 +- pyjamaz/transport/jamnp_s/protocol.py | 275 +++++++++++++++++++------- 5 files changed, 223 insertions(+), 83 deletions(-) diff --git a/pyjamaz/app.py b/pyjamaz/app.py index 142a89a7..cb339ee6 100644 --- a/pyjamaz/app.py +++ b/pyjamaz/app.py @@ -852,17 +852,21 @@ async def produce_block(self, timeslot: int, safrole_state: SafroleState, entrop if not SOLO_MODE and self.block_extrinsic.can_add_own_ticket(timeslot): ring_public_keys = [v.bandersnatch for v in safrole_state.validators] + epoch_index = timeslot // EPOCH_TIMESLOTS - self.block_extrinsic.add_own_ticket( - ring_public_keys, entropy, self.config.keys.bandersnatch, self.get_author_index() + await self.block_extrinsic.add_own_ticket( + ring_public_keys, entropy, self.config.keys.bandersnatch, self.get_author_index(), + epoch_index=epoch_index, pubsub=self.pubsub ) - self.block_extrinsic.add_own_ticket( - ring_public_keys, entropy, self.config.keys.bandersnatch, self.get_author_index() + await self.block_extrinsic.add_own_ticket( + ring_public_keys, entropy, self.config.keys.bandersnatch, self.get_author_index(), + epoch_index=epoch_index, pubsub=self.pubsub ) - self.block_extrinsic.add_own_ticket( - ring_public_keys, entropy, self.config.keys.bandersnatch, self.get_author_index() + await self.block_extrinsic.add_own_ticket( + ring_public_keys, entropy, self.config.keys.bandersnatch, self.get_author_index(), + epoch_index=epoch_index, pubsub=self.pubsub ) extrinsic = Extrinsic( diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index e7ab1559..c5fa1902 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -266,7 +266,8 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, #initial_block_hash = app.retrieve_block_hash(0).hex() nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app) app.protocol = nps_protocol - app.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, wrap_produced_block_jamnp(app, record_traces, nps_protocol)) + app.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, wrap_produced_block_jamnp(app, record_traces, nps_protocol)) #TODO: is this function wrapping still necesary? + app.pubsub.subscribe(MESSAGE_TYPES.TICKET_ADD, nps_protocol.ce131_initiate_distribute_own_ticket) tg.start_soon(nps_protocol.listen) if bootnode: diff --git a/pyjamaz/constants.py b/pyjamaz/constants.py index e132074e..df02e97e 100644 --- a/pyjamaz/constants.py +++ b/pyjamaz/constants.py @@ -44,6 +44,7 @@ class MESSAGE_TYPES(Enum): PREIMAGE_AVAILABILITY = "preimage_availability" BEST_BLOCK = "best_block" FINALIZED_BLOCK = "finalized_block" + TICKET_ADD = "ticket_add" CE128_SUCCESS = "CE128_SUCCESS" CE128_FAILURE = "CE128_FAILURE" diff --git a/pyjamaz/extrinsic.py b/pyjamaz/extrinsic.py index 61a3a8cc..1840d67c 100644 --- a/pyjamaz/extrinsic.py +++ b/pyjamaz/extrinsic.py @@ -3,6 +3,7 @@ from bandersnatch_vrfs import ring_vrf_sign, ietf_vrf_verify, ring_vrf_verify, vrf_output +from pyjamaz.constants import MESSAGE_TYPES from pyjamaz.graypaper_constants import TICKET_ENTRIES, MAXIMUM_EXTRINSIC_TICKETS, TICKET_SUBMISSION_END_SLOT, \ EPOCH_TIMESLOTS from pyjamaz.hashing import blake2b_256_hash @@ -11,6 +12,7 @@ from pyjamaz.models.state import ServicesState from pyjamaz.models.stf_output import SafroleErrorCode from pyjamaz.signing import BandersnatchKeypair +from pyjamaz.transport.pubsub import PubSub, PubSubSignal from pyjamaz.utils import vrf_input_ticket_seal, format_hash @@ -50,8 +52,9 @@ def add_ticket(self, ticket_data: TicketEnvelope, ring_public_keys: List[bytes], def can_add_own_ticket(self, timeslot: int) -> bool: return len(self.own_tickets_next) < TICKET_ENTRIES and timeslot % EPOCH_TIMESLOTS < TICKET_SUBMISSION_END_SLOT - def add_own_ticket( - self, ring_public_keys: List[bytes], entropy: bytes, keypair: BandersnatchKeypair, author_index: int + async def add_own_ticket( + self, ring_public_keys: List[bytes], entropy: bytes, keypair: BandersnatchKeypair, author_index: int, + epoch_index: int = None, pubsub: PubSub = None ): if len(self.tickets_queue) > TICKET_ENTRIES: @@ -80,6 +83,10 @@ def add_own_ticket( self.tickets_queue[ticket_id] = ticket self.own_tickets_next.append(ticket_id) + + # Distribute ticket to peers if requested + if pubsub and epoch_index is not None: + await pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.TICKET_ADD, data=[epoch_index, attempt, signature])) def collect_tickets(self) -> List[TicketEnvelope]: """ diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index d7c63797..7cc43c3e 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -13,7 +13,8 @@ from ulid import ULID from pyjamaz.constants import MESSAGE_TYPES -from pyjamaz.models.block import Block, Header +from pyjamaz.models.block import Block, Header, TicketEnvelope +from pyjamaz.graypaper_constants import EPOCH_TIMESLOTS from pyjamaz.transport.jamnp_s.stream_base import StreamDirection from pyjamaz.transport.jamnp_s.streams.stream_138 import StreamAuditShardRequest from pyjamaz.transport.jamnp_s.streams.stream_139 import StreamSegmentShardRequest @@ -31,7 +32,7 @@ MsgCE139SegmentRequest, MsgCE139SegmentShard, \ MsgCE140SegmentRequest, MsgCE140SegmentShard, MsgCE140Justification, \ MsgCE144Evidence, \ - MsgCE145JudgmentPublication + MsgCE145JudgmentPublication, MsgCE131SafroleTicket, MsgCE132SafroleTicket from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest from pyjamaz.transport.jamnp_s.streams.stream_131 import StreamSafroleTicketDistributionStep1 from pyjamaz.transport.jamnp_s.streams.stream_132 import StreamSafroleTicketDistributionStep2 @@ -53,40 +54,6 @@ logger = logging.getLogger("pyjamaz.transport.jamnp_s") -#TODO: typings -def create_jam_connection(protocol, quic_connection, direction, host, port): - def create_connection(*args, **kwargs): - conn = quic_connection(*args, **kwargs) - conn.protocol = protocol - conn.direction = direction - - conn.jam_connection_ulid = ULID() - protocol.connections[conn.jam_connection_ulid] = conn - - # Note: for accepting connections addr & port are known after the QUIC handshake, see JAMConnection::HandshakeComplete - if direction == StreamDirection.initiator: - conn.host = host - conn.port = port - protocol.conn_initiated.add(conn.jam_connection_ulid) - else: - protocol.conn_accepted.add(conn.jam_connection_ulid) - - return conn - - return create_connection - - -class SessionTicketStore: - - def __init__(self) -> None: - self.tickets: Dict[bytes, SessionTicket] = {} - - def add(self, ticket: SessionTicket) -> None: - self.tickets[ticket.ticket] = ticket - - def pop(self, label: bytes) -> Optional[SessionTicket]: - return self.tickets.pop(label, None) - class JAMNPS(ProtocolType): @@ -200,7 +167,7 @@ def up0_send_handshake(self, conn: JAMConnection): timeslot=slot, leafs=leafs ) - logger.debug(f"Sending Handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") + logger.debug(f"up0_send_handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") add_stream_type = conn.direction == StreamDirection.initiator @@ -213,13 +180,13 @@ def up0_send_handshake(self, conn: JAMConnection): def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): # Note: For now we employ a very simple strategy, where we sync blocks from the first node that announced a finalized block greater than we have if self.state_requesting_blocks: - logger.debug(f"Skipping handshake block header check, already importing blocks") + logger.debug(f"up0_received_handshake Skipping handshake block header check, already importing blocks") return block = self.app.retrieve_block_by_hash(msg.header_hash) #TODO: missch een efficientere exists check toevoegen #TODO: ook nog iets doen met slot en leafs ook mee requesten? if not block: - logger.debug(f"Received newer block from handshake: {msg.header_hash} -> initiate CE128RequestBlocks") + logger.debug(f"up0_received_handshake Received newer block from handshake: {msg.header_hash} -> initiate CE128RequestBlocks") self.state_requesting_blocks = True curr_hash = self.app.retrieve_block_hash(self.app.state.timeslot.number) # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? @@ -236,7 +203,7 @@ def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement): # Note: For now we employ a very simple strategy, where we sync blocks from the first node that announced a finalized block greater than we have if self.state_requesting_blocks: - logger.debug(f"Skipping block header announcement check, already importing blocks") + logger.debug(f"up0_received_announcement Skipping block header announcement check, already importing blocks") return """ @@ -250,7 +217,7 @@ def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement block = self.app.retrieve_block_by_hash(msg.header.hash) #TODO: missch een efficientere exists check toevoegen #TODO: ook nog iets doen met bijhorende finalized header_hash & slot? if not block: - logger.debug(f"Received new block announcement from up0: {msg.header.hash}") + logger.debug(f"up0_received_announcement Received new block announcement from up0: {msg.header.hash}") self.state_requesting_blocks = True curr_hash = self.app.retrieve_block_hash(self.app.state.timeslot.number) # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? @@ -265,7 +232,7 @@ def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement async def up0_broadcast_block(self, block: Block): - logger.debug(f'UP0 broadcasting block announcement to {len(self.connections)} connections') + logger.debug(f'up0_broadcast_block to {len(self.connections)} connections') msg = MsgUP0Announcement( header=block.header, @@ -274,7 +241,7 @@ async def up0_broadcast_block(self, block: Block): ).to_jam_bytes().to_bytes() for client_id, client in self.connections.items(): - logger.debug(f"UP0 send block header to client {client.host}:{client.port} with hash {block.header.hash}") + logger.debug(f"up0_broadcast_block send block header to client {client.host}:{client.port} with hash {block.header.hash}") client.send( client.stream_up.stream_id, client.stream_up.create_message(msg), @@ -285,7 +252,7 @@ async def up0_broadcast_block(self, block: Block): def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRequest): #TODO: missch een batch param meegeven, zodat we een grotere reeks kunnen binnenhalen maar batchen... stream = conn.open_jam_stream(StreamBlockRequest, direction=StreamDirection.initiator) - logger.debug(f"CE128 initiating block request on stream id: {stream.stream_id} direction: {req.direction}, max_block: {req.max_blocks} header hash: {req.header_hash}") + logger.debug(f"ce128_initiate_block_request on stream id: {stream.stream_id} direction: {req.direction}, max_block: {req.max_blocks} header hash: {req.header_hash}") conn.send( stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), @@ -295,7 +262,7 @@ def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRe def ce128_received_block_request(self, stream: StreamBlockRequest, req: MsgCE128BlockRequestResponse): blocks = req.blocks - logger.debug(f"CE128 received block request (parsing {len(blocks)} blocks)") + logger.debug(f"ce128_received_block_request request (parsing {len(blocks)} blocks)") asyncio.create_task( self.app.import_queue_add_blocks( blocks, @@ -337,11 +304,11 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 next_hash = block.header.parent else: # TODO: error - raise Exception("HMmmmmmz?????") + raise Exception("ce128_send_block_request HMmmmmmz?????") blocks.append(block) - logger.debug(f"CE128 sending {len(blocks)} blocks (direction={block_req.direction} max blocks={block_req.max_blocks})") + logger.debug(f"ce128_send_block_request {len(blocks)} blocks (direction={block_req.direction} max blocks={block_req.max_blocks})") if blocks: stream.conn.send( @@ -354,13 +321,13 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 if not block or block_req.header_hash == last_block_hash or block.header.timeslot == 0: #TODO: PolkaJAM lijkt in dit geval een reset te sturen???? #TODO: ook op max_blocks checken -> bijhouden op de stream? - logger.debug(f"CE128 block request finished") + logger.debug(f"ce128_send_block_request finished") # stream.acceptor_reset(1) # Replaced with FIN stream.conn.send(stream.stream_id, b'', end_stream=True) def ce128_abort_block_request(self): - logger.debug(f"Finished block request, start parsing import queue {len(self.app._import_queue)}") + logger.debug(f"ce128_abort_block_request Finished, start parsing import queue {len(self.app._import_queue)}") # Note: could happen during a block_request "session" #TODO: or do we want to clear the queue? asyncio.create_task( @@ -377,47 +344,130 @@ async def ce128_processed_block_request(self, *args, **kwargs): def up0_failure(self, reset_code: int): - logger.error(f"UP0 failure with code {reset_code}") + logger.error(f"up0_failure with code {reset_code}") # TODO: handle UP0 reset, e.g., reconnect def ce128_block_request_failure(self, reset_code: int): - logger.error(f"CE128 block request failed with code {reset_code}") + logger.error(f"ce128_block_request_failure with code {reset_code}") self.state_requesting_blocks = False # Reset state on failure - def ce131_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE131SafroleTicketDistribution): - stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep1, direction=StreamDirection.initiator) - logger.debug(f"CE131 initiating ticket distribution on stream id: {stream.stream_id}") - conn.send( - stream.stream_id, - stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), - end_stream=True + def ce131_initiate_distribute_own_ticket(self, data): + #TODO: wrap in a dataclass and pass that in the signal + epoch_index: int = data[0] + attempt: int = data[1] + proof: bytes = data[2] + + msg = MsgCE131SafroleTicketDistribution( + epoch_index=epoch_index, + ticket=MsgCE131SafroleTicket( + attempt=attempt, + proof=proof + ) ) + distributed_count = 0 + for conn in self.connections.values(): + if conn.is_connected(): + try: + stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep1, direction=StreamDirection.initiator) + logger.debug(f"ce131_initiate_distribute_own_ticket on stream id: {stream.stream_id}") + conn.send( + stream.stream_id, + stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True + ) + + distributed_count += 1 + except Exception as e: + logger.error(f"Failed to distribute ticket to peer: {e}") + + logger.info(f"ce131_initiate_distribute_own_ticket Distributed own ticket to {distributed_count} peers") + return distributed_count + def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, msg: MsgCE131SafroleTicketDistribution): - logger.debug(f"CE131 received ticket for epoch {msg.epoch_index}") - # TODO: verify proof, check if proxy - if not self.verify_ticket_proof(msg): # Example validation - raise ValueError("Invalid ticket proof") - # forward via CE132 if valid + logger.debug(f"ce131_received_ticket for epoch {msg.epoch_index}") + + # Validate epoch + current_epoch = self.app.state.timeslot.number // EPOCH_TIMESLOTS + if msg.epoch_index != current_epoch and msg.epoch_index != current_epoch + 1: + logger.warning(f"ce131_received_ticket Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") + stream.reset(1) + return + + # Verify ticket + #TODO: create helper function from this + try: + # Get ring public keys from current validators + ring_public_keys = [v.bandersnatch for v in self.app.state.safrole.validators] + + # Get entropy for the epoch + entropy = self.app.state.entropy.entropy[2] + + # Validate the ticket using the block extrinsic accumulator + #TODO: duplicate with Safrole.create_ticket_body??? + ticket_body = self.app.block_extrinsic.create_ticket_body( + TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof), + ring_public_keys, + entropy + ) + + # Check if we already have this ticket + if ticket_body in self.app.state.safrole.ticket_accumulator: + logger.debug(f"ce131_received_ticket Ticket already in accumulator") + stream.reset(2) + return + + # Add ticket to our accumulator + #TODO: ticketbody can be reused from above + self.app.block_extrinsic.add_ticket( + TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof), + ring_public_keys, + entropy + ) + + logger.info(f"ce131_received_ticket added for epoch {msg.epoch_index}") + + #TODO: check if we should forward to (proxy) nodes via CE132 + # Forward to other connections via CE132 + # for conn in self.connections.values(): + # if conn != stream.conn and conn.is_connected(): + # self.ce132_initiate_ticket_distribution( + # conn, + # MsgCE132SafroleTicketDistribution( + # epoch_index=msg.epoch_index, + # ticket=MsgCE132SafroleTicket( + # attempt=msg.ticket.attempt, + # proof=msg.ticket.proof + # ) + # ) + # ) + # logger.debug(f"ce131_received_ticket Forwarded ticket to {len(self.connections) - 1} peers via CE132") + + except ValueError as e: + #TODO: register this, detect bad nodes + logger.error(f"ce131_received_ticket Invalid ticket: {e}") + stream.reset(3) + return + stream.conn.send(stream.stream_id, b'', end_stream=True) def ce131_ticket_distribution_success(self, reset_code: int): - logger.debug(f"CE131 ticket distribution successful with code {reset_code}") + logger.debug(f"ce131_ticket_distribution_success with code {reset_code}") # TODO: handle success, perhaps update state def ce131_ticket_distribution_failure(self, reset_code: int): - logger.error(f"CE131 ticket distribution failed with code {reset_code}") + logger.error(f"ce131_ticket_distribution_failure with code {reset_code}") # TODO: handle failure, e.g., retry or log def ce132_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE132SafroleTicketDistribution): stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep2, direction=StreamDirection.initiator) - logger.debug(f"CE132 initiating ticket distribution on stream id: {stream.stream_id}") + logger.debug(f"ce132_initiate_ticket_distribution on stream id: {stream.stream_id}") conn.send( stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), @@ -426,31 +476,74 @@ def ce132_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE132S def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, msg: MsgCE132SafroleTicketDistribution): - logger.debug(f"CE132 received ticket for epoch {msg.epoch_index}") - # TODO: verify and process ticket - # stream.acceptor_reset(0) + logger.debug(f"ce132_received_ticket for epoch {msg.epoch_index}") + + # Verify epoch index + current_epoch = self.app.state.timeslot.number // EPOCH_TIMESLOTS + if msg.epoch_index != current_epoch and msg.epoch_index != current_epoch + 1: + logger.warning(f"ce132_received_ticket Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") + stream.reset(1) + return + + # Verify ticket proof + #TODO: duplicate code from CE131 for now, make helper function + try: + # Get ring public keys from current validators + ring_public_keys = [v.bandersnatch for v in self.app.state.safrole.validators] + + # Get entropy for the epoch + entropy = self.app.state.entropy.entropy[2] + + # Validate the ticket + ticket_body = self.app.block_extrinsic.create_ticket_body( + TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof), + ring_public_keys, + entropy + ) + + # Check if we already have this ticket + if ticket_body in self.app.state.safrole.ticket_accumulator: + logger.debug(f"ce132_received_ticket Ticket already in accumulator (via CE132), ignoring") + # Don't reset, just close normally as this is expected in CE132 + stream.conn.send(stream.stream_id, b'', end_stream=True) + return + + # Add ticket to our accumulator + self.app.block_extrinsic.add_ticket( + TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof), + ring_public_keys, + entropy + ) + + logger.info(f"ce132_received_ticket Valid ticket received via CE132 (forwarded) for epoch {msg.epoch_index}") + + except ValueError as e: + logger.error(f"ce132_received_ticket Invalid ticket: {e}") + stream.reset(3) + return + stream.conn.send(stream.stream_id, b'', end_stream=True) def ce132_ticket_distribution_success(self, reset_code: int): - logger.debug(f"CE132 ticket distribution successful with code {reset_code}") + logger.debug(f"ce132_ticket_distribution_success with code {reset_code}") # TODO: handle success, perhaps update state def ce132_ticket_distribution_failure(self, reset_code: int): - logger.error(f"CE132 ticket distribution failed with code {reset_code}") + logger.error(f"ce132_ticket_distribution_failure with code {reset_code}") # TODO: handle failure def ce134_initiate_sharing(self, conn: JAMConnection, sharing: MsgCE134WorkPackageSharing, bundle: MsgCE134WorkPackageBundle): stream = conn.open_jam_stream(StreamWorkPackageSharing, direction=StreamDirection.initiator) - logger.debug(f"CE134 initiating sharing on stream id: {stream.stream_id}") + logger.debug(f"ce134_initiate_sharing on stream id: {stream.stream_id}") conn.send(stream.stream_id, stream.create_message(sharing.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) conn.send(stream.stream_id, stream.create_message(bundle.to_jam_bytes().to_bytes()), end_stream=True) def ce134_received_sharing(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageSharing): - logger.debug(f"CE134 received sharing for core {msg.core_index}") + logger.debug(f"ce134_received_sharing for core {msg.core_index}") # TODO: process mappings @@ -779,4 +872,38 @@ def verify_ticket_proof(self, msg): return True def verify_bundle(self, msg): - return True \ No newline at end of file + return True + + +def create_jam_connection(protocol: JAMNPS, quic_connection: JAMConnection, direction: StreamDirection, host: str, port: int): + def create_connection(*args, **kwargs): + conn = quic_connection(*args, **kwargs) + conn.protocol = protocol + conn.direction = direction + + conn.jam_connection_ulid = ULID() + protocol.connections[conn.jam_connection_ulid] = conn + + # Note: for accepting connections addr & port are known after the QUIC handshake, see JAMConnection::HandshakeComplete + if direction == StreamDirection.initiator: + conn.host = host + conn.port = port + protocol.conn_initiated.add(conn.jam_connection_ulid) + else: + protocol.conn_accepted.add(conn.jam_connection_ulid) + + return conn + + return create_connection + + +class SessionTicketStore: + + def __init__(self) -> None: + self.tickets: Dict[bytes, SessionTicket] = {} + + def add(self, ticket: SessionTicket) -> None: + self.tickets[ticket.ticket] = ticket + + def pop(self, label: bytes) -> Optional[SessionTicket]: + return self.tickets.pop(label, None) From fcea46be087a14d8dc3a439bd4467b67fa2f3269 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 17 Jul 2025 20:09:08 +0200 Subject: [PATCH 25/51] JAMNP WIP --- pyjamaz/transport/jamnp_s/protocol.py | 6 +++--- pyjamaz/transport/jamnp_s/streams/stream_144.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 7cc43c3e..f077a396 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -31,7 +31,7 @@ MsgCE138ShardRequest, MsgCE138BundleShard, MsgCE138Justification, \ MsgCE139SegmentRequest, MsgCE139SegmentShard, \ MsgCE140SegmentRequest, MsgCE140SegmentShard, MsgCE140Justification, \ - MsgCE144Evidence, \ + MsgCE144Announcement, MsgCE144Evidence, \ MsgCE145JudgmentPublication, MsgCE131SafroleTicket, MsgCE132SafroleTicket from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest from pyjamaz.transport.jamnp_s.streams.stream_131 import StreamSafroleTicketDistributionStep1 @@ -825,13 +825,13 @@ def ce140_request_failure(self, code: int): logger.error(f"CE140 failure {code}") - def ce144_initiate_announcement(self, conn: JAMConnection, ann: MsgCE144AuditAnnouncement, evidence: MsgCE144Evidence): + def ce144_initiate_announcement(self, conn: JAMConnection, ann: MsgCE144Announcement, evidence: MsgCE144Evidence): stream = conn.open_jam_stream(StreamAuditAnnouncement, direction=StreamDirection.initiator) conn.send(stream.stream_id, stream.create_message(ann.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) conn.send(stream.stream_id, stream.create_message(evidence.to_jam_bytes().to_bytes()), end_stream=True) - def ce144_received_announcement(self, stream: StreamAuditAnnouncement, msg: MsgCE144AuditAnnouncement): + def ce144_received_announcement(self, stream: StreamAuditAnnouncement, msg: MsgCE144Announcement): # TODO: process announcement pass diff --git a/pyjamaz/transport/jamnp_s/streams/stream_144.py b/pyjamaz/transport/jamnp_s/streams/stream_144.py index fbd3c7cc..ceca23e3 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_144.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_144.py @@ -2,7 +2,7 @@ from jamcodec.base import JamBytes -from pyjamaz.transport.jamnp_s.message_types import MsgCE144AuditAnnouncement, MsgCE144Evidence +from pyjamaz.transport.jamnp_s.message_types import MsgCE144Announcement, MsgCE144Evidence from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -31,7 +31,7 @@ def initiator_message(self, data: bytes): def acceptor_message(self, data: bytes): if not self.received_announcement: logger.debug(f"CE144 acceptor received announcement") - msg = MsgCE144AuditAnnouncement.from_jam_bytes(JamBytes(data)) + msg = MsgCE144Announcement.from_jam_bytes(JamBytes(data)) self.protocol.ce144_received_announcement(self, msg) self.received_announcement = True else: From 175a68fcc081ca12e9e6f7f181c88c071f84c1fd Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 17 Jul 2025 20:14:03 +0200 Subject: [PATCH 26/51] - JAMNP WIP --- pyjamaz/cli.py | 10 ---------- pyjamaz/extrinsic.py | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index c5fa1902..2af8bc33 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -29,8 +29,6 @@ from pyjamaz.models.app import Trace, StateDump, ChainspecDump from pyjamaz.transport.rpc.ws_server import start_rpc_server, WebSocketServer from pyjamaz.settings import GP_VERSION, SOLO_MODE, APP_VERSION -from pyjamaz.state.base import state_key_constructor_service_account, state_key_constructor_preimage, \ - state_key_constructor_preimage_availability from pyjamaz.storage import LevelDBStorage, InMemoryStorage, TransactionRolledBack from pyjamaz.models.block import Block, Header, Extrinsic from pyjamaz.transport.cert import generate_cert, write_cert @@ -251,14 +249,6 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, # Start WebSocket server tg.start_soon(start_rpc_server, rpc_server) - # if block_dir: - # logging.info(f"👀 Watching directory: {block_dir} for new blocks...") - # fs_protocol = FSProtocol(block_dir, app) - # app.protocol = fs_protocol - # app.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, wrap_produced_block_fs(app, record_traces, fs_protocol)) - # app.pubsub.subscribe(MESSAGE_TYPES.REQUESTED_BLOCKS, app.requested_blocks_from_json) - # tg.start_soon(fs_protocol.listen) - # else: certificate_file = os.path.join(db_path, "cert.pem") pk_file = os.path.join(db_path, "cert.key") #nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app) diff --git a/pyjamaz/extrinsic.py b/pyjamaz/extrinsic.py index 1840d67c..7973c231 100644 --- a/pyjamaz/extrinsic.py +++ b/pyjamaz/extrinsic.py @@ -84,7 +84,7 @@ async def add_own_ticket( self.tickets_queue[ticket_id] = ticket self.own_tickets_next.append(ticket_id) - # Distribute ticket to peers if requested + # Notify new ticket is added if pubsub and epoch_index is not None: await pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.TICKET_ADD, data=[epoch_index, attempt, signature])) From 16ebd86d187d7ba1067c4f22edd7e4cacd1f4f53 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 17 Jul 2025 20:36:20 +0200 Subject: [PATCH 27/51] JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 5 +++++ pyjamaz/transport/jamnp_s/protocol.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 2483808b..1bca599f 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -49,6 +49,11 @@ def close_jam_stream(self, stream: Stream, reason:int=0, clean_close: bool = Fal del self.streams[stream.stream_id] + def is_connected(self) -> bool: + """Check if the connection is active and ready""" + return self._quic is not None and not self._quic._close_pending + + def send(self, stream_id: int, data: bytes, end_stream=False): try: self._quic.send_stream_data( diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index f077a396..0ffd14c4 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -353,7 +353,7 @@ def ce128_block_request_failure(self, reset_code: int): self.state_requesting_blocks = False # Reset state on failure - def ce131_initiate_distribute_own_ticket(self, data): + async def ce131_initiate_distribute_own_ticket(self, data): #TODO: wrap in a dataclass and pass that in the signal epoch_index: int = data[0] attempt: int = data[1] From e7f9aa3f6cc024efefafa3cf4ccd57ef8a8fe2bb Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 17 Jul 2025 20:50:51 +0200 Subject: [PATCH 28/51] JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 2 +- pyjamaz/transport/jamnp_s/streams/stream_0.py | 20 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 1bca599f..9e11eed1 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -139,7 +139,7 @@ def quic_event_received(self, event: QuicEvent) -> None: # If we're on the acceptor side of this stream, send a handshake message back if self.stream_up is None or self.stream_up.stream_id < stream_id: self.stream_up = stream_obj - self.protocol.up0_send_handshake(self) + # Note: handshake will be sent after processing the incoming message # Only the first time an acceptor receives a message, we expect a stream id byte data = data[1:] diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py index 36d0c4b2..ec308d77 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -49,13 +49,31 @@ def initiator_reset(self, reset_code: int): self.protocol.up0_failure(reset_code) super().initiator_reset(reset_code) + def acceptor_reset(self, reset_code: int): self.protocol.up0_failure(reset_code) super().reset(reset_code) + def acceptor_message(self, data: bytes): # Note: in case of UP0, initiator and acceptor have symmetrical message flow - self.initiator_message(data) + + match self.state: + case UPState.IN_PROGRESS: + self.state = UPState.CONNECTED + + msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) + self.protocol.up0_received_handshake(self.conn, msg) + self.protocol.up0_send_handshake(self.conn) + + case UPState.CONNECTED: + # After handshake is completed, we receive Announcement messages + msg = MsgUP0Announcement.from_jam_bytes(JamBytes(data)) + self.protocol.up0_received_announcement(self.conn, msg) + + case _: + raise RuntimeError(f"Unexpected state {self.state}") + def handle_fin(self): logger.warning(f"Unexpected FIN on persistent UP0 stream {self.stream_id}") From 0c7cd06fbce46c4cbdcc1de585be28b065472380 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 17 Jul 2025 20:51:55 +0200 Subject: [PATCH 29/51] - JAMNP WIP --- pyjamaz/cli.py | 7 ++--- pyjamaz/data/chainspecs/dev-spec.json | 38 ++++++++++++------------- pyjamaz/transport/jamnp_s/connection.py | 8 +++--- pyjamaz/transport/jamnp_s/protocol.py | 9 ++++-- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 2af8bc33..3db56226 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -166,8 +166,7 @@ async def main(): # @click.pass_context @main.command(name='run', help='Run a Pyjamaz JAM node') -@click.option('--seed', type=str, - help='Seed to generate validator keys') +@click.option('--seed', type=str, help='Seed to generate validator keys') @click.option('--port', type=int, default=9000, show_default=True, help='UDP port on which the validator should run') @click.option('--ts', type=int, help='Unix timestamp for when the validator starts.') @click.option('--culprit', is_flag=True, help="Culprit mode: node will intentionally act malicious") @@ -280,7 +279,7 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, #await nps_protocol.connect(conn["addr"], conn["port"]) #await nps_protocol.connect("54.39.18.64", 40000) await nps_protocol.connect("localhost", conn["port"]) - #await nps_protocol.connect("127.0.0.1", 40000) #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + #await nps_protocol.connect("127.0.0.1", 40001) #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! except Exception as exc: traceback.print_exc() else: @@ -300,7 +299,7 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, # # logging.debug(f'Connecting to node {validator_address}:{validator_port}') # tg.start_soon(nps_protocol.connect, validator_address, validator_port) - tg.start_soon(nps_protocol.connect, "127.0.0.1", 40000) + tg.start_soon(nps_protocol.connect, "127.0.0.1", 40001) pass await anyio.sleep(ts - time.time()) diff --git a/pyjamaz/data/chainspecs/dev-spec.json b/pyjamaz/data/chainspecs/dev-spec.json index cee1e769..19d4b9b1 100644 --- a/pyjamaz/data/chainspecs/dev-spec.json +++ b/pyjamaz/data/chainspecs/dev-spec.json @@ -1,28 +1,28 @@ { + "genesis_header": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25bb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aaff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499acedee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d9339326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e3410746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a2105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c060000ffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "genesis_state": { - "08000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "05000000000000000000000000000000000000000000000000000000000000": "00000000", - "0b000000000000000000000000000000000000000000000000000000000000": "00000000", + "06000000000000000000000000000000000000000000000000000000000000": "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25bb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aae88bd757ad5b9bedf372d8d3f0cf6c962a469db61a265f6418e1ffed86da29ec", + "02000000000000000000000000000000000000000000000000000000000000": "a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8", "04000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000af39b7de5fcfb9fb8a46b1645310529ce7d08af7301d9758249da4724ec698eb127f489b58e49ae9ab85027509116962a135fc4d97b66fbbed1d3df88cd7bf5cc6e5d7391d261a4b552246648defcb64ad440d61d69ec61b5473506a48d58e1992e630ae2b14e758ab0960e372172203f4c9a41777dadd529971d7ab9d23ab29fe0e9c85ec450505dde7f5ac038274cf012105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b30746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981cc0746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981cc2105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392e9326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b662105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392e151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b0746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981cc151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40bff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b30746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981cc00", - "0e000000000000000000000000000000000000000000000000000000000000": "000000000000000000000000", - "0f000000000000000000000000000000000000000000000000000000000000": "000000000000000000000000", + "03000000000000000000000000000000000000000000000000000000000000": "01b5af8edad70d962097eefa2cef92c8284cf0a7578b70a6b7554cf53ae6d5122200000000000000000000000000000000000000000000000000000000000000000000", + "ff000000000000000000000000000000000000000000000000000000000000": "bc804dccb6bae35cd48cc1e11e426e7ba218727e2ba7dd43e8163f4f5d06333fffffffffffffffff0a000000000000000a00000000000000c97102000000000004000000", "00e800c0000100009365e44fe301c284baad2fb8fbf048d1deb729ca23e5c3": "0100000000", - "02000000000000000000000000000000000000000000000000000000000000": "a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8", - "09000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0f000000000000000000000000000000000000000000000000000000000000": "000000000000000000000000", + "05000000000000000000000000000000000000000000000000000000000000": "00000000", + "0a000000000000000000000000000000000000000000000000000000000000": "0000", "0d000000000000000000000000000000000000000000000000000000000000": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "00fe00ff00ff00ff804dccb6bae35cd48cc1e11e426e7ba218727e2ba7dd43": "5000156a616d2d626f6f7473747261702d7365727669636506302e312e32330a4170616368652d322e30012550617269747920546563686e6f6c6f67696573203c61646d696e407061726974792e696f3e28330070270000000020000200ffff1c00ffff1400ffff1800ffff0c00ffff2200ffff3c00ffff1a00ffff4600ffff1000ffff4200ffff0800ffff0e00ffff2a00ffff0600ffff1600ffff0400ffff1e00ffff3e00ffff4c00ffff2000ffff2600ffff4800ffff5000ffffe200ffffec00ffffec00ffffec00ffffec00ffffe400ffffec00ffffec00ffffec00ffffda00ffffde00ffffda00ffffda00ffffda00ffffec00ffffec00ffffec00ffffec00ffffdc00ffffe800ffffec00ffffec00ffffdc00ffff6a01ffffb001ffff9c01ffffa201ffff8601ffffc801ffffdc01ffffa801fffff001ffff9601ffffea01ffff8401ffff8e01ffffd601ffff7e01ffffa001ffff7c01ffffba01ffffe401ffff0202ffffc201ffffce01fffffa01ffff0802ffffce01ffffde01ffffd601ffffd801ffffd001ffff2a02ffff6a02ffffa602ffff8e02ffff5e02ffff7002ffffb202ffffba02ffffde02ffffd402ffffd402ffffc802ffff5202ffff7a02ffff6402ffff4a02ffffb803ffff5002ffff4602ffff3402ffffc002ffffd402ffffbc02ffff9a02ffffa603ffffd603ffffcc03ffffd203ffffbc03ffffe003fffff603ffffd403ffff0004ffffc403fffffe03ffffb403ffffbe03ffffe403ffffb003ffffd003ffffae03ffffda03fffffa03ffff0604ffffde03ffffe203ffff0404ffff0a04ffff0c05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff1605ffff1005ffff0e05ffff0e05ffff1405ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff1805ffff0e05ffff0e05ffff0e05ffff0e05ffff1205ffffb004ffffb004ffffb004ffffae04ffffac04ffffae04ffffac04ffffb004ffffb004ffffb004ffffb004ffffb004ffffac04ffffac04ffffb004ffffac04ffffb004ffffac04ffff7604ffff7604ffff7604ffff7404ffff7004ffff7404ffff7004ffff7604ffff7604ffff7604ffff7604ffff7604ffff7004ffff7004ffff7604ffff7004ffff7604ffff7004ffff3204ffff3204ffff3204ffff3004ffff2a04ffff3004ffff2a04ffff3204ffff3204ffff3204ffff3204ffff3204ffff2a04ffff2a04ffff3204ffff2a04ffff3204ffff2a04ffff2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7665632f737065635f66726f6d5f697465725f6e65737465642e72734661696c757265206f6e6c7920696e2063617365206f6620626164206d656d6f7279206f722062616420736572766963652049443b20626f74682061726520676f6f643b207165642f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f686f73745f63616c6c732e727363616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c75652f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f6d656d2e72732829436f6d7061637420656e636f646573206c656e677468696e666f6d656d6f4974657268617368636f72656b65797369746572626f6f7472656164536f6d65206f6620203c3d207472756573656564202d3e204e6f6e654974656d202020206461746144726f704f6b4572724f706171756556616c4b65797365746564323535313962616e646572736e61746368626c73636f64655f6c656e496d706f727465646e625f6974656d736175746f5f6163636d657461646174616578706f7274656420286279746573204578706f727465645472616e7366657262616c616e63653f696d706f72746564526573756c743a207472616e736665724c6f6f6b65645570707265696d61676550726f7669646564636f64655f686173686d696e5f6974656d5f6761736d696e5f6d656d6f5f676173656e646f776d656e74726567697374726174696f6e437265617465536572766963655570677261646564657374696e6174696f6e616d6f756e746761735f6c696d69745a6f6d62696679656a6563746f72456a65637474617267657444656c6574654974656d7373746f726167655f6974656d73536f6c696369746c656e466f726765744c6f6f6b7570736572766963656561676572496d706f72746974656d734578706f7274426c6573736d616e6167657241737369676e717565756544657369676e6174655969656c64436865636b706f696e7450616e696350726f76696465736572766963655f6964636f756e7452616e646f6d53746f72616765526566696e6552616e646f6d53746f72616765416363756d756c61746542656e63686d61726b4d61705b5d2f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d74797065732f7372632f66697865645f7665632e7273436c6f6e65646974556e72657175657374457870756e67654e6f74596574556e72657175657374737563636573735f61667465724e6f74596574457870756e6765556e70726f766964656473696e6365556e72657175657374656470726f76696465645f73696e6365756e7265717565737465645f73696e63655265726571756573746564756e7265717565737465645f617472657265717565737465645f73696e63656b6579496e7075744f7574707574426f6f747374726170205365727669636520526566696e652c206852657475726e696e672000000029207768656e20736c6963696e672060207769746820636f64655f68617368204372656174656420736572766963652020696e746f20616363756d756c61746572616e676520656e6420696e646578204578706f7274656420736c6963653a2001000000000000000000000000000000736572766963655f726567697374727944657369676e61746564206b65797320303132333435363738396162636465664561676572206c6f6f6b75702c20676f742073657276696365732f626f6f7473747261702d736572766963652f7372632f6c69622e7273426f6f747374726170205365727669636520416363756d756c6174652c2068204020244465636f64656420696e737472756374696f6e3a20436f64652070726f766973696f6e20726573756c74656420696e20417474656d74696e67207472616e736665722c206761733d5472616e73666572206f66202077697468202067617320726573756c74656420696e204661696c656420746f20637265617465215570677261646564214761732072656d61696e696e673a20417474656d7074696e67207472616e736665723a20205a6f6d62696679696e6720736572766963652e20456a6563746f723a20235a6f6d6269666965644661696c656420746f207a6f6d62696679202d20696e76616c696420636f64655f686173683f4661696c656420746f207a6f6d62696679202d206c6167676172647320696e2073746f726167652f6c6f6f6b75703f456a656374696e6720736572766963652023206974656d732064656c65746564207375636365737366756c6c792c20206b657973206e6f7420666f756e6444656c6574696e67206974656d3a204661696c656420746f2072656d6f7665206974656d3a20536f6c69636974656420206f66206c656e67746820726571756573746564517565727920726573756c743a20202866693a20756e7265717565737465644c6f6f6b75702c20676f74206c6f6f6b65645f7570426c6573736564207365727669636573206d3a20232c20613a20232e20763a20232e2061613a2041737369676e696e6720636f72652020746f2071756575652041737369676e6564214661696c656420746f2061737369676e215969656c646564206861736820436865636b706f696e7465642150616e696320696e737472756374696f6e2065786563757465642150726f766964656420707265696d61676520746f20496d706f72746564206461746120696d706f72742d4578706f7274656420206974656d73636f756e745f72616e646f6d5f73746f726167656974656d2d2d62616c616e6365206c6f77496e737472756374696f6e206e6f742068616e646c65643a207472616e736665727265647472616e736665722d636f756e745265636569766564207472616e736665722066726f6d202077697468206d656d6f20000b000000000000000c000000000000000b00000000000000070000000000000006000000000000000e000000000000000d000000000000006361706163697479206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72736d656d6f727920616c6c6f636174696f6e206f6620206279746573206661696c65642f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f737472696e672e72736120666f726d617474696e6720747261697420696d706c656d656e746174696f6e2072657475726e656420616e206572726f72207768656e2074686520756e6465726c79696e672073747265616d20646964206e6f742f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f666d742e7273292e2e3a5b63616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c756570616e69636b6564206174203a0a696e646578206f7574206f6620626f756e64733a20746865206c656e20697320206275742074686520696e646578206973203a20207b202c20207b0a2c0a7d207d28280a2c0a5d2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6e756d2e727330303031303230333034303530363037303830393130313131323133313431353136313731383139323032313232323332343235323632373238323933303331333233333334333533363337333833393430343134323433343434353436343734383439353035313532353335343535353635373538353936303631363236333634363536363637363836393730373137323733373437353736373737383739383038313832383338343835383638373838383939303931393239333934393539363937393839392f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6d6f642e727366616c736572616e676520737461727420696e64657820206f7574206f662072616e676520666f7220736c696365206f66206c656e67746820736c69636520696e64657820737461727473206174202062757420656e6473206174202f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f6d6f642e7273010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002020202020202020202020202020202020202020202020202020202020203030303030303030303030303030303040404040400000000000000000000005b2e2e2e5d626567696e203c3d20656e642028606279746520696e64657820206973206e6f742061206368617220626f756e646172793b20697420697320696e736964652029206f662060206973206f7574206f6620626f756e6473206f6620602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f756e69636f64652f7072696e7461626c652e7273000601010301040205070702080809020a050b020e04100111021205131c140115021702190d1c051d081f0124016a046b02af03b102bc02cf02d102d40cd509d602d702da01e005e102e704e802ee20f004f802fa04fb010c273b3e4e4f8f9e9e9f7b8b9396a2b2ba86b1060709363d3e56f3d0d1041418363756577faaaeafbd35e01287898e9e040d0e11122931343a4546494a4e4f64658a8c8d8fb6c1c3c4c6cbd65cb6b71b1c07080a0b141736393aa8a9d8d909379091a8070a3b3e66698f92116f5fbfeeef5a62f4fcff53549a9b2e2f2728559da0a1a3a4a7a8adbabcc4060b0c151d3a3f4551a6a7cccda007191a22253e3fe7ecefffc5c604202325262833383a484a4c50535556585a5c5e606365666b73787d7f8aa4aaafb0c0d0aeaf6e6fddde935e227b0503042d036603012f2e80821d03310f1c0424091e052b0544040e2a80aa06240424042808340b4e03340c813709160a08183b45390363080930160521031b05014038044b052f040a070907402027040c0936033a051a07040c07504937330d33072e080a0626031d080280d0521003372c082a161a261c1417094e042409440d19070a0648082709750b423e2a063b050a06510601051003050b5908021d621e48080a80a65e22450b0a060d133a060a06141c2c041780b93c64530c48090a46451b4808530d49070a80b6220e0a06460a1d03474937030e080a0639070a813619073b031d55010f320d839b66750b80c48a4c630d843010160a8f9b0582479ab93a86c68239072a045c06260a460a28051381b03a80c65b654b0439071140050b020e97f80884d6290aa2e781330f011d060e0408818c89046b050d030907108f6080fa0681b44c4709743c80f60a73087015467a140c140c570919808781470385420f1584501f060680d52b053e2101702d031a040281401f113a050181d02a80d62b040181e080f7294c040a04028311444c3d80c23c06010455051b3402810e2c04640c560a80ae381d0d2c040907020e06809a83d80411030d0377045f060c04010f0c0438080a0628082c04023e81540c1d030a0538071c06090780fa840600010305050606020706080709110a1c0b190c1a0d100e0c0f0410031212130916011704180119031a071b011c021f1620032b032d0b2e01300431023201a704a902aa04ab08fa02fb05fd02fe03ff09ad78798b8da23057588b8c901cdd0e0f4b4cfbfc2e2f3f5c5d5fe2848d8e9192a9b1babbc5c6c9cadee4e5ff00041112293134373a3b3d494a5d848e92a9b1b4babbc6cacecfe4e500040d0e11122931343a3b4546494a5e646584919b9dc9cecf0d11293a3b4549575b5c5e5f64658d91a9b4babbc5c9dfe4e5f00d11454964658084b2bcbebfd5d7f0f183858ba4a6bebfc5c7cfdadb4898bdcdc6cecf494e4f57595e5f898e8fb1b6b7bfc1c6c7d71116175b5cf6f7feff806d71dedf0e1f6e6f1c1d5f7d7eaeaf4dbbbc16171e1f46474e4f585a5c5e7e7fb5c5d4d5dcf0f1f572738f747596262e2fa7afb7bfc7cfd7df9a00409798308f1fcecfd2d4ceff4e4f5a5b07080f10272feeef6e6f373d3f42459091536775c8c9d0d1d8d9e7feff00205f2282df048244081b04061181ac0e80ab051f08811c03190801042f043404070301070607110a500f1207550703041c0a090308030703020303030c0405030b06010e15054e071b0757070206170c500443032d03010411060f0c3a041d255f206d046a2580c80582b0031a0682fd03590716091809140c140c6a060a061a0659072b05460a2c040c040103310b2c041a060b0380ac060a062f3180f4083c030f033e0538082b0582ff1118082f112d03210f210f808c04829a160b158894052f053b07020e180980be22740c80d61a81100580e109f29e033709815c1480b80880dd153b030a06380846080c06740b1e035a0459098083181c0a16094c04808a06aba40c170431a10481da26070c050580a61081f50701202a064c04808d0480be031b030f0d2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f756e69636f64652f756e69636f64655f646174612e7273617474656d707420746f20646976696465206279207a65726f617474656d707420746f2063616c63756c617465207468652072656d61696e646572207769746820612064697669736f72206f66207a65726f0000000003000083042000910560005d13a0001217201f0c20601fef2c202b2a30a02b6fa6602c02a8e02c1efbe02d00fe20369eff6036fd01e136010a2137240de137ab0e61392f18e139301ce14af31ee14e4034a1521e61e153f06a61544f6fe1549dbc615500cf615665d1a15600da215700e0a158aee2215aece4e15bd0e8615c2000ee5cf0017f5d00700007002d0101010201020101480b30151001650702060202010423011e1b5b0b3a09090118040109010301052b033b092a180120370101010408040103070a021d013a0101010204080109010a021a010202390104020402020303011e0203010b0239010405010204011402160601013a0101020104080107030a021e013b0101010c01090128010301370101030503010407020b021d013a01020201010303010407020b021c02390201010204080109010a021d0148010401020301010801510102070c08620102090b0749021b0101010101370e01050102050b0124090166040106010202021902040310040d01020206010f01000300041c031d021e02400201070801020b09012d030101750222017603040209010603db0202013a010107010101010208060a0201301f3104300a040326090c02200402063801010203010105380802029803010d0107040106010302c6400001c32100038d016020000669020004010a200250020001030104011902050197021a120d012608190b01012c03300102040202020124014306020202020c0108012f01330101030202050201012a020801ee010201040100010010101000020001e201950500030102050428030401a5020004410500024f04460b31047b01360f290102020a033104020207013d03240501083e010c0234090101080402015f030204060102019d01030815023902010101010c0109010e07030543010206010102010103040301010e025508020301011701510102060101020101020102eb010204060201021b025508020101026a0101010208650101010204010500090102f5010a040401900402020401200a280602040801090602032e0d010200070106010152160207010201027a06030101020107010148020301010100020b023405050317010001060f000c030300053b0700013f0451010b020002002e021700050306080802071e0494030037043208010e011605010f000701110207010201056401a00700013d040004fe0200076d07006080f00073657276696365732f626f6f7473747261702d736572766963652f636f6d6d6f6e2f7372632f6c69622e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e72732f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e72734572726f722f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6d706163742e72736372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f686f73745f63616c6c732e7273686f73742063616c6c2072657475726e7320636f727265637420747970653b207165646974656d206d75737420626520617661696c61626c653b2071656443616e6e6f74206661696c2065786365707420666f72206d656d6f7279206163636573733b2077652070726f76696465206120676f6f6420616464726573733b2071656443757272656e742073657276696365206d7573742065786973743b207165644661696c757265206f6e6c7920696e2063617365206f6620626164206d656d6f72793b20697420697320676f6f643b2071656450616e69632068616e646c65722063616c6c65642150616e6963206d6573736167653a2050726f746f636f6c20706172616d65746572732061726520696e76616c69646372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f737461727475702e7273756e6b6e6f776e206572726f7220636f64653a206372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f726573756c742e72734f7572206f776e2041504920696d706c2068617320726573756c74656420696e20737563636573732076616c7565207768696368206973206f7574206f662072616e67652e61737369676e64657369676e6174654f75744f66426f756e6473496e646578556e6b6e6f776e53746f7261676546756c6c426164436f72654e6f436173684761734c696d6974546f6f4c6f77416374696f6e496e76616c69646372617465732f6a616d2d74797065732f7372632f73696d706c652e727360726563656e745f626c6f636b5f636f756e7460206d6179206265206e6f206c6172676572207468616e2060426c6f636b496e6465783a3a4d415860202832353529607472616e736665725f6d656d6f5f73697a6560206d75737420626520657175616c20746f7220604d454d4f5f4c454e602028313238296076616c5f636f756e7420213d20636f72655f636f756e74202a2056414c535f5045525f434f524560202833296076616c5f636f756e746020646f6573206e6f7420646976696465206279206056414c535f5045525f434f524560202833296065706f63685f7461696c5f737461727460206d757374206265206c657373207468616e206065706f63685f706572696f646060726f746174696f6e5f706572696f646020646f6573206e6f742064697669646520696e746f206065706f63685f706572696f64606062617369635f70696563655f6c656e6020646f6573206e6f742064697669646520696e746f20605345474d454e545f4c454e602028342c313034296062617369635f70696563655f6c656e60206973206e6f74206576656e2e2e2e223078000b000000000000000c000000000000000b00000000000000070000000000000006000000000000000e000000000000000d0000000000000068030100000000008b000000000000003400000005000000000000000000000001000000000000000100000000000000e8010000000000003b040100000000005b00000000000000f5030000040000000000000000000000000000000000000001000000000000007206000000000000c10401000000000054000000000000002000000009000000701e0100000000005e00000000000000540400002b000000000000000000000020000000000000000100000000000000ba070000000000000000000000000000080000000000000008000000000000003601000000000000000000000000000080000000000000000100000000000000c007000000000000000000000000000008000000000000000800000000000000dc00000000000000e10e010000000000010000000000000071070100000000005500000000000000a600000020000000520f0100000000000200000000000000d31e01000000000060000000000000001401000036000000d31e0100000000006000000000000000150100001000000078080100000000001a000000000000009208010000000000010000000000000093080100000000000a00000000000000d008010000000000100000000000000040090100000000001200000000000000520901000000000025000000000000002600000019000000520901000000000025000000000000003a00000019000000520901000000000025000000000000003100000019000000f008010000000000100000000000000065050100000000000400000000000000520901000000000025000000000000002900000019000000520901000000000025000000000000003c0000001a00000052090100000000002500000000000000220000003400000052090100000000002500000000000000370000003400000077090100000000001e0000000000000095090100000000000300000000000000980901000000000002000000000000009a090100000000001500000000000000c008010000000000100000000000000092080100000000000100000000000000af090100000000001b00000000000000ca090100000000001800000000000000e2090100000000000c00000000000000ee090100000000000600000000000000f40901000000000011000000000000005209010000000000250000000000000072000000460000005209010000000000250000000000000060000000310000001f0a0100000000000f000000000000002e0a0100000000001500000000000000430a0100000000000100000000000000430a0100000000000100000000000000430a0100000000000100000000000000ff050100000000000800000000000000440a0100000000001e00000000000000c00a0100000000001200000000000000b008010000000000100000000000000001000000000000000000000000000000d20a0100000000001d00000000000000ef0a0100000000000f00000000000000fe0a0100000000000f000000000000000d0b0100000000001700000000000000240b0100000000000a000000000000002e0b0100000000000b0000000000000052090100000000002500000000000000bf0000003e00000052090100000000002500000000000000bd0000003600000052090100000000002500000000000000c20000003c000000420b0100000000000e00000000000000500b0100000000000600000000000000dd0e010000000000010000000000000052090100000000002500000000000000ca0000004000000052090100000000002500000000000000c900000035000000610b0100000000000c0000000000000052090100000000002500000000000000d000000042000000760b01000000000015000000000000008b0b0100000000000600000000000000910b0100000000000600000000000000970b01000000000006000000000000009d0b0100000000000f00000000000000ac0b0100000000000a0000000000000020090100000000001000000000000000d00b0100000000000d00000000000000ea0b0100000000001b0000000000000052090100000000002500000000000000fa00000019000000050c01000000000015000000000000004d0f010000000000020000000000000052090100000000002500000000000000d7000000420000001a0c0100000000000e00000000000000280c010000000000070000000000000052090100000000002500000000000000b50000002200000052090100000000002500000000000000b2000000510000002f0c0100000000000900000000000000380c010000000000060000000000000052090100000000002500000000000000ba0000004300000052090100000000002500000000000000080100002200000052090100000000002500000000000000040100004b000000520c0100000000000500000000000000570c010000000000010000000000000052090100000000002500000000000000110100001e000000630c0100000000001900000000000000520901000000000025000000000000004c0000005000000052090100000000002500000000000000850000004f00000052090100000000002500000000000000ff00000049000000950c010000000000170000000000000055050100000000000400000000000000ac0c0100000000000b0000000000000007060100000000000800000000000000520901000000000025000000000000002d0100000e00000052090100000000002500000000000000220100002f00000020210100000000002b210100000000003721010000000000422101000000000049210100000000004f210100000000005d2101000000000074060000000000001800000000000000080000000000000076060000000000007c060000000000008406000000000000f00c0100000000001100000000000000780d01000000000015000000000000008d0d0100000000000d00000000000000010d0100000000007700000000000000b60100000d0000009a0d0100000000007800000000000000ea010000170000000000000000000000000000000000000001000000000000007206000000000000680e01000000000075000000000000007e0200000e0000009a0d01000000000078000000000000008d0500001b00000001000000000000000000000000000000e00e0100000000000100000000000000e00e01000000000001000000000000001b0f01000000000020000000000000003b0f0100000000001200000000000000010000000000000000000000000000004d0f0100000000000200000000000000000000000000000018000000000000000800000000000000ce06000000000000d406000000000000d806000000000000620f01000000000078000000000000005b00000017000000a2100100000000007800000000000000a709000026000000a2100100000000007800000000000000b00900001a0000001f11010000000000120000000000000031110100000000002200000000000000e0080100000000001000000000000000311101000000000022000000000000005311010000000000160000000000000069110100000000000d00000000000000f3120100000000000e0000000000000059050100000000000400000000000000a00801000000000010000000000000000113010000000000010000000000000002130100000000000b000000000000000d130100000000002600000000000000d7050100000000000800000000000000331301000000000006000000000000000113010000000000010000000000000002130100000000000b00000000000000391301000000000016000000000000000113010000000000010000000000000076110100000000007800000000000000f20000002c0000004f1301000000000082000000000000001a000000360000004f1301000000000082000000000000000a0000002b0000007a1901000000000085000000000000004e000000280000007a1901000000000085000000000000005a00000016000000ff190100000000001900000000000000181a0100000000003900000000000000cb1d0100000000002c00000000000000cf00000017000000cb1d0100000000002c00000000000000ed00000013000000f71d01000000000079000000000000002b02000011000000000000000000000008010000000000000800000000000000800700000000000092070000000000009407000000000000d31e01000000000060000000000000002d0100003d000000701e0100000000005e00000000000000920400001e000000701e0100000000005e000000000000006c0400000f000000701e0100000000005e000000000000009e04000010000000701e0100000000005e00000000000000f70000000f000000331f0100000000002700000000000000c90000001c0000000000000000000000000000000000000001000000000000007206000000000000331f0100000000002700000000000000d10000002e000000331f0100000000002700000000000000db0000002e000000331f0100000000002700000000000000150100002b000000331f010000000000270000000000000021010000290000000000000000000000010000000000000001000000000000003a07000000000000331f0100000000002700000000000000e20200000a000000331f0100000000002700000000000000480300000a000000331f01000000000027000000000000006603000020000000331f0100000000002700000000000000a00300000a000000331f0100000000002700000000000000ae0300000e000000331f0100000000002700000000000000150400000a000000331f01000000000027000000000000002804000018000000331f0100000000002700000000000000260400000e000000331f01000000000027000000000000003e04000018000000331f01000000000027000000000000003c0400000e000000331f01000000000027000000000000005c0400000a000000331f01000000000027000000000000006a0400001c000000331f0100000000002700000000000000650400000e0000000000000000000000100000000000000008000000000000009a0700000000000071200100000000002400000000000000040000001400000095200100000000001400000000000000a92001000000000023000000000000002100000012000000cc200100000000004500000000000000a9200100000000002300000000000000560000000d000000a9200100000000002300000000000000820000000d0000006a210100000000001e000000000000009a0100000c0000006a210100000000001e000000000000009d0100000c0000002423010000000000010000000000000024230100000000000100000000000000252301000000000002000000000000002123010000000000030000000000000020210100000000002b210100000000003721010000000000422101000000000049210100000000004f210100000000005d2101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000ff030000000000005000000000000000004b000008000000f065010083e603c1c033f600000d01001c01003e0100c301000c02002302003a0200610200970200ae0200bd0200d30200ea02000103001803007e0300890300bf0300ca0300ef03008d0400d804007105008005008f0500a10500b30500d005000e0600250600300600660600710600a70600fc06000707003d07004807007e0700a607002c0800430800e508003e0a007a0a00a20a00980b00690c005d0d009a0d00060f00270f00840f006112006c1200db1300ef13000416003e1800191a00dd1b007b1c00911c00691d00bc1d00e01d00051e00261e00711e007f1e00851e00a81e00d11e00df1e001e1f00421f00531f00581f005e1f00931f00af1f00f41f00182000342000742000b32000c12000f020002121005021006321008b2100ba2100f121002022003322005622007b22008b2200ca2200ee2200ff2200042300252300d62300fa2300ab2400cf2400d424002025003c25007c2500a02500ae2500da2500112600402600532600822600b32600e22600f526002227005027007f2700922700e22700652800742800992800ba28000529001329001829004529007d2900bd2900e12900f22900212a00522a00812a00942a00a22a00cd2a00ee2a00062b00512b00622b00722c00fa2c001e2d00cd2d00f12d00a02e000c2f00152f002f2f005f2f00612f00772f00a52f00a72f00d52f00f22f000030002830004730005b30006f3000833000973000ab3000bf3000d33000e73000fb30000f31002331003731004b31005f31007331008731009b3100af3100c33100d73100eb3100ff31001332002732003b32004f32006332007732008b32009f3200b33200d432000633002e33005e3300753300ef33000034000a3400223400c134004c3500ef35008a3600323700c337003e3800733800813800ae3800e13800113900243900513900823900b13900c43900103a002c3a006c3a00903a00bb3a00bb3b00293d00cd3d00073e006b3e00933e00063f00293f00b73f00794000fd4100824200aa4200d04200fc42001843005d43008343009f4300df43001e44002c44005a4400764400bb4400e444000045004045007f45008d45000446003e46001947003847005b47006f4700824700e54700a94800e74800514a00a94a00cf4a00144b00914b00bb4b00d44b00ed4b00064c001f4c00384c004f4c008d4c00a14c00c04c00e54c001f4d00824d00a44d00c74d00e14d001b4e003d4e00604e007a4e00b44e00d34e00f84e00324f00a54f00a84f00bc4f00db4f000050003a50005c50007f5000995000d35000fb50001e51003751005151008b5100ad5100d05100ea5100245200435200685200a25200c15200e652002053003f5300605300a85300b45300d35300f85300325400545400775400915400cb5400ea54000f55004955006855008d5500c75500ef55001256002b56004556007f56009e5600bf5600075700135700325700575700915700735900885900b05900cf5900195a002a5a00855a00e65a000f5b00565b00b15b00155c003d5c00845c00a95c00ca5c00155d00235d00405d005c5d00805d00a15d00d95d00f55d00195e003a5e006f5e00b85e00c65e00255f00465f005f5f00b55f00d75f003560005a60007060008960000261005064007964009a6400ac6400bc6400d56400f46400496500af6500d665004066006866007c6600b366008167009d6d00f06d00906e00c07000f370004371008e7100067200387200ed72003e7300807300a57300c873003674005874007d7400be7400de7400ea74001d7500737600b47600cf7600e57600fa76007377009b7700a57700ed77008c7800af7800d07800e37800e67800077900167900227900397900557900b47a00397b00617b009e7b00ef7b00157c00217c00ad7d00d57d00067e00777f00977f009e7f00d88000f380000f8100ad8100dd81003b82006782006a8200a08200f682002283004d83007e8300b18300d48300f883001c84002784007484009c8400a78400e98400028500198500248500848500d985001586002886002b8600628600b88600e086006c8700908700cb8700688900908900aa8900b78900378b00668b00798b00c68b00ee8b00f88b005b8c00c58c001f8d008a8d00a48d00298e003c8e004f8e00988e00c08e00ca8e00d18e000390005090007890009b90000591005e9100779100f291000d92002a9200c49200ec92000993005793006d9300df9300529400729400c49400e49400f094005695007295007e9500ac95002196004996006c9600869600a79600e896005c9700789700849700bf9700cd9700ea9700059800339800859800a59800b19800799900a19900d09900319a000d9c00179c008a9c00b59c00cd9c00d79c00f49c00199d00a19d00c99d00db9d00319e00599e001b9f003b9f00479f005d9f007d9f00a89f00bb9f00cb9f001ca00049a00007a1003ea10061a100cda1004ba20072a2009ea200d8a20004a30056a30073a3008fa300c0a300e0a3000fa40029a40028a50009a700cca70017a8004daa006faa0092aa00b3aa00c5aa0003ab001bab0092ab00aaab00c0ab00deab0049ac0065ac0082ac0099ac0007ad0023ad00daad00f0ad003ab200bbb80024bc0042bc0054bc006cbc00e0bc0007bd00ebbd000bbe006fbe0070bf0013c10056c100a8c200bcc200e1c200f6c200bcc300e2c3000cc4001cc4003ac4007fc40093c400dcc400eec40004c50047c50062c50016c6003bc6006bc600aec600eec60015c70036c7005dc7009bc70019c8005dc800adc900c2c900c5c900e7c9001fca0026ca00f1ca0096cb00b7cb00c8cb00e1cb004dcc007dcc0087cc00bdcc007acd009bcd00adcd00aece00b9ce00dbce002ccf0044cf0089cf00f0cf0060d0006ad00098d000b1d00036d1008ad100d9d100ffd10024d20044d20062d2007cd2009fd200b2d200f8d2004cd30077d3007ad30093d3000dd40032d40077d400a8d400abd40000d50026d50056d5006dd50083d5009fd500b7d500cbd500dfd500f3d50007d6001bd6002fd60043d60057d6006bd6007fd60096d600add600c4d600dbd600efd60003d70017d7002bd70042d70058d70074d7008cd700a0d700b4d700cbd700dfd700f5d70011d80029d8003dd80054d80068d8007fd80093d800a7d800bbd800cfd800e3d800f7d8000bd90021d9003fd90059d9006dd90081d90095d900a9d900e6d90001da0010da003fda0002db000bdc0024dc0034dc0044dc007edc008fdc00a0dc00eedc000cdd001cdd002cdd0045dd0076dd002bde005adf00c7e000dde00013e10077e10090e100a7e100b6e100bee100fce1001be2002ee20052e2005ce20070e20080e20095e200c4e200e4e20043e30054e3007be300b8e300eee300fce30027e40037e4004be400c4e40018e500cce6001ee7004fe70087e7009ee700b1e700bee700e9e7000fe80016e8003de800dee80003e9000ae90073e90099e900e5e9002cea00a8ea00bdeb007dec00ccec00eeec0030ed005ded0073ed009ded00b6ed000eee0023f1003df1006bf100e3f10062f200f0f20048f30058f30097f300b1f300d7f4008ef500ddf700fcf7001ff80039f80061f800cef800e1f800f2f8000bf90037f900c2f90015fb0044fb0079fb00adfb0099fc00f3fc000bfd0034fd009ffd00b8fd0030fe004dfe0079fe00e4fe00fdfe00a8ff00c6ff00dbff00f0ff000500015200018900019d00013302013902014702014d0201530201a00301400401a00601c60601e00601f706010607012107013607014d07016607017808019009019e0901f209018f0a011b0b01770b019a0b01e50b01400c014f0c01570c01640c01900c01e30c01e90c01ee0c01070d011e0d012e0d01480d017e0d01850d018c0d01c50d01cc0d019d0e01350f016d0f01c80f01e71001a11101ca12017a1301e31301f31301d41401e41401ae1501bf1501f21601021701121701221701321701421701691701c91701331801971801cf1801e018010e1901661901801901d41901e61901511a01121b018a1b01d11c01aa1e01d71e01b11f013b20015420017320012b21013e21017f2101242201c32201e622016823018e2301e22301f82301042401b727010b28016f2801be28013f29014e29019d29011e2a01c32b01280abc000028f0db000028cadc00828908330a08330b01aca992009511d87b10207b15187b161082837c307c32017c36027c3c037c3a047b1a087c35057c34067c3a077b1a953a087b8a959af87b8a08510a49330b972208d4020a97661097cc18d46c0cd4ca0a975508821c08d45c0c9744108216976618d44606d46c0c97cc20d4ca0a7c3c089599f79533097b837b89087b7a08787c10018210208215188216109511287b7b3200490701281e03951168fb7b1090047b1588047b168004828908647551097f07648482877c7a959bff9578017b4b08330b177b48acab670797aa02330b000001c8ba0a81aac8ba0a7b14d000320a645764488210900482158804821680049511980428b10e140710000000000000807b57284507645c140a0000000000000080330b2114051800000000000080aeb958067bc5282307330805ac8902077c72017c78027b18507c76037c7c04959afb9578057b483308047b4a08ac8ae1067c7a057b1a407c7a067b1a287c7a077b1a387c7a087b1a30959af7957b097b4b7b4a08531a04ba067c78097b18687c780a7b1888007c780b7b18607c780c7b18209599f395770d7b477b490864477b12187b16107b1c0850100a330e01821ad000837752077a067b15b00083874921c8019519d0013302087b12d0014921d8015107d508330c9788209888201407f8ffffffffffff7f7b174882146882106082168800287c06645764488210900482158804821680049511980428d719645764488210900482158804821680049511980428480a9517300364483300129511d0fe7b1028017b1520017b16180164857b1764873300e00128930d018218300314070000000000000080aa78e2058218400382193803821a30037b58187b59107b5a08140709000000000000807b5728ca05645764488210900482158804821680049511980428841714070f000000000000807b5728a405330a0514081800000000000080aea9e4047b58288e0564576448821090048215880482168004951198042853116457644882109004821588048216800495119804281c13645764488210900482158804821680049511980428d008330809ac8928057c78027c7a017c7b037c7c04978808d4a80897bb1097cc18d4cb0bd4b8087c7a067c7b057c7c077c760897aa08d4ba0a97cc10976618d46c0cd4ca0a97aa20d4a8089599f79577097b477b4908140714000000000000807b577b580828e3049517300364485010241a378218300314070000000000000080aa78ba048218400382193803821a30037b58187b59107b5a08140705000000000000807b5728a20495173003644850102849fc8217300352077504821738037c184003140915000000000000807b597b570878581028720437075827034921c8014921d001014921d8015107f5037b19a80033066e783302204921f000017b15b00028118215b0008218b800c938085108cb01821ac801330730e67803c96a077b18b8007b13c800ae3772c83605ac656707330c5001d6c5083307520865079659500157095d07510a1896a750018218f0007b1830037b17400333070128053307017b1738039517e001330801951a300350102c352f8217e00152070a078217e8017b17f0007b17d0017b15c8018214d0003302208213c80001966550019a377b17c00033000133070128457b18f000c858079518e0013309500150102e1b2c019566017b16d8018213c800821be000d83b0795bb018218c000c8b808955550018214d00033022064b05118011aff7b15e8007b16f800824908ac29c60082459598e09556207b467b4808ac28b5009598c0955b407b4b7b4808330a9000aca8a200959a30ff9558d0007b487b4a08330c8000acca8d009597b0fe955950017b497b470895173003330980007b10e0007b1bd800501030822b013309209517b0036458501032732b013309209517d0036468501034642b01330990009517f0038218d800501036522b019517e0019518300333095001501038402b018217c8018216f8008218f0008215e800ab7600ff9517c80150103a9a318218d00128effe847701520754028215b0008216f8008218b800c9380852083dfe8217c801140800000000000000808219a800821af000ab87f5012838026457644882109004821588048216800495119804282d12951730036448501040e13f8218300314090100000000000080aa9813028218400382193803821a30037b58187b59107b5a08140712000000000000807b5728fb0195173003644850104432348218300314070000000000000080aa78d2018218400382193803821a30037b58187b59107b5a0814070a000000000000807b5728ba01330805ac89990164537c72017c76027c7b037c7c049595fb9578057b48330a207b4508aca51e01959adb9575257b457b4a08510a0f017c75259599da9577267b477b490851059203521501f9004921f8000128890395173003644850104a9e408218300314090100000000000080aa983c018218400382193803821a30037b58187b59107b5a08140716000000000000807b5728240195173003644850104e5b338218300314070000000000000080aa78fb008218400382193803821a30037b58187b59107b5a08140713000000000000807b5728e300140717000000000000807b5728d4009599df9577217b477b490864c695c7083309205010524d29019555f67b6528b3007c7a027c7b017c7c037d760497aa08d4ba0a97cc10976618d46c0cd4ca0a9599fb9577057b477b4908140803000000000000807b587a5a082878140718000000000000807b37286a3306330a010137085827036e88e6860814090d000000000000807b597b57087b5a107b5818955720951800013309b800501054c7280128328217c8018215b00051070d8217f000501056e42501140718000000000000807b57280f140718000000000000807b57018210900482158804821680049511980432008477015207f50182185882179800c9780851085602821bc80133070004e67809c9cb077b18587b199800ae9772c89c05acc5200398583c330752083303975904821a48ac9a29037b1cf800510b1397bb047b12e0017b1bf00133070828053307017b17e80195170001330808951ae001501058dd2a821700015207e202821208017b12d0017b15c801821ad000821468821060821cf800821998000197c5049a977b1790003303013307012893009766088217f000d467078218e8009788108219e000979918d49808d487078218a000978808d4b8088219b800979910821ba80097bb18d4b909d498088219c000979908d4c909821bd80097bb10821cc80097cc18d4cb0bd4b909979920d49808c852097a977b9808821cf80095cc017b1cd80182179800d8730795330182189000c8380895551082168800511801cefe82a808330903ae89bcfe7b1cf80082a97c9b7b1bf0007c96017c9b027b1be8007c9b037b1be000958bfc959c047bac7bab08541b07797c9b047c97057b17a0007c97067b17b8007c97077b17a8007c9c087c97097b17c0007c970a7b17d8007c970b7b17c8008217c80195990c7ba99588f47ba8088218f800ab78f7fe9517c8017b1380007b1c787b1b7050105a232a821b70821c7882138000821060821468821ad0008212d00128c8fe84770182168800821cf800510713fe8217c801510790008217d00150105cad23012882004921f80001976608d4260797bb1097cc18d4cb0bd47b05643695370c33092050105e512601140708000000000000807b677a65088217f80078672c28a6fd82980882997b18c0017b19b801821468821060821c2082168800283a9519d00182988217c8018299087b18b801140800000000000000807b19c001821c20ab87168215b000140718000000000000807b572852fd821850978808821918d49808821910979910821a0897aa18d4a909d49808821928979908821a40d4a909821a3897aa10821b3097bb18d4ba0ad4a909976a08d44a0a970b1097cc188216c0018215b801d4cb0bd4ba0a821cb0007bc6187bc510140b0b000000000000807bcb7bc7087ac8207ac9247aca2895c72c951830033309ac005010605b250128c6fc8217e8018218f0013309a82f01282cda0033073309a82f012821da003309a82f012818da0033073309a82f01280dda0082170801821810013309a82f0128fcd9003309a82f0128f3d9009511e07b10187b15107b1608828908140a0000000000000080330b0414051100000000000080acb91f828b7cb27cbc017cb4027cb3039596fc95ba047b8a7b860855162012140518000000000000807b75283b97cc08d42c0c974410973318d44306d46c0c7b1c9599dc95bb247b8b7b89089578083309206476648764a85010628a24017b6582177a6728018210188215108216089511203200951188fe7b1070017b1568017b166001828b083309027b1718ae9b15140718000000000000808218187b8728da028287140affffffff000000007c797b19107c79017b190895bbfe9577027b877b8b087b173a0760270349211001492118010149212001330551075902d2a7073302017b1a207b18302810821a2001821728c9b70751072902821a10017b172833090002e6970bc95a077b1b40aeb75fc8b506ac56880298683b330752088a0297690557098302510a1397aa057b1240017b1a500133070128053307017b17480195172801330801951a400150106465268217280152073502821230017b1218017b161001821830821b40019756059ab77b1738330c01330701283dc8620795184001330920642550106659230164528215509555017b152001821b40821c48d8bc0795cc01821938c8c90995662082183051190139ff82890853192047018287959ae09579207b897b8a087c78017c797c7a027c7b03978808d4980897aa1097bb18d4ba0ad4a8087c79057c7a047c7b067b1c487c7c07979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1840017c78097c79087c7a0a7c7b0b978808d4980897aa1097bb18d4ba0ad4a8087c790d7c7a0c7c7b0e7c7c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1848017c78117c79107c7a127c7b13978808d4980897aa1097bb18d4ba0ad4a8087c79157c7a147c7b167c7c17979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1850017c78197c79187c7a1a7c7b1b978808d4980897aa1097bb18d4ba0ad4a8087c791d7c7a1c7c7b1e7c771f979908d4a90997bb10977718d4b707821a1001d49707977720d487077b1758017b1550aba58ffe95171001501068f32582121801287efe847701821a205107e8fd82171001510780fd642750106a001f012874fd8217100114080000000000000080aa8763fd2806330201013a08602703821908979908821b10d4b909d2a808e68508140a0c00000000000080821b187bba7bb7087bb2107bb81879b92095b72295185a3309b60050106c6f210182107001821568018216600195117801320082173001821838013309a82f012831d60033073309a82f012826d6003309a82f01281dd600828308330a20aea311140818000000000000807b7832009511f0fd7b1008027b1500027b16f801828b9532e095bc207b8c7b82087cbc017cb67cb5027cba0397cc08d46c0c97551097aa18d45a0ad4ca0a7cbc057cb6047cb5067cb90797cc08d46c0c975510979918d45909d4c909979920d4a9097b1940017cb9097cba087cbc0a7cb60b979908d4a90997cc10976618d46c0cd4c9097cba0d7cbc0c7cb60e7cb50f97aa08d4ca0a976610975518d46505d45a0a97aa20d4a9097b1948017cb9117cba107cbc127cb613979908d4a90997cc10976618d46c0cd4c9097cba157cbc147cb6167cb51797aa08d4ca0a976610975518d46505d45a0a97aa20d4a9097b1950017cb9197cba187cbc1a7cb61b979908d4a90997cc10976618d46c0cd4c9097cba1d7cbc1c7cb61e7cb51f97aa08d4ca0a976610975518d46505d45a0a97aa20d4a909330c087b195801acc209017cb4207cb9217b19f8007cba227cb9237b1900017cb2247cb0257cb5267cb62795b9287b899539d87b8908531908da007b1af0007cb9287b19d8007cb9297b19b8007cb92a7b19c8007cb92b7b19c0007cbc2c7cb92d7b19d0007cb92e7b19e8007cb92f7b19e0009539d095ba307b8a330a087b8908aca98f007cb9307b1998007cb9317b19787cb9327b1988007cb9337b1980007cb9347b19b0007cb9357b1990007cb9367b19a8007cb9377b19a00095b9387b899539c87b8908531908497b1c707cb9387b19507cb9397b19307cb93a7b19407cb93b7b19387cb93c7b19687cb93d7b19487cb93e7b19607cb93f7b19589539c095ba407b8a330c80007b8908aec922140818000000000000807b780182100802821500028216f8019511100232007b107b14087b16107b15187b12207b1728953740ff95b9c0007b897b87089517600133098000648564a850106e921e019517e0016458501070a5338218e00114090100000000000080ab9814140818000000000000808217287b7828938215f800975508821708d457078218f00097881082160001976618d46808d487078218978808821920d49808821918979910821a1097aa18d4a909d49808978820d487077b1700018217b8009777088218d800d487078218c8009788108219c000979918d49808d487078218d000978808821970d498088219e800979910821ae00097aa18d4a909d49808978820d487077b17f80082177897770882189800d487078218880097881082198000979918d49808d48707821890009788088219b000d498088219a800979910821aa00097aa18d4a909d49808978820d487077b17f000821730977708821850d48707821840978810821938979918d49808d48707821848978808821968d49808821960979910821a5897aa18d4a909d498089788208219f001821ae801821be001d478067b1918017b1a10017b1b08019517200195184001330920501072181d018215289557589518600133098000501074041d018217080182181001821918017b577b58087b5910821720018218280182193001821a38017b57187b58207b59287b5a30821700017b57388217f8007b57408217f0007b57487b565028e8fd9511c07b10387b15307b1628828308140a0000000000000080330b2014040700000000000080acb32a01828a9532e095ac207b8c7b82087cac017ca57ca6027cab0397cc08d45c0c97661097bb18d46b0bd4cb0b7cac057ca6047ca5067ca90797cc08d46c0c975510979918d45909d4c909979920d4b9097b19087ca9097cab087cac0a7ca60b979908d4b90997cc10976618d46c0cd4c9097cab0d7cac0c7ca60e7ca50f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19107ca9117cab107cac127ca613979908d4b90997cc10976618d46c0cd4c9097cab157cac147ca6167ca51797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19187ca9197cab187cac1a7ca61b979908d4b90997cc10976618d46c0cd4c9097cab1d7cac1c7ca61e7ca51f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b909330b087b1920aeb211140818000000000000807b7828767ca9217cab207cac227ca623979908d4b90997cc10976618d46c0cd4c9097cab257cac247ca6267ca52797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b199539d895aa287b8a7b8908957a08951808330920647664a714050700000000000080501076ef1a017b6582177b67280182103882153082162895114032009511a87b10507b15487b1640828208330b20140a01000000000000807b1a18acb25901828a9523e095ac207b8c7b83087cac017ca67ca5027cab0397cc08d46c0c97551097bb18d45b0bd4cb0b7cac057ca6047ca5067ca90797cc08d46c0c975510979918d45909d4c909979920d4b9097b19207ca9097cab087cac0a7ca60b979908d4b90997cc10976618d46c0cd4c9097cab0d7cac0c7ca60e7ca50f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19287ca9117cab107cac127ca613979908d4b90997cc10976618d46c0cd4c9097cab157cac147ca6167ca51797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19307ca9197cab187cac1a7ca61b979908d4b90997cc10976618d46c0cd4c9097cab1d7cac1c7ca61e7ca51f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b909330c087b1938acc3327ca0207ca9217ca6227cab237ca5247b15107ca4257ca5267b15087ca5277b1595a3287b839525d87b85085515081b8218189588177b78018210508215488216409511583200979908d4090997661097bb18d46b0bd4b909974408821b10d4b40b821c0897cc108216976618d46c0cd4cb0b97bb20d4b9097b19107ca9297cab287cac2a7ca52b979908d4b90997cc10975518d45c0cd4c9097cab2d7cac2c7ca52e7ca62f97bb08d4cb0b975510976618d46505d45b0b97bb20d49b059529d095aa307b8a7b8908957a08951820330920647664a7501078b518018217187b678217107b67287b6530284fff9511c07b10387b15307b1628828308140a0000000000000080330b2014040600000000000080acb32a01828a9532e095ac207b8c7b82087cac017ca57ca6027cab0397cc08d45c0c97661097bb18d46b0bd4cb0b7cac057ca6047ca5067ca90797cc08d46c0c975510979918d45909d4c909979920d4b9097b19087ca9097cab087cac0a7ca60b979908d4b90997cc10976618d46c0cd4c9097cab0d7cac0c7ca60e7ca50f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19107ca9117cab107cac127ca613979908d4b90997cc10976618d46c0cd4c9097cab157cac147ca6167ca51797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19187ca9197cab187cac1a7ca61b979908d4b90997cc10976618d46c0cd4c9097cab1d7cac1c7ca61e7ca51f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b909330b087b1920aeb211140818000000000000807b7828767ca9217cab207cac227ca623979908d4b90997cc10976618d46c0cd4c9097cab257cac247ca6267ca52797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b199539d895aa287b8a7b8908957a08951808330920647664a71405060000000000008050107ada16017b6582177b6728018210388215308216289511403200951168ff7b1090007b1588007b168000828c08140a0000000000000080330b0414050200000000000080acbca50082837c397c34017c3b027c3a037b1a7895c6fc953a047b8a3302087b8608ac2683007c3a047b1a587c3a057b1a387c3a067b1a487c3a077b1a407c3a087b1a707c3a097b1a507c3a0a7b1a687c3a0b7b1a6095300c7b80649a95c9f47b89085319084264b07c390c7b19207c390d7b197c390e7b19107c390f7b19087c3b107c39117b19187c39127b19307c39137b192895c9ec9532147b827b89085529800024140518000000000000807b7501821090008215880082168000951198003200953994007b8995c96cff7b890864bc974b08d4ab08970910821478974418d49409d498087b1878821a3897aa08821858d4a808821948979910821a4097aa18d4a909d49808821950979908821a70d4a909821a6897aa10821b6097bb18d4ba0ad4a909979920d498087b18708218978808821920d49808821910979910821a0897aa18d4a909d49808821918979908d4c909821a3097aa10821b2897bb18d4ba0ad4a909979920d498087b18689578083309800064766487642850107c1615017b658217707b6788008217687b6790008217787a6798002817ff9511e07b10187b15107b1608828908140a0000000000000080330b0414050400000000000080acb91f828b7cb27cbc017cb4027cb3039596fc95ba047b8a7b860855162012140518000000000000807b75283b97cc08d42c0c974410973318d44306d46c0c7b1c9599dc95bb247b8b7b89089578083309206476648764a850107e7814017b6582177a6728018210188215108216089511203200951178ff7b1080008089348277849a10520a3e8499205209757c7a531a64c700648796a82998880c968964bf9a0a97aa3998aa383309da0f01c8a9097c9a017c9b3309781a02781b01648a28a1007c77330a810095197f0164ab847a0f98770488a20a86ac3095aa57db2c0a789a9599ff95baff5207e495b7fe56278000bb009aab8100641ac87a0a283e7c77330a810095197f0164ab847a0f98770488a20a86ac3095aa37db2c0a789a9599ff95baff5207e495b7fe562780008b009aab8100641ac87a0a01330902648733082523015020820069ce0082108000951188003200330902551a0a1364870186a830641ac89a0a78a82821648797aa3998aa383308da0f01c8a8087c89017c8878190278180133090101641ac89a0a859b0333080133095020840016ce00821080009511880032003309002d0133088000285ecc003309002d01330880002851cc009511d87b10207b151864858288288279825720828a187b193308720801330906b4a00288007b15087817104611113308e40601330bee00951708330905641a50208a0061d9007c19117c1810d4890751094284880152083c8217087c783484880452081a827828827720828a1833085a0f0133090233008c00320a827828827720828a183308590f01330901b4a0028c008477018210208215189511283200827728ee09019511c07b10387b15307b162864858288288276825720828a183308013309b4a00292007b15087817104901461111017b16189566087b16203305d20064179518183309d20050209400d0dd006417951820645950209600c2dd0082187c19108e87d49707510859849901520953521801367c17118215085107307c5734847704520727825828825720828a1833085f0f01330901b4a00298006478330701510809281c82150801825828825720828a183308dd0e01330901b4a0029a008477018210388215308216289511403200827728013c827728aa04019511d07b10287b152082798297140a00000000000000806485aba78900825828825720828a187b1933087f0501330903b4a002a2007b15107817184911084611193309200195170864185020a400f2dc008218087c19188e87d497075108f9008499015209f200521801b9007c17198215105107b2007c57348477045207a800825828825720828a1833085f0f01330901b4a002a60064783307015108890028b600825828825720828a187b1933087d0501330902b4a002a8007b15107817184911084611193309860095170864185020aa006ddc008218087c19188e87d4970751087484990152096e521801517c171982151051074b7c5734847704520742825828825720828a1833085f0f01330901b4a002ac006478330701510824283782151001825828825720828a183308dd0e013309013300ae00320a82151001825828825720828a183308dd0e01330901b4a002ae0084770182102882152095113032009511c07b10387b15307b1628827764858288288276088277107b1708825720828a183308e10e01330901b4a002b2007b1510821508781718461119510535975504330790007b17019567107b17087b162095171095182082195020b400c9dc009555f08216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002b6006478016487821038821530821628951140320082777c779777033309b80c01c879098299330a482b01828b28c8a707827a82872082bc1864a8320c9511c07b10387b15307b1628827764858288288276088277107b1708825720828a183308e10e01330901b4a002bc007b151082150878171846111951053b975703975505c975053307f6007b17019567187b17087b162095171095182082195020be00f9db009555e88216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002c000647801648782103882153082162895114032009511c07b10387b15307b162864858288288276825720828a183308013309b4a002c4007b15087817104901461111017b16189566087b16203309d60064179518185020c60026da003309d20064179518205020c80016da0082187c19108e87d49707510859849901520953521801367c17118215085107307c5734847704520727825828825720828a1833085f0f01330901b4a002ca006478330701510809281c82150801825828825720828a183308dd0e01330901b4a002cc008477018210388215308216289511403200827728ff0c82777c79648751090f33085d05013309042833cb0033081a11013309052827cb00951178ff7b1080008089348277849a10520a1584992052094d82108000951188002832e500827b3309810095177f01649a98bc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000689a9b8100641ac87a0a283f827b3309810095177f01649a98bc0484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520ce295a7fe56278000379a9b8100641ac87a0a01330902648733082523015020d400fcc700821080009511880032003309002d01330880002844c6003309002d01330880002837c600951178ff7b1080008089348277849a10520a1584992052094d82108000951188002896cf00817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000689a9b8100641ac87a0a283f817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520ce295a7fe56278000379a9b8100641ac87a0a01330902648733082523015020d80027c700821080009511880032003309002d0133088000286fc5003309002d01330880002862c500827728593582798297140a0000000000000080aba716828928828720829c183308690501330904320c9511d07b10287b1520828a2882872082ab187b19330a51050133090464853308510501b4b002de007b15107817184911084611193309f60095170864185020e00065d7008218087c19188e87d49707510859849901520953521801367c17198215105107307c5734847704520727825828825720828a1833085f0f01330901b4a002e2006478330701510809281c82151001825828825720828a183308dd0e01330901b4a002e40084770182102882152095113032009511c07b10387b15307b1628648582882882768277087b1708825720828a183308e10e01330901b4a002e8007b151082150878171846111951053b975703975505c975053307f6007b17019567187b17087b162095171095182082195020ea00d9d7009555e88216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002ec00647801648782103882153082162895114032009511c07b10387b15307b1628827764858288288276088277107b1708825720828a183308e10e01330901b4a002f0007b1510821508781718461119510535975505330704017b17019567207b17087b162095171095182082195020f20037d7009555e08216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002f400647801648782103882153082162895114032009511c07b10387b15307b162882777b1808828928827508827610828720829a183308e10e01330901b4a002f8008218087b1810781718461119510632330780007b17019557017b17087b152095171095182082195020fa009ad6009566ff8215085206e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002fc00647801648782103882153082162895114032009511807b10787b15707b1668827664853307800033081f6469015107137d9a9599019577ffada8f5510af2282a3309800001c876087c8851080b957701ab97f528056479016417646850200001d5cd00821751073f7b164911080864177b17583307aa077b17603307d032017b1728491130029517587b1738825720825828491140014911489519283300020128cec9008217088218107b17187b18209517187b17583307b8077b17603307b032017b1728491130024911489519588257208258287b193849114001951928502002018fc9008210788215708216689511800032009511d87b10207b151864858288288279825720828a187b1933086d0501330904b4a00206017b150878171046111133086a0801330bce00951708330903641a50200801cdce007c19117c1810d4890751094284880152083c8217087c783484880452081a827828827720828a1833085a0f0133090233000a01320a827828827720828a183308590f01330901b4a0020a018477018210208215189511283200827728ba219511b87b10407b15387b16308277648582882882768277087b17825720828a183308350501330904b4a00210017b15107817184911084611198217c967079877031408abaaaaaaaaaaaaaaca87077b16207b17283309e6009517089518205020120124d3008218087c19188e87d49707510859849901520953521801367c17198215105107307c5734847704520727825828825720828a1833085f0f01330901b4a00214016478330701510809281c82151001825828825720828a183308dd0e01330901b4a002160184770182104082153882163095114832009511c07b10387b15307b1628827764858288288276088277107b1708825720828a183308e10e01330901b4a0021a017b15108215087817184611195105359755043307c2007b17019567107b17087b1620951710951820821950201c0198d3009555f08216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a0021e01647801648782103882153082162895114032003309026487330815050128aac3009511d07b10287b15207b161864868288288275826720828a189558087b1833086d0801330905b4a00224017b16087817104611113308610501330b3601951708330904645a5020260199cc003308b70501330b8000951708330908641a5020280181cc007c19117c1810d4890751094284880152083c8217087c783484880452081a827828827720828a1833085a0f0133090233002a01320a827828827720828a183308590f01330901b4a0022a018477018210288215208216189511303200951178ff7b108000808934849a10520a6e8499205209aa007e79989a04532a7102fd00648796a84703988813968a1027bfa909979a3098aa3296aa7b1498ab1198aa1084aafe0796bb64bfb909979931989930330bda0f01c8ba0a7cac7caa01c8b9097c9b017c99781a02781c01781b04781903330b01648928de007e7b3309810095177f01649a97b930989c3484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520cdf95a7fe56278000f5009a9b8100641ac87a0a28437e7b3309810095177f01649a97b930989c3484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520cdf95a7fe56278000c0009a9b8100641ac87a0a013309026487330825230150202e0160bf0082108000951188003200330b05330ada0f015319642e989b0296bb7b1498bc1196c764bf7909979931989930c8a9097c97017c99781704781903330b0364c90155190a1664870195bbff6418c8b80886993078892820648797993198993095bbfec8a9097c98017c99641ac8ba0a78a80178a901641ac8ba0a9abb05330801330950203001d8be00821080009511880032003309002d01330880002820bd003309002d01330880002813bd00951178ff7b108000808934849a10520a1584992052094d82108000951188002874c600817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000689a9b8100641ac87a0a283f817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520ce295a7fe56278000379a9b8100641ac87a0a01330902648733082523015020340105be00821080009511880032003309002d0133088000284dbc003309002d01330880002840bc00951178ff7b108000808934849a10520a1584992052094d82108000951188002868da00827b3309810095177f01649a98bc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000689a9b8100641ac87a0a283f827b3309810095177f01649a98bc0484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520ce295a7fe56278000379a9b8100641ac87a0a01330902648733082523015020380132bd00821080009511880032003309002d0133088000287abb003309002d0133088000286dbb009511d87b10207b15187b1610647a82771408ffffffffffffff7fc8870988991714080000000000000080d378089398330916ac89fc009788023309600001c898088188c89808320882a70852071a28e00082a70814080000000000000080d48707aa87cd0082a71028b60082a6187b1a82a7107b170851069d00821508955508280c9566ff95551851068b008257f85107f3825750204001c9fe0028e714080000000000000080d48707aa87840082a708286d82a6187b1a82a7107b1708510655821508955508280b9566ff9555185106448257f85107f482575020460183fe0028e882a6187b1a82a7107b1708510625821508955508280b9566ff9555185106148257f85107f4825750204a0153fe0028e882178278088217085108138210208215188216109511282833fe0082102082151882161095112832009511d07b10287b15207b16186485828828825a20828b1864763308e10e0133090164a7b4b00250017b157817084611097b16103305800064179518103309800050205201a3cd009567017b171064179518106459502054018fcd009567027b171064179518106459502056017bcd009567037b1710641795181064595020580167cd009567047b17106417951810645950205a0153cd009567057b17106417951810645950205c013fcd009567067b17106417951810645950205e012bcd009567077b1710641795181064595020600117cd009567087b1710641795181064595020620103cd009567097b17106417951810645950206401efcc0095670a7b17106417951810645950206601dbcc0095670b7b17106417951810645950206801c7cc0095670c7b17106417951810645950206a01b3cc0095670d7b17106417951810645950206c019fcc0095670e7b17106417951810645950206e018bcc0095670f7b1710641795181064595020700177cc009567107b1710641795181064595020720163cc009567117b171064179518106459502074014fcc009567127b171064179518106459502076013bcc009567137b1710641795181064595020780127cc009567147b17106417951810645950207a0113cc009567157b17106417951810645950207c01ffcb009567167b17106417951810645950207e01ebcb009567177b17106417951810645950208001d7cb009567187b17106417951810645950208201c3cb009567197b17106417951810645950208401afcb0095671a7b171064179518106459502086019bcb0095671b7b1710641795181064595020880187cb0095671c7b17106417951810645950208a0173cb0095671d7b17106417951810645950208c015fcb0095671e7b17106417951810645950208e014bcb0095661f7b1610641795181064595020900137cb003307017c180852081b8217827828827720828a183308610f01330901b4a002920182102882152082161895113032009511c07b10387b15307b162864859888205208b1006476645764987b191850209401132e821818510574975703975505c97507956510282d7b18827708c88707821808646950209601c6fd008218188217c876067b86108217109577e895551851073b7b1710825698672052073e8257f87b1708646750209801bb2d8217188278108279c98909ae69b1646950209a0133d800821718827810289f82103882153082162895114032003307170501330ab02301330be82301330816951927283ec8003307170501330ab02301330be823013308169519272825c8009511c87b10307b15287b162082ab0864866478510b3c82a71051073b7b177b181882a77b17107b1908649850209c0145f6005107406475821810821950209e01f4fc008217105020a0011efa00282f51091f280551091a7b1818649864955020a20112f600821818645964752817646528133305821908821818280982190882181801885701db55067b86087b89107b8782103082152882162095113832009511b87b10407b15387b16306475827a95a70151077c97a801e4780898893c330752096e330904e498069769043308f1140bf8ffffffffffff7fac9b55510a1482570897aa047b17187b1a2833070828053307017b17206417330808951a185020a40102ff82175207198217087b57087b5682104082153882163095114832008217088218103309c02f0128edb0003309c02f0128e4b0009511b87b10407b15387b16306475827a95a70151076f97a801e4780898893b3307520961330904e49806976905570955510a1482570897aa057b17187b1a2833070128053307017b17206417330801951a185020a60177fe82175207198217087b57087b5682104082153882163095114832008217088218103309c02f012862b0003309c02f012859b0009511b07b10487b15407b16386476827a95a701648c51077797a801e47807330804e487053308d800d68509330752095f9659d8003308f1140bf8ffffffffffff7fac9b5f7b1c510a1582670896a8d8007b17207b183033070828053307017b1728951708330808951a205020a801d4fd82170852071f8217107b67087b65821048821540821638951150320064c928c7af00821710821818821c64c928b9af0064c928b3af009511b87b10407b15387b16306475827a95a70151077c97a801e4780898893a330752096e330904e498069769063308f1140bf8ffffffffffff7fac9b55510a1482570897aa067b17187b1a2833070828053307017b17206417330808951a185020aa0139fd82175207198217087b57087b5682104082153882163095114832008217088218103309c02f012824af003309c02f01281baf009511b87b10407b15387b16306475827a95a70151078c0097a801e47807330804e48706330718d676083307520874976803976905c989093308f1140bf8ffffffffffff7fac9b5b510a1a82570897a80397aa05c98a0a7b17187b1a2833070828053307017b17206417330808951a185020ac0191fc82175207198217087b57087b5682104082153882163095114832008217088218103309c02f01287cae003309c02f012873ae009511b87b10407b15387b16306475827a95a70151077597a801e47807330804e4870633085001d68609330752095d96695001570956510a1582570896a850017b17187b182833070128053307017b17206417330801951a185020ae0100fc82175207198217087b57087b5682104082153882163095114832008217088218103309c02f0128ebad003309c02f0128e2ad009511b87b10407b15387b1630c88909ac895464758277977801330a08e4a909e498065706415107108258087b18187b1728330701017b17206417330801951a1864695020b00185fb82175207248217087b57087b56821040821538821630951148320033073309302f012874ad008217088218103309302f012865ad00827a08827910648764a828cbb5009511c07b10387b15307b162864858288288276088277107b1708825720828a183308e10e01330901b4a002b6018218087b15107817184611195108389685d80033070c017b17019567d8007b17087b162095171095182082195020b80109c500955528ff8216085205e07c17188477010133080152071f821710827828827720828a183308610f01330901b4a002ba01647801648782103882153082162895114032009511c07b10387b15307b162864858288288276088277107b1708825720828a183308e10e01330901b4a002be017b15108215087817184611195105359755043307c2007b17019567107b17087b162095171095182082195020c00168c4009555f08216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002c201647801648782103882153082162895114032008279140a0000000000000080aba916828928828720829c183308690501330904320c9511d07b10287b1520828928828a20829c187b17330b51050133090464a764853308510501b4c002c6017b15107817184911084611193309f60095170864185020c80175c2008218087c19188e87d49707510859849901520953521801367c17198215105107307c5734847704520727825828825720828a1833085f0f01330901b4a002ca016478330701510809281c82151001825828825720828a183308dd0e01330901b4a002cc018477018210288215209511303200951110ff7b10e8007b15e0007b16d80064857b1764875020ce013cd5008377510724140700000000000000808218017b878210e8008215e0008216d8009511f0003200838749219000951698004911280849219800084921a000510710044911389788209888201407f8ffffffffffff7f7b17086487140800000000000000807b18107b154028228217387b173828067b1738018217208477015207a103821718c987075107ea03821a90007b17183308aa02e68709821838c98a077b1930ae9742c89805ac852604330718d67508330752083604975803975905c98909821b08ac9b2604510a2097a70397aa05c97a0a8217287b17c0007b1ad0003307082809649828383307017b17c8009517a800330808951ac0005020d00108f88217a8005207d5038217b0007b17287b1798007b159000821540821830018253083302491120018217382838952201821938979703979805c978086497821928c898087b8a8219687b89087b8b109577017b17a000821830d882097b1920aa8215ff7b1738510312ff82577c7a9536ff9579017b597b5608491168017b1248510a6f522aff00820053130985027c78027c79017c7a037c7b04978808d4980897aa1097bb18d4ba0ad4a8087c79067c7a057c7b077c7c08979908d4a90997bb1097cc18d4cb0bd4b909979920d489099533f79577097b17607b577b5308989720951698008218305107c7002880fe646364ab9516980082179000821838aa78a201282bff6ca857080b33054921c000284a84874033050151073a84872033050251073184871033050351072884870833050451071f84870433050551071684870233050651070d8487013305075207a6024921c000ac5683019517c0007b187864987b197064595020d201cbf300821248c95603821770c857078219407b17607b978557079b77ff821878e078078218c0007b9308975503cf57076495c87809989720951698008218305207c1fdac93bdfd5109de006498330a33064911680101330700407b1858e67805c96a077b1370ae5721c85609ac69db015709d701510a188217687b17c0007b1ad000330701280ac8560b28353307017b17c8009517a800330801951ac0007b19785020d401f6f58217a8005207a7018217b0007b1768821b7864ba82137001ac6b7b01ac539d00821768c86707821660646864597b1a507b1b785020d601edf200821b78821a50821370821248c95303c856068217407b16607b76821858c958087b730864b6520850ff82154095169800821710aa7a820082179000821838ab78a2fd2811330a330b82179000821838ab7891fd95179000643664a57b1b785020d80158f8821b78645a646395169800821540821248821798007b17282864fd510a238217685020da0195ef0082172084770182154095169800821830510799fc2832821540951698008217388218307b17382879fc8217388218307b1738286dfc82172084770195169800821830510767fc821798007b1778821538510534821678956608280b9555ff9566185105238267f85107f482675020dc0122ef0028e882680882697b1888007b1980002836821790005107a5fb8217785020de01ffee002897fb8268821790008269087b188000140800000000000000807b198800aa8779fb8218880082198000821a7ba81064a87ba9082870fb3309902f01646764b828c4aa0033073309a82f012860a6008217b0008218b8003309a82f01284fa60033073309a82f012844a6008217b0008218b8003309a82f012833a6003309a82f01282aa6003307782f012807b20083775107121407000000000000008082182893027b151883874921d0009519d800330b087b1b407b1bd8004921e00051073002330a9788209888201407f8ffffffffffff7f7b1708330407821318281384770152076502821810c9080851081102821bd00033070004e67800c9ab077b18107b1020ae0770c80a05aca5640298583c330752086602975904821c08ac9c5c0264a6510b1697bb048217407b1700017b1b100133070828053307017b1708019517e800330808951a00015020e2014af38217e800520708028217f0007b17407b17d8007b15d000821318646a3304078210200197ac049a077b173833020133070128b4009755088217a000d4570782189000978810976618d46808d48707821898009788088219b800d498088219b000979910821ba80097bb18d4b909d49808978820d48707821848978808821968d49808821958979910821b5097bb18d4b909d49808821960979908821b8000d4b909821b7897bb10821c7097cc18d4cb0bd4b909979920d49808821940821c8800c8c9097b977b980895aa017b1ae000d80207952201821838c8280895cc10330901aa98b4fe823808ae84a6fe82397c9b7b1ba0007c95017c9b027b1b90007c96037c9b047b1bb8007c9b057b1b98007c9b067b1bb0007c9b077b1ba800959b087b3b958bf87b3b08aeb466fe7c97087b17687c97097b17487c970a7b17587c970b7b17507c970c7b1780007c970d7b17607c970e7b17787c970f7b17708217d0009599107b399588f07b38087b1c8800ab7ac4fe9517d0007b1a307b12285020e40165f2821228821020330407821a308213188217d8007b1740289afe82980882997b18c8007b19c00028259519d80082988217d0008299087b18c000140800000000000000807b19c800aa8778fd8218c8008219c000821a7ba81064a87ba908017b878210280182152001821618019511300132008217d000510749fd8217d8005020e601a6eb00283afd8217f0008218f8003309a82f012842a30033073309a82f012837a3003309a82f01282ea3007c779777033309b80c01c879098299330a482b01828b28c8a707827a82872082bc1864a8320c9511d07b10287b15207c7964855219078c00825828825a20828b187b1733087d050133090264a7b4b002ec017b15107817184911084611193309200195170864185020ee0189b9008218087c19188e87d497075108fb008499015209f400521801bb007c17198215105107b4007c57348477045207aa00825828825720828a1833085f0f01330901b4a002f001647833070151088b0028b800825828825a20828b187b1733087f050133090364a7b4b002f2017b15107817184911084611193309b80095170864185020f40102b9008218087c19188e87d4970751087484990152096e521801517c171982151051074b7c5734847704520742825828825720828a1833085f0f01330901b4a002f6016478330701510824283782151001825828825720828a183308dd0e013309013300f801320a82151001825828825720828a183308dd0e01330901b4a002f80184770182102882152095113032009511d07b10287b15207c79648551098e00825828825a20828b189577017b1733087f050133090364a7b4b002fc017b15107817184911084611193309b80095170864185020fe012bb8008218087c19188e87d497075108fe008499015209f700521801be007c17198215105107b7007c57348477045207ad00825828825720828a1833085f0f01330901b4a0020002647833070151088e0028bb00825828825a20828b189577087b1733087d050133090264a7b4b00202027b15107817184911084611193309d200951708641850200402a1b7008218087c19188e87d4970751087484990152096e521801517c171982151051074b7c5734847704520742825828825720828a1833085f0f01330901b4a00206026478330701510824283782151001825828825720828a183308dd0e0133090133000802320a82151001825828825720828a183308dd0e01330901b4a002080284770182102882152095113032009511887b10706482957c80009578a0009579c0007b17683307fe007b17609517687b1758491150083307c705017b17483307c6077b17407b1938491130033307a405017b17283307ba077b17207b18184911100c33089805017b18083308820501330a91050133090f330b077b17642750200c0229b9008210709511783200828a08510a559511d87b10207b1518828b7cb995aaff95bb017b8b7b8a08510948330a01aba94e6475641750200e024cc800821814070000000000000080ab784c140701000000000000807b578210208215189511283200140801000000000000807b78320014080000000000000080280d14080100000000000080017b78821020821518951128320082171082180882197b57107b58087b5982102082151895112832009511d07b10287b15207b16188279140a00000000000000806485aba91b8258108257aa877c825708c8870746079588017b5810285d8258108259aa8976825908c88909827610330a01789ac8a8089869207b5810520982008277087b17086467645850201002001a8258108257c98707ac67567b18825708c88707821808646950201202bce9008217c876067b56100182102882152082161895113032003309016457502014024dc4008258102878ff330901647664575020160239c4006467825810287aff645764695020180226c400825810289f3307170501330ab02301330be823013308169519172842b400951148ff7b10b0007b15a8007b16a0008289087b1714070100000000000080510942828a7cab9599ff95aa017b8a330a017b8908aaab22520b2a6487648550201a0212c800837751073014070100000000000080280d140700000000000000800182187b878210b0008215a8008216a0009511b80032007b151083874911509519584911580149116051075202330697882098882049112801821b10281384770152075702821808c9c80851080f02821a5033070002e6780cc96a077b18087b1c20aec761c8c605ac65530298583b33075208550297590557094e02510a1697aa058217287b1780007b1a900033070128053307017b178800951768330801951a800050201c021aeb821768520700028217707b17287b17587b1550821b10821c20019765059ac77b1718330a01330701283c821728c857079518800033092050201e020de8008216389566017b1660821c20821a30d8ca0795aa01821818c8a808955520821b105118013dff82b808330920ac982bff82b79588e09579207bb97bb8087c78017c797b1a307c7a027c7b03978808d4980897aa1097bb18d4ba0ad4a8087c79057c7a047c7b067c7c07979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1880007c78097c79087c7a0a7c7b0b978808d4980897aa1097bb18d4ba0ad4a8087c790d7c7a0c7c7b0e7c7c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1888007c78117c79107c7a127c7b13978808d4980897aa1097bb18d4ba0ad4a8087c79157c7a147c7b167c7c17979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1890007c78197c79187c7a1a7c7b1b978808d4980897aa1097bb18d4ba0ad4a8087c791d7c7a1c7c7b1e7c771f979908d4a90997bb10977718d4b707821a50d49707977720d487077b1798007b1638aba68ffe95175050202002a9ea8217587b1728287cfe95195882988217508299087b1840140800000000000000807b1948aa8775fd280e82980882997b18487b194001821848821940821a7ba8107ba908286cfd82175051074ffd821728502022027fe3002841fd8217708218783309a82f01281d9b0033073309a82f0128129b003309a82f0128099b009511f0fe7b1008017b1500017b16f8008279140affffffffffffff7fc8a90b88bb17140a0000000000000080d3a90993b9979902330abc0001c8a9098199c8a90964853209957818957938957a40957b48957c509576587b17707b187833070024017b1780007b19880033072024017b1790007b1a98007b17a0007b1ba8007b17b0007b1cb8007b17c0007b16c80033074024017b17d0009517707b17d80082582833076024017b17e000825720828a189516d80033085d060133090db4a00228027b15e8007817f0004621f10033082706013305aa069517e800330909951a78330baa0650202a02ccab003308a705019517e800330908951a8800645b50202c02b3ab0033083006019517e80033090c951a9800645b50202e029aab0033083c06019517e80033090c951aa800645b5020300281ab0033084806019517e800330909951ab800645b5020320268ab0033083105019517e800330904951ac800645b502034024fab0033085106019517e80033090c646a645b5020360238ab007c17f1007c18f000847901d487075109160d84880152080f0d8217e8007c78348488045208cd0b827828827720828a1833085a0f013309023300c602320a825828825720828c1833081e070133090528050b825828825a20828b189577087b17e80033080f070133090564a7b4b0023c027b157878178000462181003308390501330bce00951778330904951ae80050203e02a2aa007c1981007c188000d489075109830c84880152087c0c8217787c78348488045208b10a827828827720828a1833085a0f013309023300c602320a957c209578249579289577087b1778330718017b17609517787b1758491150083307bf05017b1748330732017b17407b19384911300933091721017b19287b17207b18184911100633081121017b18083308ef0601330af40601330905330b07281b02825828825a20828b189576289577087b17e800330899060133090564a7b4b00244027b1578781780004621810033089e0601330b3201951778330906646a50204602c0a9003308270601330b9e00951778330909951ae80050204802a6a9007c1981007c188000d489075109870b8488015208800b8217787c78348488045208cc09827828827720828a1833085a0f013309023300c602320a825828825a20828b189576209577087b17e8003308fb060133090664a7b4b0024c027b1578781780004621810033083d0501330b2c01951778330904646a50204e0227a9003308010701330b9c00951778330905951ae800502050020da9007c1981007c188000d489075109ee0a8488015208e70a8217787c783484880452084a09827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e8003308de060133090664a7b4b00254027b157878178000462181003308e40601330bb000951778330905951ae800502056028fa8007c1981007c188000d489075109700a8488015208690a8217787c78348488045208e308827828827720828a1833085a0f013309023300c602320a957c980095788800957990009577087b177833078e007b17609517787b17584911500433073105017b1748330736017b17407b19384911300933098206017b19287b17207b18184911100633077c06017b1708330732013308e70501330a710601330908330b0b017b17645750205a0288af0028ce09825828825720828c18330814070133090a28ea07825828825a20828b189577087b17e80033088b060133090764a7b4b00260027b157878178000462181003308920601330bd600951778330907951ae8005020620287a7007c1981007c188000d4890751096809848801520861098217787c78348488045208f207827828827720828a1833085a0f013309023300c602320a825828825a20828b189576089577287b17e8003308c6060133090664a7b4b00266027b157878178000462181003308390501330bba07951778330904646a5020680208a7003308c30601330bd200951778330903951ae80050206a02eea6007c1981007c188000d489075109cf088488015208c8088217787c783484880452087007827828827720828a1833085a0f013309023300c602320a825828957930825a20828b187b19e8009576089577287b176833086a060133090764a7b4b0026e027b157878178000462181003308270601330bba07951778330909646a5020700269a6003308300601330b360195177833090c821a685020720250a60033083c0601330bd20095177833090c951ae8005020740236a6007c1981007c188000d4890751091708848801520810088217787c78348488045208e606827828827720828a1833085a0f013309023300c602320a825828825a20828b189576289577087b17e800330823070133090764a7b4b00278027b1578781780004621810033082a0701330b320195177833090a646a50207a02b7a5003308170601330bce00951778330908951ae80050207c029da5007c1981007c188000d4890751097e07848801520877078217787c783484880452086406827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e8003308df050133090864a7b4b00280027b157878178000462181003308340701330bd200951778330905951ae800502082021fa5007c1981007c188000d48907510900078488015208f9068217787c78348488045208fd05827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e8003308a4060133090b64a7b4b00286027b157878178000462181003308af0601330bba0095177833090d951ae80050208802a1a4007c1981007c188000d489075109820684880152087b068217787c783484880452089605827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e800330839070133091364a7b4b0028c027b1580007817880049117846218900330922019517789518e80050208e0241a9008218787c1988008e87d4970751080706849901520900065218013b057c17890082158000510733057c573484770452072905825828825720828a1833085f0f01330901b4a0029002647833070151080a0528c205825828825a20828b189577087b17e800330806070133090964a7b4b00294027b157878178000462181003308410501330bda00951778330904951ae800502096028fa3007c1981007c188000d4890751097005848801520869058217787c78348488045208b704827828827720828a1833085a0f013309023300c602320a825828825a20828b189576089577287b17e8003308bc060133090764a7b4b0029a027b157878178000462181003308390501330bba07951778330904646a50209c0210a3003308c30601330bd200951778330903951ae80050209e02f6a2007c1981007c188000d489075109d7048488015208d0048217787c783484880452083504827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e80033080f060133090864a7b4b002a2027b157878178000462181003308750501330bdc00951778330904951ae8005020a40278a2007c1981007c188000d4890751095904848801520852048217787c78348488045208ce03827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e8003308e9060133090664a7b4b002a8027b157878178000462181003308750501330bba00951778330904951ae8005020aa02faa1007c1981007c188000d489075109db038488015208d4038217787c783484880452086703827828827720828a1833085a0f013309023300c602320a82582895792c825a20828b187b19e80095760895770c7b17683308cc060133090664a7b4b002ae027b157878178000462181003308d20601330b3201951778330907646a5020b00275a1003308390501330bba07951778330904821a685020b2025ca1003308d90601330bd000951778330905951ae8005020b40242a1007c1981007c188000d489075109230384880152081c038217787c78348488045208c602827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e80033084c070133091764a7b4b002b8027b15800078178800491178462189003309a0009517789518e8005020ba02e2a5008218787c1988008e87d497075108a8028499015209a1025218016b027c17890082158000510763027c573484770452075902825828825720828a1833085f0f01330901b4a002bc02647833070151083a02286302825828825a20828b189577087b17e8003308af050133090864a7b4b002c0027b157878178000462181003308750501330bba00951778330904951ae8005020c20230a0007c1981007c188000d489075109110284880152080a028217787c78348488045208e701827828827720828a1833085a0f013309023300c602320a825828825720828c1833086307013309090182100801821500018216f80095111001320c827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a8215800001825828825720828a183308dd0e013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a8215800001825828825720828a183308dd0e013309013300c602320a827828827720828a183308590f01330901b4a002c60284770182100801821500018216f8009511100132009511d87b10207b15187b16106486828828826a20828b18647533086c070133090364a7b4b002ca027b167817084611093308450501330be0026417330904645a5020cc02b89d007c19097c1808d4890751094184880152083b82177c783484880452081a827828827720828a1833085a0f013309023300ce02320a827828827720828a183308590f01330901b4a002ce028477018210208215188216109511283200951170ff7b1088007b1580007b16788279105109eb007b198275087b156833070a027b17107b177033078024017b17389517687b17488287208288284911400149115849112801491150019519387b17207b18185020d2026f970052078e0037075827035317026b6e779558500182169569ff9575ff3307a824017b1708017b193051099d007b18688217107b17708217087b1738491140014911589517687b1748491128014911500195193882172064868218185020d4020e970052072d956850019555ff8219309599ff5205b3821718827a183308610f01330901821720b4a002d6027b1728018217288210880082158000821678951190003200828928828720829c1833086f0701330902821088008215800082167895119000320c33099024018217647828188f00951170ff7b1088007b1580007b16788279105109ed007b198275087b15683307ba077b17107b177033078024017b17389517687b17488287208288284911400149115849112801491150019519387b17207b18185020da02439600520790003a076027035317026d97772098772095582082169569ff9575ff3307a824017b1708017b193051099c007b18688217107b17708217087b1738491140014911589517687b1748491128014911500195193882172064868218185020dc02df950052072c9568209555ff8219309599ff5205b4821718827a183308610f01330901821720b4a002de027b1728018217288210880082158000821678951190003200828928828720829c1833086f0701330902821088008215800082167895119000320c33099024018217647828ea8d009511d87b10207b15186485828828825a20828b187b173308c6070133090664a7b4b002e2027b15087817104611113308cc0701330b0e01951708330902641a5020e402bd9a007c19117c1810d4890751094284880152083c8217087c783484880452081a827828827720828a1833085a0f013309023300e602320a827828827720828a183308590f01330901b4a002e60284770182102082151895112832009511d87b10207b15188179979902330a1c0101c8a9098199c8a9093209828928828720829c183308790501330904821020821518951128320c828928828a20829c189577047b17330bfa070133090d64a764853308fa0701b4c002ee027b15087817104611113308ed0701330bd60095170833090d641a5020f002e699007c19117c1810d4890751090e01848801520807018217087c78348488045208cd00827828827720828a1833085a0f013309023300fc02320a828928828720829c183308d70701330907821020821518951128320c828928828a20829c189577047b17330bde070133090f64a764853308de0701b4c002f6027b15087817104611113308ed0701330bd60095170833090d641a5020f8024d99007c19117c1810d4890751097584880152086f8217087c783484880452084d827828827720828a1833085a0f013309023300fc02320a828928828720829c183308ce0701330909821020821518951128320c827828827720828a183308590f013309013300fc02320a827828827720828a183308590f01330901b4a002fc0284770182102082151895112832009511c87b10307b15287b16208179330a016485ad9a265209bb00825828825720828c18330807080133090a821030821528821620951138320c5219021001825828825a20828b189576049577087b1708330816080133090b64a7b4b00200037b15107817184611193308210801330b320195171033090e646a5020020341980033082f0801330bd600951710330911951a08502004032898007c19197c1818d4890751099a01848801520893018217107c783484880452084201827828827720828a1833085a0f0133090233001203320a825828825a20828b189577047b170833081f060133090864a7b4b00206037b15107817184611193308110801330bd600951710330905951a0850200803b097007c19197c1818d489075109220184880152081b018217107c78348488045208e100827828827720828a1833085a0f0133090233001203320a82582895790c825a20828b187b19089576049577087b17330840080133090b64a7b4b0020a037b151078171846111933082108013305320195171033090e646a330b320150200c032d970033084b080195171033090e821a645b50200e031797003308590801330bd600951710330911951a0850201003fe96007c19197c1818d4890751097084880152086a8217107c7834848804520848827828827720828a1833085a0f0133090233001203320a827828827720828a183308590f0133090133001203320a827828827720828a183308590f0133090133001203320a827828827720828a183308590f01330901b4a002120384770182103082152882162095113832009511c87b10307b15287b162083796482510932977720987320330807330902330a0133070128098b8801ca990901848b01510bf6ca7907aba8f0ae732233042831028228108227aa875403822708c8870746079588017b281028ee02330807330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b33090e330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330401ac83cc0133080e330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b330915330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330402ac838001330815330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b33091c330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330403ac83340133081c330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b330923330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330404ac83e800330823330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b33092a330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330405ac839c0033082a330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32a330931330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330406ac8350330831330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32c01330938330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330407ae8304018d4808330a02330b0133090128098b8801caaa0a01848c01510cf6ca9a09abb8f07b1210ae73228228108227c99305aa871201822708c887097895958c017b2c1033080128688a4603330802330a01330701646b28098bbb01ca88080184bc01510cf6ca7807ababf051071601822810822acb7305c99505aa8ae800822708c887097895958c017b2c10330902330a0133080128098b6601ca990901846b01510bf6ca8908aba6f05108fa008229cd8308c9c9097b1818ac492ec8c707951818644964c6644550201403a4cc00c865058217107b7510018210308215288216209511383200642764c8644964266445502016032fa7006454826c1082670828bc461118ff95181833090164276425643650201803b9a6007b1618951818330908645750201a03a7a60028ae3309016427642550201c03eca6006452825810289dfc330901642764367b140850201e03d3a600821408646382121082281028d6fe33090164277b137b140850202003b4a60082140882138212108228102800ff9511d03307e02e017b1749110801491120491110084911183308b82401641728f784003307d0240128dea4009511d07b10287b15207b16186486647533088c0050202203ebc4005107850081680849018c007b17089889187879039889107879028269787898880878780178790498983878780b98983078780a9898287878099898207878089898187878079898107878069899087879054911100c95680c95770c330980005020240345cb00821782180833098c007b59107b577b5808821028821520821618951130320033078c00282c80009517f0069518bc017b18f805330824077b18e8007b1800069518b8017b1808063308b6067b1848017b1810067b171806330720077b1718017b17200633070026017b17d8014921e001034921f8019518f8057b18e8014921f001039517c0019518d801502028033180008216c0018215c801821bd0013308490501330702330904645a0a640151060c645750202a03c0c7004921a8070e9517c0079518a80750202c0341a6008218c007140700000000000000807b183801ab7806284e498218c8078217d0077b1830017b18d8077b17e0079517d80750202e0344a900837752078e0a83874921e807330a083309087b1980017b19f0074921f807330c52070628df45978820988b203302b8001407f8ffffffffffff7f7b174001330320140700000000000000807b1760012811821b50013302b800c99b0b510b520a821ae807330759e67b05c9ca077b1578017b1b5001ae577fc8c505aec506289645d6250833075108062895459659b80082164001ae96062899457b1cb001510a1896a7b800821880017b18d0067b17e00633070828053307017b17d8069517f805330808951ad0065020300342cc8217f805510706284b45821700067b1780017b17f0077b15e807821cb001330320821578010196c7b800957ab0009a577b176801330601330b01281a0182178001c857077b7250ff951800108288f0f87b7858ff951800108288f8f87b7860ff821890017b7868ff821888017b7870ff821870017b7878ff95180010828830f97b789895180010828828f97b789095180010828820f97b788895180010828818f97b7880951800108288e8f87b78b8951800108288e0f87b78b0951800108288d8f87b78a8951800108288d0f87b78a0951800108288c0f87b78d0951800108288b8f87b78c8951800108288b0f87b78c0951800108288c8f87b78d8951800108288a8f87b78f8951800108288a0f87b78f095180010828898f87b78e895180010828890f87b78e0821898017b7895cc017b1cf80782197801d8960b95660182176801c86707955ab800330320330801aa8733fe7b1b88017b16a0017b1aa8017b1cb0018212e007ac320e088218d8079529e07c8a017c8b7c8c027c860397aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b057c8c047c86067c850797bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a40037c8a097c8b087c8c0a7c860b97aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b0d7c8c0c7c860e7c850f97bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a48037c8a117c8b107c8c127c861397aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b157c8c147c86167c851797bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a50037c8a197c8b187c8c1a7c861b97aa08d4ab0b97cc10976618d46c0c7c861d7c851c958a20d4cb0b976608d465057c8c1e7c861f7b1ad8077b19e00797cc10976618d46c0cd45c0c97cc20d4cb0b7b1b5803ac39e106952bc07ca9017cac7ca6027ca503979908d4c909976610975518d46505d459097cac057ca6047ca5067ca70797cc08d46c0c975510977718d45707d4c707977720d497077b17d8017ca7097ca9087cac0a7ca60b977708d4970797cc10976618d46c0cd4c7077ca90d7cac0c7ca60e7ca50f979908d4c909976610975518d46505d45909979920d497077b17e0017ca7117ca9107cac127ca613977708d4970797cc10976618d46c0c7ca6157ca514958940d4c707976608d465057cac167ca6177b19d8077b1be00797cc10976618d46c0cd45c0c97cc20d4c7077b17e8017ca7197cac187ca61a7ca51b977708d4c707976610975518d46505d457077cac1d7ca61c7ca51e7caa1f97cc08d46c0c97551097aa18d45a0ad4ca0a97aa20d4a7077b17f001ac3bb805952ba07c97017c9a7c9c027c9603977708d4a70797cc10976618d46c0cd4c7077c9a057c9c047c96067c950797aa08d4ca0a976610975518d46505d45a0a97aa20d4a7077b17f8057c97097c9a087c9c0a7c960b977708d4a70797cc10976618d46c0cd4c7077c9a0d7c9c0c7c960e7c950f97aa08d4ca0a976610975518d46505d45a0a97aa20d4a7077b1700067c97117c9a107c9c127c9613977708d4a70797cc10976618d46c0c7c96157c9514958a60d4c707976608d465057c9c167c96177b1ad8077b1be00797cc10976618d46c0cd45c0c97cc20d4c7077b1708067c97197c9c187c961a7c951b977708d4c707976610975518d46505d457077c9c1d7c961c7c951e7c991f97cc08d46c0c975510979918d45909d4c909979920d497077b171006ac3b8f04952780958880007b18d8077b17e0077ca7017ca87ca9027cab03977708d4870797991097bb18d4b909d497077ca8057ca9047cab067cac07978808d4980897bb1097cc18d4cb0bd4b808978820d487077b17d0067ca7097ca8087ca90a7cab0b977708d4870797991097bb18d4b909d497077ca80d7ca90c7cab0e7cac0f978808d4980897bb1097cc18d4cb0bd4b808978820d487077b17d8067ca7117ca8107ca9127cab13977708d4870797991097bb18d4b909d497077ca8157ca9147cab167cac17978808d4980897bb1097cc18d4cb0bd4b808978820d487077b17e0067ca7197ca8187ca91a7cab1b977708d4870797991097bb18d4b909d497077ca81d7ca91c7cab1e7caa1f978808d4980897bb1097aa18d4ba0ad4a808978820d487077b17e8069517d807502032033ca4003303205207570364858218e00751084d038219d8077c979588ff9599017b19d8077b18e0077b159801510717330807ae872b033306821560017b15900128a0009517ff07957701019518d807502034039aa00095170010827700f9821c6001aac71e037b179001951700107c7708f9951800107c880af9951900107c9909f9951a00107caa0bf9951b00107cbb0cf9978808d4980897aa1097bb18d4ba0a951900107c990df9951b00107cbb0ef9d4a808951a00107caa0ff997992097bb28d4b90997aa30951b001082bb10f97b1b7001d4a909d4980897860864c501d476069517ff07957701019518d80750203603fa9f0095170010827200f9aa523b027b1688019518ff0795880901828708828882194003821a4803951b00107bb788f8951700107b7880f8951700107b7960f8951700107b7a68f882175003821858038219d801821ae001951b00107bb770f8951700107b7878f8951700107b7940f8951700107b7a48f88217e8018218f00182191006821a0806951b00107bb750f8951700107b7858f8951700107b7938f8951700107b7a30f8821700068218f8058219d006821ad806951b00107bb728f8951700107b7820f8951700107b7900f8951700107b7a08f88217e0068218e80695190010829988f8951a001082aa80f8951b00107bb710f8951700107b7818f8951700107b79f8f8951700107b7af0f895170010827778f895180010828870f895190010829968f8951a001082aa60f8951b00107bb730f9951700107b7828f9951700107b7920f9951700107b7a18f995170010827758f895180010828850f895190010829948f8951a001082aa40f8951b00107bb7e8f8951700107b78e0f8951700107b79d8f8951700107b7ad0f895170010827738f895180010828830f895190010829928f8951a001082aa20f8951b00107bb7c8f8951700107b78c0f8951700107b79b8f8951700107b7ab0f895170010827718f895180010828810f895190010829908f8951a001082aa00f8821be807951c00107bc7a8f8951700107b78a0f8951700107b7998f8951700107b7a90f8821cb0018215a8018216a001abbc11f79517e8077b125801502038037ba00082125801821cb0018217f0077b17800128eef61407000000000000008082189001d47808330320aa7810646750203a0335bd003303200182178801847701520729821cb001821b50013302b80082197801c99b0b520bfff52849821788018477013303205107dd8218b00151080628663c8217e80751070e8217800150203c03e5bc0033075a1f01330a083001330b2830013308239519ff079599500128818a008218e80714070000000000000080aa78d48215f0077b180801821738015107138217300164c650203e039abc00646c0196c7b800c857077b177001140700000000000000807b1798011407f8ffffffffffff7f7b1750014921380121384c4921d000abaaaaaa7b150001280d9595b80082179801ab763582177001ab750628a63a645982578256188258207b1868018258287b18b0015107d682970864955020400322bc00645928c57b16f0007b19f80082176801951800107b8760f88217b001951800107b8768f89517ff0795776150204203bf9d0083773303d80051070628e23a83874921d8013309087b19e0014921e801520706280d3a330c97882098852033020828153303d8008212a80182157801c9a50551057c018214d80133074be6750ac9c4077b1578017b1a9001aea73fc8ac06aec60628a73ad63608330751080628a63a9669d800821b5001ae9b0628aa3a7b16b00164c551041a9647d8007b12d0067b17e006330708280b7b12a80128433307017b17d8069517f805330808951ad00650204403d6c08217f80551070628583a821700067b17a8017b17e0018217b0017b17d801645c821a90013303d8000196c9d8009aa77b178801330501330b01283f7b18a801c868079518d0063309d80050204603b6bd00821cb00195cc017b1ce801821a9001d8a50b95550182178801c857079569d800330801aa8707ff64b67b1cb0017b19a0019517f8059518ff0795886150204803358d8217f805821898019588187b188001aa87409517d0069518f8053309d80050204a034fbd008217d8018218a8018216a0018219b001ab7973ff3308c02f019517d80150204c03bdc18218e001285cff846701821578015107062812393303d8008212a801821cb001821a9001c9a50552058cfe8218d80182179801ab78062825397b18d80096c7d800c872087b12a801520c062803381407ffffffffffffff7f7b17600164267b18400128169517f80550204e0394ba82184001ab860628dc3782659568089566d8009517d8013309d000502050039cbc0082178001ab750628bf377b1678017b15f8059518d8013309d000951700065020520377bc009517f8057b174003330724027b17b0017b17480333073026017b17d0064921d806014921f006951840037b18e0064921e806019517a8029518d00650205403b2718216a8028215b002821bb8023308490501330703330904645a330056030a640151060c6457502058033eb900821af80582196001c89a07887817959501d35a079387541717072838199777023308300101c887078177c887077b15580132077b1a2001951c10067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17d8027cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17d0027cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17c8027cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d48707821830067b18b0018216400682184806821538067b17c002951700107b7620f8951700107b7840f89517d006330980009518500650205c0381ba00821700067b17c800821708067b17e0009517c0028218b0016459646a0a090198782051080628a91b977720d4780884880152080628c61b33081150205e0380b300821678015207062845393308050a0133091164755020600325ba00330b11330733083309645a0a6464575020620343b7003307018219200182186001d29808510826262849348218100682190806821700067b17c8009787037b18b001978805c97808c898087b19e0007b1940037b1820017b18480395174003951800107b8718f93307c802951800107b8720f933079029017b17d0064921d806014921f0069518ff07958819017b18e0064921e806019517a0049518d00650206603fd6e8216a0048215a804821bb0043308490501330702330904645a0a640151060c6457502068038db60033080450206a038fb20052070628be3864758218b00178789887187857039887107857029887087857013307f70501330808330a0464590a03013308f7ac870c3308ffaa8706285538645750206c033bb6008216e0008217b00152070628d21833058216e00095661828199566188217900195771895550182182001ab870628ed188268e882179801ab780628a3187b1888019567e88268f07b18b0017b1790018277107b17a001951700107b75d0f89517ff079577d1007b17400333078e067b1748033307a029017b17d0064921d806014921f006951840037b18e0064921e806019517b8049518d00650206e03e46d8217c0048218c8048219b8047b1930017b1728018219b001821aa001330070030a03013308f7ac870c3308ffaa870628de358217300151070e821728015020720358b50082178801510732ff8217b0015020740345b5002823ff8216000682179801aa760628e81833076d0b013308093309330a330078030a0301957801821678015518f8ba3d2806240a0833080d50207c0312b1005207062847373308dd0b0133090d647550207e03bbb7003308490501330702330904330b0d645a0a6464573300380428d0b400801720067a179405951600067c68197c69187c6a1a7c6b1b978808d4980897aa1097bb18d4ba0ad4a8087c691d7c6a1c7c6b1e7c6c1f979908d4a90997bb1097cc18d4cb0bd4b909979920d49808951900107b9830f97c68117c69107c6a127c6b13978808d4980897aa1097bb18d4ba0ad4a8087c69157c6a147c6b167c6c17979908d4a90997bb1097cc18d4cb0bd4b909979920d49808951900107b9828f97c68097c69087c6a0a7c6b0b978808d4980897aa1097bb18d4ba0ad4a8087c690d7c6a0c7c6b0e7c6c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d49808951900107b9820f97c68017c697c6a027c6b03978808d4980897aa1097bb18d4ba0ad4a8087c69057c6a047c6b067c6c07979908d4a90997bb1097cc18d4cb0bd4b909979920d49808951900107b9818f99518ff07958819013309200a1b01520706285c19957809951900107b97d0f85318070728b6379a87062848197f171806951a000682a81082a90882aa951b001079b7b0f8951700107b7828f9951700107b7920f9951700107b7a18f99517ff079577b1007b17400333072c017b1748039517ff07957719017b1750033307d8027b1758033307f028017b17d0064921d806024921f006951740037b17e0064921e80602951730059518d00650208403376b8216300582153805821b40053308490501330702330904645a0a640151060c645750208603c7b20095170010827828f9590806286e35951700107e77b0f87b17900195170010827720f97b17a0017b1788017b18b00197860595650364585020880396ae00520706284e347b15d0067b17d8064921e0068218b00152080628e91b7b17b0013305821aa001018217d006c9570754171f378217b0010195a8207b18a0017b17b001c8570733092064a850208a0305b5009555209566e07b15e006821aa0015206c8281b159517d006330920645864a550208c03938f00645a8215e0068217d80628b681172006951800107a87b0f8951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8730f97cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8728f97cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8720f97cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8718f99517ff079577b1007b1740038217e8007b1748039517ff07957719017b1750033307ba077b17580333075027017b17d0064921d806024921f006951740037b17e0064921e80602951720049518d00650209003c3688216200482152804821b30043308490501330702330904645a0a640151060c64575020920353b000951700108077b0f89518ff07958819010a0c01520706288c16957809951900107b97d0f85318070728e2349a8706287816951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1758037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1750037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1748037cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d487077b174003951740030a100151070628473195174003951800107b8718f933074e01951800107b8720f933072029017b17d0064921d806014921f0069518ff07958819017b18e0064921e80601951778059518d00650209603f9668216780582158005821b88053308490501330702330904645a330098030a640151065915284c15951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1758037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1750037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1748037cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad48a0a821820068219280697aa20d4a7077b174003951740030a0a01510706283f3133080950209c035ca9008216780152070628152f3308160a01330909647550209e0301b0003308490501330702330904330b09645a0a64645733007a042816ad00821700067b1728018218100682160806951700104927d0f87b1630017b18880152080628c513978703978805c978092826951700108277d0f8957701951800107b87d0f88219b001019599e89566185209062843107b19b0017b165004951750047b1740033307f6007b1790017b1748033307a027017b17d0064921d806014921f006951840037b18e0064921e80601951758049518d0065020a203c364821758047b17a00182156004821b68043308490501330702330904645a0a64018217a00151070c64575020a4034bac00821850048287088288103309330a0a03018219b0015317f766ff5117ff07286e2c951750047b174003821790017b1748033307b027017b17d0064921d806014921f006951840037b18e0064921e80601951770049518d0065020a603356482157804821b8004821770047b17a001330733083309645a0a64018217a0015107e6fe64575020a803c1ab0028d9fe9518000682861082878288087b1650037b1740037b18b0017b18480359060628492e966550017b15900195550364585020ac0394a700520706284c2d7b15d0067b17d8064921e006520606286c157b17880133068217b0019577c0007b17b001018217d0067b17a001c9670754171fcb0082178801018218b0019588c06475c867073309205020ae03fead009566208217a001c967077b16e00654171fc0006457018218b0019588e06475c867073309205020b003d2ad009566208217a001c967077b16e00654278f00b7006457016475c86707330990008218b0015020b203a7ad00956690008217a001c967077b16e00654177faf006457018215b001955840ff7b178801c86707330980005020b40376ad00956680007b16e006821790019577b0fe955550017b15b0017b179001520731ff28f00d9517d00633092064685020b603f787008216e0068217d8068218d0067b18a001281fff9517d00633092064685020b803d487008216e0068217d8068218d0067b18a0012828ff9517d0063309900064685020ba03b087008216e0068217d8068218d0067b18a0012831ff9517d0063309800064685020bc038c87008216e0068217d8062840ff81170006951800107a87b0f89517ff079577b1007b1740038217e8007b17480333074027017b17d0064921d806014921f006951840037b18e0064921e80601951708049518d0065020c003fc618216080482151004821b18043308490501330702330904645a0a640151060c64575020c2038ca9009517d0065020c403f3900082171007531751580d811818075218024f0d9578af9517d0060a0f018216780152070628aa17957809951900107b97d0f8531807072800333308265020c6034ba500520706283b33647533086b0a0133092664575020c803f2ab00330b26330733083309645a0a64645733007a04280ca900330528143300cc030a08018355015225e8030828c40f951700107a75d0f89517b8017b174003821648017b1648039517ff079577d1007b1750037b1658033307482a017b17d0064921d806024921f006951840037b18e0064921e806029517b0059518d0065020ce03ec608217b0057b1790018217b8057b17b0018217c0057b17a0019517ff079577d1007b1740037b16480333070009017b17d0064921d806014921f006951840037b18e0064921e806019517c8059518d0065020d00397608216d005821ad8058217c8057b1788018217b0018218a00164690a03013308f7ac870c3308ffaa87062862278217880151070c64675020d20313a80082179001510704ff8217b0015020d40300a80028f5fe811790068218800682198806951a00107aa700f8951700107b7820f8951700107b7940f89517400333098000951800065020d80392aa000a951800107b87d0f83307e026017b17d0069517ff079577d100951800107b8718f982171801951800107b8720f94921d806014921f0069518ff07958819017b18e0064921e806019517c0039518d0065020da03b85f8216c0038215c803821bd0033308490501330702330904645a0a640151060c64575020dc0348a7009517ff079577017b17d006821748017b17d8069517ff079577217b17e006821718017b17e8069518ff079588417b18f0067b17f806951740037b1700073307c0077b1708073307f02601951800107b8718f995170010492720f9049517ff079577390149079517d006951900107b9728f995170010492730f9049517d8039518ff07958819015020de03045f8215e003821be8038216d80333070233083309645a0a640151060c64575020e00398a60095170010807700f895180010828820f895190010829940f8951a40030a0b015107190e957809951900107b97d0f853180707285c2b9a870628040e951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8730f97cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8728f97cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8720f97cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad48a0a8218200697aa20d4a707951900107b9718f9951700107b78b0f89517ff07957719010a0e015107062862289517ff07957719017b1740033307ba077b1748039517ff079577b1007b175003821718017b1758033307c027017b17d0064921d806024921f006951740037b17e0064921e806029517e8049518d0065020e403085d8216e8048215f004821bf8043308490501330702330904645a0a640151060c64575020e60398a4003307390b013308099519ff0795991901330a203300e8030a03015317f7460b5117ff410b28ee27951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8730f97cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8728f97cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8720f97cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a80882162006978820d48707951800107b8718f99517ff079577190164680a0d01837b987a20551b03cd0d951900107a97d0f8951900107a9ad4f8951900107a98d8f8531b02fb0d8119b801521b02d60d3907682703be8707330b04ae97d70d330728dc0d8217000682169801aa67b1097b179001821708067b17a0018215100633083e0c019517d0063309145020ee033c89008218d006ab68770e4921b0015205120f28851082170006951800107b87d0f89517ff079577d1007b174003821718017b1748033307e029017b17d0064921d806024921f006951740037b17e0064921e806019517d0049518d0065020f203aa5a8216d0048215d804821be0043308490501330702330904645a0a640151060c64575020f4033aa2003308085020f6033c9e0052070628ff236475951700108277d0f878579878387858079878307858069878287858059878207858049878187858039878107858029877087857013307cf0501330808330a0864590a03013308f782167801ae870628c2103308ffab87b22328b810801c1806951700107a7c40f8951a000682a780181c068019200682ab10951200107b2718f982a608951700107a7890f8951700107a79b0f8951700107b7b28f9951700107b7620f9510b260a7b1ba0017b19c8007b18e0007b1c20017b17b00133080c5020fa036f9d00520706282c218065826808787598591878790398591078790298590878790178780498893878790b98893078790a9889287879099889207879089889187879079889107879069888087b17880178780533080c5020fc03159d0052070628d220647a8217a001977704c867077b1730019567107b17900182178801957b0c9576018458ff0078a84921d0060c7b1ad80633080128188219d0067c65956601aa98a400c88a097895958801017b18e0068215b001abb6e27b1a88017b18a0015020fe039ea000821690015106250482173001aa761d0433080c50200004909c00520706284d2081688269087878988a18787a03988a10787a0298880878780178790498983878780b98983078780a9898287878099898207878089898187878079898107878069899087879059566107b169001957b0c64768218a001821a88012856ffc96b0a3309fee69a099599017b1788019517d0067b18a0017b1b280150200204d3a9821b28018218a00182178801821ad806282eff9517d0069518f8053309d80050200604a5a2009517d006951800107b8718f98217b001951800107b8720f93307802a017b1740034921480301492160039518ff07958819017b18500349215803019517e0059518400350200804d8578216e0058215e805821bf0053308490501330702330904645a0a640151060c645750200a04689f009517d00650200c0400a0821678012868e5951c04067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1758037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1750037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1748037cc7017cc87cc9027cca03977708d4870797991097aa187cc805d4a909d497077cc9049788087cca067ccb07d489098018000697aa1097bb18d4ba0ad4a909979920d497077b1740039517ff07957719019519400350201004af83009517ff0795771901951800107b87d0f83307c401951800107b87d8f833078828017b17d0064921d806014921f0069518ff079588d1007b18e0064921e80601951718059518d0065020120420568216180582152005821b28053308490501330702330904645a0a640151060c645750201404b09d0095170010827618f982179801ab763a0333076d0b013308093309330a330016040a03019578015318f80728862695170010827718f982189801d48707aa873c0495170010827520f9645733003804285b9d0082172001aa673982172001c967078219d000979820c89808d68705985504956608280b9555ff9566185105148267f85107f4826750201804239d0028e88217c8005107e5038217e00033003804280a9d008217d0067b17a001821790018216b00164680a060164758217a0015207800651058906955709951800107b85d0f88216780153170707284f2133081150201a04d69800520706289f1e64753308bf0b01330911645750201c047d9f00330b11330733083309645a0a64645733005a0428979c008216d0068215880164570a0701647952068b06289506951700108277d0f882188801284a038215d0068216d806821c20018218e0008219c800821ba00164c7646a0a050151070a05280c1f82150806821a100633076d0b01330809645933001e040a03013308f7ac87f9013308ffaa87f20128982433082f502020042798008216780152070628442564753308910a0133092f645750202204ca9e00330b2f330733083309645a0a64645733007a0428e49b005517f707286425957809951900107b97d0f8531807072807259788089a8700068678018488015108062840e4987720951800107a8700f89517ff07957701951800107b8718f98217e800951800107b8720f933074026017b1740034921480302492160039517ff07957719017b17500349215803019517e0029518400350202404ac538216e0028215e802821bf0023308490501330702330904645a0a640151060c6457502026043c9b0095170010817700f8951800107a87b0f87b1738330807502028042b970052070628ca2464754615066446150565461504744615036146150265461501724605633308079519ff079599b100330a0464570a03013308f7ac870c3308ffaa8706285225645750202a04d19a009517ff07957719013308ff9519c00250202c0445800095170010827518f982179801aa75620a95170010827620f995170010827928f995170010807700f864680a1b017b15b0017b16a0015107a609957809951900107b97b0f8531807072821279a870628910995170010827520f995170010827a28f933076d0b01330809645933002e040a03013308f7ac870c3308ffaa8706280d235206f90028fe0033070701951800107887d0f8951794057b174003821748017b1748039517ff079577d1007b1750033307ea017b17580333075829017b17d0064921d806024921f006951740037b17e0064921e80602951798059518d006502030041e52821698058215a005821ba8053308490501330702330904645a330032040a6401520675287b33070701951800107887d0f89517ff079577d1007b1740033307ea017b17480333073027017b17d0064921d806014921f006951840037b18e0064921e80601951738049518d00650203404ac518216380482154004821b48043308490501330702330904645a330036040a640151060c64575020380438990082167801282508330701c97807951800107b8718f99517ff07957719017b17400333078e067b1748039518ff079588d1007b1850037b17580333077027017b17d0064921d806034921f006951740037b17e0064921e80602951788049518d00650203a041a5182159004821b98048216880433070233083309645a33003c040a640151060c645750203e04aa9800821678018218880151082e82153001955508280b9588ff95551851081c8257f85107f482576486502040047c980064688216780128e082172801510761078217300133007a04285d98003307070195180010788700f99517ff0795770101951800107b8718f93307ea01951800107b8720f933073027017b17d0064921d806014921f0069518ff07958819017b18e0064921e806019517f0039518d006502042044f508216f0038215f803821b00043308490501330702330904645a0a640151060c645750204404df970095170010817500f895170010827620f895184003330980009517dc0650204604889a007a15d8067b16d0069517ff079577d1009518d00650204804aece951700108275d8f895170010827ae0f833077c0c0133080b645933004a040a03013308f7ac870c3308ffaa8706281f1b951700108277d0f88216780152074e06285306330533060164c7330a010a050151070628091a51050c646750204c044097009517ff079577417b17d006821748017b17d8069518ff07958891007b18e0067b17e8069518ff079588b1007b18f0067b17f8069517ff07957719017b1700073307bc017b1708073307b028017b1740034921480304492160039517d0067b1750034921580304951748059518400350204e04144f82155005821b58058216480533070233083309645a330050040a640151060c645750205204a4960095170010827718f9510764fd95170010827720f9330038042885960064768217900164680a066475646733005404286f960064675020540469960052057ff9330809502056046792008216780152070628201864753308b60b013309096457502058040a99003308490501330702330904330b09645a0a64645750205a0423960095170010827718f951070c0595170010827720f933007a042804960064750a076479016457649550205c04f5950064590151090628691895174003951800107b8718f93307d002951800107b8720f933071029017b17d0064921d806014921f0069518ff07958819017b18e0064921e80601951760059518d00650205e04eb4d8216600582156805821b70053308490501330702330904645a330060040a640151060c6457502062047795008217400351073bfc821748033300380428609500511b030728cc1f988b20951900107a97d0f88119b801951700107a7ad4f8951700107a78d8f8951700107a7bdcf8013907682703be870c330b03330701ac9c0e390a682703c88a0a64b7017b16b001951800107a87b0f8951700107a7ab4f89517ff079577d1007b1740033307fe027b1748039517ff079577b1007b1750033307e8027b17580333072828017b17d0064921d806034921f006951740037b17e0064921e80602951700059518d00650206404f74c8215000582160805821b10053308490501330702330904646a0a640151050c6467502066048794009517ff07957719018218b0010a0f01510706285f173307560b0133080b9519ff0795991901330a20330068040a0301821678015317f745035117ff40032864177b1588018217e00655170807288b1e8217d8067c797c76017c72027c73037c7a047c75057c7b067c7c075108437b1ab0017b16c80064b67b1c30017b1928017b15200164257b13e00050206a04f793008213e00064528215200182192801821c3001646b8216c800821ab00101976608d49607972810973918d49808d48707975808d4a80897b91097ca18d4a909d49808978820d487077b17b0018215880151057b019755058216a00128179566209555e08217b0019577017b17b00151055d017c67197c68187c691a7c6a1b977708d4870797991097aa18d4a909d497077c681d7c691c7c6a1e7c6b1f978808d4980897aa1097bb18d4ba0ad4a808978820d47802951700107b7230f97c68117c69107c6a127c6b13978808d4980897aa1097bb18d4ba0ad4a8087c69157c6a147c6b167c6c17979908d4a90997bb1097cc18d4cb0bd4b909979920d48903951700107b7328f97c69097c6a087c6b0a7c6c0b979908d4a90997bb1097cc18d4cb0bd4b9097c6a0d7c6b0c7c6c0e7c670f97aa08d4ba0a97cc10977718d4c707d4a707977720d47909951700107b7920f97c67017c6a7c6b027c6c03977708d4a70797bb1097cc18d4cb0bd4b7077c6a057c6b047c6c067c680797aa08d4ba0a97cc10978818d4c808d4a808978820d48707951800107b8718f97b1258037b1350037b1948037b1740039517400333082095194003330a200a03013308f7ac87a0fe3308ffaa8799fe2841128217900151070e8217a00150206c041b920033080850206e041d8e008216780152070628dc1364758218b001787898873878570798873078570698872878570598872078570498871878570398871078570298870878570133073e0c01330814330a0864590a03013308f7ac87aa003308ffaa87a30028f719951700108176b0f87b17b00133087450207004aa8d0052070628131e647578769867187857039867107857029866087856019557043309703308502072048996006457330833090a0a0151070628e51d6457502074045b9100330809502076045d8d008216780152070628161364753308620a013309096457502078040094003308490501330702330904330b09645a0a64645733007a0428159100645750207a040f91008217f80582186001c8780888881782195801d3970793875417160728f8d682183801d078078477015107ebd628f1d633070701951800107887b0f89517ff079577b100951800107b87d0f83307ea01951800107b87d8f833076026017b1740034921480301492160039518ff079588d1007b18500349215803019517f8029518400350207c04cf488215f80282160003821b08033308490501330702330904646a0a640151050c646750207e045f90008217b00151070e8217a001502080044d90000a951800107b87d0f833077026017b1740039517ff079577d100951800107b8718f982171801951800107b8720f94921480301492160039518ff07958819017b18500349215803019517100395184003502082043f488216100382151803821b20033308490501330702330904645a0a640151060c645750208404cf8f0095170010807700f895180010828840f895190010829920f8951ad0060a0b0151071a957809951900107b97d0f85318070728a3199a870628063307070195180010788700f99517ff079577417b174003821718017b1748039518ff079588217b1850037b1758039517ff07957701017b1760033307ea017b1768033307802601951800107b8718f995170010492720f9039517ff0795773901490795174003951900107b9728f995170010492730f903951728039518ff07958819015020860455478216280382153003821b38033308490501330702330904645a330088040a640151060c645750208a04e18e008217980182182001aa78d80b33081450208c04d78a0052070628701849214003147b174803492150033307109518400350208e049cc18215500382194003c9590754170f290c7b19b0010182164803c856073308100901330910502090044c91009559109517ff0795771901646850209204e6748217b00151070c6467502094045d8e0095170010827818f982179801ab78113306330a330b08821320012888007b181095170010827820f995170010827728f97b1808951900107b9890f8951800107b8798f89517ff079577910050209604da6f83778213200151073c0482169801821828821920018217107b18287b1920510718821708643550209804df8d0064538219208218280114070000000000000080ab760c3306330a330b082807649b648a018217d8028218d0028219c802951c00107bc730f9951700107b76d0f8951700107b7828f9951700107b7920f98217c00295180010818800f87b189001951800107b8bd8f8951800107b8ae0f8951800107b8718f97b1ba001510ac0007b163001330995b5107b1a880197a6068218e000280f9599019566c0955540510685008257ab78f18257f88218c8007b19b0018219e00050209a04a192008219b0018218e00083775207ce9797068218a001c8870782768278087b1828019578408599ff82158801c8590997990650209c047090009555ff7b158801951700107b75e0f88213200151062a82172801643650209e04c78c0064638216300182158801281d821320018216300182158801280f8216300182158801280533050195170010827730f995180010828828f995190010829920f9951a001082aa18f97b1758037b1850037b1948037b1a4003ab65269517ff079577d100645664355020a004606864536465951700108277d8f87b17a001019758068217a0017b18b001c887077b738218c8007b78088218e0007b7810821850037b7828821848037b7820821840037b7818821858037b7830821890017a7838955501951700107b75e0f859050628ee16951700108277d8f87b17a00197560695660464685020a204e98700520706288a167b1640037b17480398572049215003510706287a169518400364575020a404a7be51052f018217a0019578388217b001957640282f821690017b1ba00195a8e0c85b073309205020a604568e008218b0019555207b1550039566c09588405106ff007b1690018285d89857205207ae0c7b18b0018287d07b1730019518400364575020a80441be8216500382184003c96807ac5782007b18880101821748037b17a001c867078218300164595020aa04f08d00c856087b185003821ab00181a5821c8801c98c075417036a821ba00101c88b079859187879039859107879029859087879017875958504c95c077b15500356171f44ff9517400333092064585020ac045168821ab00182155003821b48032824ff95174003646864595020ae04346882165003821740037b178801286dff951740033309045020b0041868821ab00182185003821b4803821c40032882821748037b17a0018215500301821740037b17b0013308145020b204748600520706280d1449214003147b17480349215003330710951840035020b40439bd821a500382194003c9a90754170fe4077b198801017b1a900182164803c8a60733081009013309105020b604e58c00821890019588107b16300164678219a001645a3300b8040a030164795519f70d7b1990013306286c065119ff5f06959709951800107b89b0f8330807ac87062869179a99fd7b1990013306012846068387951900104929b0f8951a0010492ab8f808951900104929c0f85107d706978820988820282082132001018217b0008477015207c9068218308217b800c9780851084e076489951700108277c0f895180010828ab0f8330800017b1930e68909c97a087b19b800ae988b008215b800c87505ae750628ba1598583a330751080628b915975906821b5001ae9b0628ac156436510a1b951700108277b8f897aa067b1740037b1a500333070828053307017b1748039517ff0795771901330808951a40035020ba049b8e95170010827718f951070628431595170010827720f9951800107b87b8f8951700107b75b0f864630195170010827698f833044921b00001288f00951700108277b8f8976806c88707821890017b78821888017b78088218b0017b7810821840037b7818821848038214c0009544017b7820821850038219a001979908821ab000d4a909821aa80097aa107b782882185803821b280197bb18d4ba0ad4a9097b78307a7938956601951700107b76c0f88217b800d874087b18b0008213200182163001aa749ffe51069bfe95170010827790f87c72956bff957a01951800107b8a90f8951800107b8b98f83308017b1888017b14c00051026a5222ff006c330809ac8661fe7c78027c79017c7a037c7b04978808d4980897aa1097bb18d4ba0ad4a8087c79067c7a057c7b077c7c08979908d4a90997bb1097cc18d4cb0bd4b909979920d49808956bf7957a09951700107b7a90f8951700107b7b98f828c100642c28d3016c2857080f330595170010492718f9285084874033050151073c84872033050251073384871033050351072a84870833050451072184870433050551071884870233050651070f84870133050751070628251595170010492718f9ac5b87039517ff07957719017b18b00164a864597b1aa00164b65020bc04eb8900821aa001c9560bc85a0a951700107b7a90f88557079b77ff8218b001e0780795180010828818f9951900107b9b98f8975503cf5707c878088213200101988720520741fdac8b3dfd330251080701330549218801010133070040e67806c952077b1aa0017b183001ae672ec86509ac59600757095c077b1b280151022182178801951800107b8718f9951700107b7228f9330701280ac865072851330701951800107b8720f99517ff079577d100330801951aff0795aa19017b19b0015020be04f78b951700108277d0f85207f307951700108277d8f87b1788018217b0016472821aa001821b2801017b17b001ac57d206ac6b550282178801c8570764a8646964b57b1290015020c004dd88008218300182129001821aa001c9650bc96808c86a0a821cb00164c552082dff951700107b7b98f814070000000000000080951800107b8a90f882132001ab72092833fc330c01531b0424027ca57ca7017b17a0017ca7027ca60395b0fc95a804951900107b9890f8951900107b9098f833091fae09f7017b17a8007b15b0007b1628017b1cb0017b12900195b9dc95a724951a00107ba790f87b193001951700107b7998f87c87117c89107c8a127c8b13977708d4970797aa1097bb18d4ba0ad4a7077c89157c8a147c8b167c8c17979908d4a90997bb1097cc18d4cb0bd4b909979920d479057c87077b17a0007c87067b1798007c87047b1790007c87057b1788007c87037b1780007c87027b17787c877b17707c87017b17687c870f7b17607c870e7b17587c870c7b17507c870d7b17487c870b7b17407c800a7c86087c84097c831f7c821e7c8c1c7c8b1d7c8a1b7c87197c89187c881a7b155003977708d4970797881097aa18d4a808d4870797bb08d4cb0b972210973318d42308d4b808978820d487077b175803974408d46407970010821840978818d40808d48707821848978808821950d49808821958979910821a6097aa18d4a909d49808978820d487077b174803821768977708821870d4870782187897881082198000979918d49808d487078218880097880882199000d4980882199800979910821aa00097aa18d4a909951a001082a6c0f8951a001082aab0f8d49808978820d487077b174003aba628fb9517ff079577b1005020c204de8b2816fb951700107b7b98f8951700107b7a90f8510225fa821788015020c404b983008217b0008477018213200151071bfa28dc00510209fa8217880164355020c604968300645328f6f933068217187b179001018217880151070e821730015020c8047583008217b00151070e8217a0015020ca0463830051060628320e951700108276e0f8951700108277d8f87b17b0015106268215b001955508280b9566ff9555405106148257f85107f482575020cc0425830028e8951700108277d0f851070e8217b0015020ce040d8300821790017b171882179801018216780182192001d3970788770182186001d298085108ddf15107d9f18217c80033007a0428d582003308330633090828daf4951700108276c0f8951700108277b8f87b17b0015106268215b001955508280b9566ff9555405106148257f85107f482575020d00496820028e8951700108277b0f851070e8217b0015020d2047e8200821320012878f49517400333091064585020d404e75f82155003821740037b17b00128c6f39517400333091064a85020d604c95f821a5003821740037b178801280bf8951700108276b0f814070000000000000080aa7627f4951700108279b8f8951700108278c0f8281ef4642601ab680c283782184001aa683114075f427b09ed25b497c96808d6780598550764673300d80428868264675020d80481829555ff9566d8005205f18217d80051070e8217a8015020da04c381008217f8008218f0009575b8005208062864c5821768015020dc04a681002856c5821708018218000151070c64875020de048f8100821710014607951178078210e8018215e0018216d8019511f001320033050849210801821738015107d1c428bdc433073309a82f01280639281282170006821808063309a82f0128f4383309a82f0128ec388216b0015106228215a80164573300e00428c88164575020e004c3819566ff9555d8005206f18217d80151070e8217a8015020e2040581003307960401330ab02301330b902a0133082b9519ff079599500128a14e33073309a82f01288d38281282170006821808063309a82f01287b383309a82f01287338957809951a00107ba718f95518078b008d87067817d0063307580c01330a782301330b682a0133080b9519d006284d4e82178001957520140600000000000000807b16a801280f9588ff9555b800520806287cc38257f88219a801d49707ab970b8257e052071c28e082577b18b0015020e4044e80008218b0018257e05107ca8257e864865020e604388000646828b933070c2807389517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628e23b957809951a00107ba7b0f85518079d008d87067817d0063307ef0501330a782301330b302a013308089519d00628784d957809951a00107ba718f9551807b8008d87067817d0063307981f01330a883001330bc030013308449519d00628484d957809951a00107ba718f9551807e4008d87067817d0063307ef0501330a782301330bb029013308089519d00628184d3309902f0164578218b00128593b33073309a82f0128f6369517ff079577b100951800107b87d0f882171801951800107b87d8f833071832017b17d0064921d806014921f0069518ff079588d1007b18e0064921e8060133082832019517d00628ef3a9517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628b03a3307170501330ab02301330be823013308169519ff079599500128594c9517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628543a951700108277d8f8951800108288e0f83309a82f0128f83533077d1f01330958300133081b28784c6457280d3633070928073633070828013633071128fb35957809951a00107ba718f955180758038d87067817d0063307ef0501330a782301330b002a013308089519d00628ab4b957809951a00107ba718f955180767038d87067817d0063307ef0501330a782301330bc829013308089519d006287b4b33070428953533070d288f35957809951a00107ba7d0f85518076a038d87067817d0063307981f01330a883001330ba830013308449519d006283f4b959709951a00107ba9d0f855170785038d78067818d0063307fb1f01330a883001330b2031013308339519d006280f4b3307d82f012808353307d82f01280035957809951a00107ba7d0f855180790038d87067817d0063307f30301330a782301330b9823013308489519d00628cf4a957809951a00107ba7b0f85518079f038d87067817d0063307960401330a782301330b70280133082b9519d006289f4a957809951a00107ba7b0f8551807ae038d87067817d0063307ef0501330a782301330b5828013308089519d006286f4a957809951a00107ba7d0f8551807bd038d87067817d0063307960401330a782301330bf8270133082b9519d006283f4a957809951a00107ba7d0f8551807cc038d87067817d0063307ef0501330a782301330be027013308089519d006280f4a957809951a00107ba790f8551807db038d870695180010788718f93307ef0501330a782301330ba82a013308089519ff079599190128d749957809951a00107ba7d0f855180708048d87067817d0063307fb1f01330a883001330bf030013308339519d00628a74933073029017b17d0064921d806014921f0064921e006084921e80633084029019517d00628ba379517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006287b379517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006283c379517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628fd369517ff079577d100951800107b8718f982171801951800107b8720f933071832017b17d0064921d806014921f0069518ff07958819017b18e0064921e8060133082832019517d00628b2369517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d0062873369517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d0062834369517ff079577d100951800107b8718f982171801951800107b8720f933071832017b17d0064921d806014921f0069518ff07958819017b18e0064921e8060133082832019517d00628e9359517ff079577d100951800107b8718f982171801951800107b8720f933071832017b17d0064921d806014921f0069518ff07958819017b18e0064921e8060133082832019517d006289e359517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006285f359517ff079577b1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d0062820359517ff079577b1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628e1349517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628a2349517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d0062863349517ff0795779100951800107b87b0f882171801951800107b87b8f83307183201951800107b8718f995180010492820f9019518ff079588390149089518ff079588b100951900107b9828f995180010492830f90133082832019517ff079577190128fe339517ff079577d100951800107b8718f982171801951800107b8720f933071832017b17d0064921d806014921f0069518ff07958819017b18e0064921e8060133082832019517d00628b333957809951a00107ba718f9551807b0028d87067817d0063307ef0501330a782301330b182a013308089519d006284945957809951a00107ba718f9551807bf028d87067817d0063307ef0501330a782301330b7829013308089519d006281945957809951a00107ba718f9551807ce028d87067817d0063307981f01330a883001330bc030013308449519d00628e944957809951a00107ba7b0f8551807dd028d87067817d0063307ef0501330a782301330b9828013308089519d00628b944957809951a00107ba7b0f8551807f8028d87067817d0063307981f01330a883001330bc030013308449519d00628894433072f28a32e9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328723233074032017b174003492148030149216003492150030849215803330868320195174003284b32330714282b2e33070728252e9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328f43133072628d42d9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328a3313307960401330ab02301330bc02a0133082b9519ff0795995001284c433307102801282039957809951a00107ba7d0f855180779023308fdbf780664575020ea046c75781640033307ef0501330a782301330bc8260133080895194003280943646728242d3307170501330ab02301330be823013308169519ff079599500128e74282179001781740033307ef0501330a782301330bb026013308089519400328c6423307d82f0128bf2c9517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628b9309517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006287a309517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006283b309517ff079577b100951800107b87d0f882171801951800107b87d8f833071832017b17d0064921d806014921f0069518ff079588d1007b18e0064921e8060133082832019517d00628f02f9517ff079577b100951800107b87d0f882171801951800107b87d8f833071832017b17d0064921d806014921f0069518ff079588d1007b18e0064921e8060133082832019517d00628a52f95170010827720f995180010828828f93309a82f0128492b33073309a82f01283f2b3309a82f0128372b330774285b2b957809951a00107ba7d0f8551807bb008d8706781740033307fb1f01330a883001330b08310133083395194003280b419517ff079577b100951800107b87d0f882171801951800107b87d8f833071832017b1740034921480301492160039518ff079588d1007b185003492158030133082832019517400328fa2e9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328af2e9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328642e9517ff079577b100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328192e3307782f0128b335951180fc7b1078037b1570037b166803648564765020ec044a5f9766209866209755209855207b1618017b152001951718015020ee04b453837751070628bb1e951718015020f004a253837751070628a91e64859517f001951818015020f2041d528218f00114070000000000000080ab780628851e7b1808821720018218f8017b1830330820ae8706285f1e821700027b17c8007a15d4009517d4007b171801330724077b1720013307e824017b17f0014921f8010249211002951718017b17000249210802019517d8009518f0015020f40490298216d8008215e000821be8003308490501330702330904645a0a640151060b64575020f6042071140700000000000000807b1790008217307b17f0008218c8007b18f800492100014921080108492110014921c0000851087b091407b9e5e41c6d4758bf7b17401407eb113113bb49d0947b1738821890009587187b1788003307c4017b172833072825017b172033074905017b171833073825017b171407f9ffffffffffff7f7b175049114808104911580006209588127b18102841821708019658d800c88707821890009588168219f802821a0003821b08037b787b79087b7a107b7b189555017b15100128077b151001018217f8005107d9089517f0019518f0005020f804cb42ff8217f00182188800ab870628d21c951718019518f0013309d8005020fa04e8728217180182199000c879089588ff888817d3790993895a190966511908340352190995047b19800082163001330718d6760751070628a11c976703976505c97505821750ac7506288f1c821728017b1778821720017b17705105330664585020fc04c46b646982184852070628721c7b19607b17685206260628b406951c100251190a6e045219152f047c19280182172001492148034921500301492158037b19a00051097402140854f029fde5e6dd783302c8780898891ed39808821340ca380898891bd3980814093f74df7d2c6da6da821538ca5808988a1fd38a0bc8790998981ed39808ca380898891bd3980814092af894fe72f36e3cca5808988a1fd38a0ac8790998981ed39808ca380898891bd398081409157c4a7fb979379eca580898861fd38606c8970798781ed38707ca370798781bd38707ca570798751fd375054921a800019517f001283d9727058218a800c88707951828033309205020fe0483718212c8009522017b125803951c10028217a000821bb800821ab000aa7297019517f00101c85b089e8829c85808976911d35a0ad36b0bd3a606d3b505d39a0a98882078789577019ebb13abc7da7b1ab0007b1bb8007c1709027c1808027c190a027c1a0b02977708d4870797991097aa18d4a909d497077c180d027c190c027c1a0e027c1b0f02978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1740037c1701027c1800027c1902027c1a0302977708d4870797991097aa18d4a909d497077c1805027c1904027c1a06027c1b0702978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1738037c17f9017c18f8017c19fa017c1afb01977708d4870797991097aa18d4a909d497077c18fd017c19fc017c1afe017c1bff01978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1730037c17f1017c18f0017c19f2017c1af301977708d4870797991097aa18d4a909d497077c18f5017c19f4017c1af6017c1bf701978808d4980897aa1097bb18d4ba0a82194803d4a808978820d487077b1728037b12c800ab9254fe9517480350200005b3498212c800821750037b17a800283afe82175803821850038219480382151001821a00017b1708037b1800037b19f802aba55bfc3308a0250195170001502002053074ff2847fc7c1744015107620180152001951c24017cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1740037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1738037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1730037cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1728039518280364573309330a330b0a110164765516f7150359060628ce1851061b036468502004058b675207062843187b17c8003308646950200605817028010382171001821800019676d800957501ab87133308b825019517000150200805a572ff82170801c86707951818013309d80050200a05fd6d28d9fa7b198000821720017b17a00082183001821728017b17b0007b18a80051080a018218a800978703978805c978088216b000c868087b18b800280d9566188217b800aa76e5008267088268107b1610030a13015517f70c33087b172003281b9578097b174803531807072804178d8706781719033308010178181803951710037b1728033307f6007b173003951718037b1738033307fa017b17400333078025017b17f0014921f8010249211002951728037b170002492108020233082850200c05776652070628881649214803287b175003492158033308802b01951748039519f00150200e05112e510706286816821748037b17c80082155003821b58033308490501330702330904645a330010050a64018217c800510723ff6457502012050b6a2817ff8215100182170001ab7513330868250195170001502014054c71ff821708019658d800c88707821890009588147b788216a8007b76089555017b1510015106258215b000955508280b9566ff9555185106138257f85107f4825750201605a86928e98217a000510708018217b00050201805946928fa003307088218484911607b176851069600976604821978c896067b1698003306957a08649b017b1ac8007b16b00082b77b17b8007b1ba80082b7087b17a000648550201a055765520706284b1564763308645950201c054f6e330a06646733086459821bb800330c33001e050a12018219c8005317f7072825158217a000e657077b95f87b967b97088216b000956601821ba80095bb10959a18645882179800ab7b8682177051070c82177850202005e0688215100182170001ab7513330850250195170001502022052470ff821708019658d800c88707821890009588137b788218607b78088218687b78107b76189555017b15100101821880005618151201821758d08707847701520735f82800015116ff07289215821790007b17480328274921c80001019518280364578215c8006459330a646b0a117b1648037b1550037b16580301951748037b1718038217287b1720038217207b17f0014921f8010149211002951818037b18000249210802019517c8029518f001502024055a208215d002821bd8028216c802330702330904821818645a0a640151060b645750202605ec6782175803821850038219480382151001821a00017b17f0027b18e8027b19e002aba510951700018218502028051b6fff821708019658d8008219e002821ae802c887078218f0027b79087b7a108219107b797b78189555017b1510019517180133002a05282568ff9517180150202a051d68ff2826f7951700017b1748033307b4017b17500333070825017b17f0014921f8010249211002951748037b1700024921080201951728039518f00150202c05821f8216280382153003821b38033308490501330703330904645a0a640151060b645750202e0512678215100159050628ff13821708017b17b8009656d800956604646850203005fd6252070628b5137b16f0017b17f8019857204921000251070628d5139518f001645750203205bc998216b800520506284c129657d800c867077b17a00033079001017b17a800330a07140700000000000000807b17980028649517f001330908502034050c4482180002018217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178759588087b180002018216b8009566d8008217a000330a07ab760628c311826782199000c879089588ff888817d3970793879777028218a800c887078177c887077b16b8003207821800028217f001aa87140f8217f801c8870746078219f001958501c959077b15000254171f0d0f7b19c800018217f8019568187b17c000c857073309205020380596688266389558208217c800c987077b180002541707f90e8217c00001c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178768217f0018216b800826540958808c987077b180002541707c30e8217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f001826548958808c987077b1800025417078e0e8217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f001826550958808c987077b180002541707590e8217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f001958508c957077b15000254177f270e8217f801956858c857073309800050203a054667955780007b1700029518f001646750203c05f67cff2819fe821800028217f001aa87dd0b8217f801c8870733091078799588017b18000228f7fd821800028217f001aa87cf0b8217f801c8870746070e8217f001958501c957077b15000256171f42069517f001330920645850204205884182150002282a06821800028217f001aa87a40b8217f801c8870746070b8217f001816520958801c987077b180002541703990b8217f801c8870798591878790398591078790298590878790178758217f001816524958804c987077b1800025417037c0b8217f801c8870798591878790398591078790298590878790178758217f001816528958804c987077b1800025417035f0b8217f801c8870798591878790398591078790298590878790178758265189588049857207b1800025107062890108266109518f0016457502046052896330a075105e7fc646b82180002975504c8560c7b1cc80028438217f801c88707985938787907985930787906985928787905985920787904985918787903985910787902985908787901787595880895bb107b180002aacb94fc8217f00181b682b508c987075417034f8217f801c8870798691878790398691078790298690878790178768217f001958804c987077b180002ac7a869517f00133090864b6502048051140821cc800646b330a07821800022867ff9517f0013309047b1bc00050204a05f03f821cc800821bc000330a07821800022895821800028217f001aa87520a8217f801c88707330904282904821800028217f001aa874d0a8217f801c8870746070c8217f0017e6520958801c987077b180002541701420a8217f801c8870798590878790178758266189585027b1500025106b5fb8217b800827810976605282b8217f8019589207b19c800c8570733092050205005a6649555209566e07b1500028218c800510680fb8217f001c9570756171fd09517f0013309207b18c8006458502052052a3f8218c8008215000228b1821800028217f001aa87cf098217f801c887074607098265189588019857207b1800025207de0e8266109518f0016457502056055c94330a0751051bfb646b82180002975504c8560c7b1cc80028438217f801c88707985938787907985930787906985928787905985920787904985918787903985910787902985908787901787595880895bb107b180002aacbc8fa8217f00182b682b508c98707ae7a678217f801c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178768217f001958808c987077b180002ac7a6fff9517f00133090864b6502058052d3e821cc800646b330a0782180002284fff9517f0013309087b1bc00050205a050c3e821cc800821bc000330a0782180002287cff821800028217f001aa87bd088217f801c887074607028217f00181659800958801c987077b180002541703b4088217f801c8870798591878790398591078790298590878790178758217f00182658800958804c987077b180002ae7a99088217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f00182659000958808c987077b180002ae7a67088217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f001958508c957077b15000254177f36088217f801956808c857073309800050205e054562955780007b1700022825f9821800028217f001aa8721088217f801c8870733090f78799588017b1800022803f9821800028217f001aa8713088217f801c887074607039588018217f0017b180002816508c9870754170308088217f801c8870798591878790398591078790298590878790178759588047b18000228b2f8821800028217f001aa87ea078217f801c88707787a288803821800028217f001aa8786088217f801c887074607018219f001958501c959077b15000254171f7e087b19c800018217f8019568087b17c000c85707330920502068056a618266289558208217c800c987077b1800025417076a088217c00001c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178768217f0018219b800829530958808c987077b180002561707b8f728a1f7821800028217f001aa8724088217f801c887073309110178798217f001816528958801c987077b18000254170346058217f801c8870798591878790398591078790298590878790178758217f001958504c957077b15000254171fc8f98217f801956808c8570733092050206c0593609557207b1700022874f7821800028217f001aa87be078217f801c887074607149588018217f0017b180002826508c98707ac7a0ff728f9f6821800028217f001aa87a7078217f801c8870733090528e003821800028217f001aa87a2078217f801c887074607159588017b1800028217f00164698266087c9510c98707ae7a95078217f801c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178768217f0019588087b180002aa8767078217f801c8870778759588017b18000228a8f6821800028217f001aa875c078217f801c8870733090d78798267189586017b160002510783f68218b8008288109588c0007b18c8009675500128398215c800955840ff8217c000c867073309800050207605695f956680007b1600028217b0009577b0fe955550017b15c8006475510737f67b15b0008217f0016475c9670754171f9e008218c8008217f8017b18c8009588c07b17c000c86707330920502078051a5f956620c965077b16000254171f8a008218c8009588e08217c000c8670733092050207a05f45e956620c965077b16000254278f0086008217c000c86707330990008218c80050207c05cf5e95669000c965077b16000256177f42ff9517f00133098000646850207e056339821600028217f8017b17c0002821ff9517f0013309206468502080054539821600028215f0012850ff9517f0013309206468502082052b39821600028217f8017b17c0008215f001285cff9517f001330990006468502084050839821600028217f8017b17c0008215f0012860ff821800028217f001aa87f4058217f801c88707460706018219f001958501c959077b15000254171f9d027b19c800018217f8019568087b17c000c8570733092050208805fb5d8266289558208217c800c987077b18000254170789028217c00001c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178769588087b1800022892f4821800028217f001aa876e058219f801956708c889094609129588017b1800029518f00150208c054173ff2864f4821800028217f001aa8754058217f801c8870733090a28fe00821800028217f001aa874f058217f801c887074607089588018217f0017b180002816508c9870754170344058217f801c8870798591878790398591078790298590878790178758219f001958604c969077b16000254171f2a057b19c800018217f8017b17c0008215b80095580cc8670733092050209205e65c7c592c8215c0009567207b1700028218c800aa781105c8750578599577017b17000228acf3821800028217f001aa8712058217f801c887074607169588017b18000282670882199800ab97778217f001aa872b058217f801c887073309017879c898087b1800022867f3821800028217f001aa87e1048217f801c887073309130178799588017b1800028267108268189519f00150209805415eff2833f3821800028217f001aa87c1048217f801c8870733091778799588017b1800022811f38217f001aa87cb048217f801c88707460764698266189588019867207b1800025207ef068295109518f001646750209c05198c5106dbf2645882150002976605282b8217f8019589207b19c800c8570733092050209e05cd5b9555209566e07b1500028218c8005106a7f28217f001c9570756171fd09517f0013309207b18c80064585020a00551368218c8008215000228b19517f0013309045020a2053a368218000228aefa9517f00133092064585020a4052436821500028217f0017b17c8002852fd9517f0013309085020a6050836821800028217f801286cfd9517f0013309015020a805f035821800022816f49517f0013309015020aa05dc35821800022824f49517f0013309015020ac05c83582180002284ff49517f0013309045020ae05b43582180002285bf49517f0013309045020b005a035821800022878f49517f0013309045020b2058c35821800022895f49517f0013309015020b40578358218000228a1f59517f0013309015020b60564358218000228a6f59517f0013309025020b80550358218000228b2f59517f0013309015020ba053c35821800022824f69517f0013309015020bc052835330a07821800022833f79517f0013309045020be051135330a0782180002283df79517f0013309085020c005fa34330a07821800022857f79517f0013309085020c205e33482180002288cf79517f0013309800064585020c405cc348215000228bbf79517f0013309015020c605b8348218000228d2f79517f0013309015020c805a4348218000228e0f79517f0013309045020ca0590348218000228ecf79517f0013309015020cc057c34330a07821800022806f89517f0013309015020ce0565348218000228dff09517f00133092064585020d0054f34821500028217f0017b17c80028e2f09517f0013309085020d2053334821800028217f80128fcf09517f0013309085020d4051b34821800022831f19517f0013309085020d6050734821800022866f19517f0013309085020d805f33382180002289bf19517f0013309800064585020da05dc338215000228caf19517f0013309015020dc05c83382180002286df79517f00133092064585020de05b233821500028217f0017b17c8002871f79517f0013309085020e0059633821800028217f801288bf79517f0013309015020e2057e338218000228cff79517f0013309015020e4056a33330a07821800022832f89517f0013309015020e605533382180002284cf89517f0013309015020e8053f33330a0782180002284ef89517f0013309085020ea05283382180002285ef89517f0013309015020ec05143382180002288cf89517f0013309015020ee050033821800022897f89517f0013309015020f005ec328218000228fff99517f0013309015020f205d832821800022885fa9517f0013309015020f405c43282180002289ffa9517f0013309015020f605b0328218000228a4fa9517f0013309045020f8059c328218000228b0fa9517f00133092064685020fa058632821600028217f0017b17c80028c5fa9517f00164953309015020fc0568326459821700028215f80128dafa9517f0013309015020fe054e328218000228e1fa9517f001330901502000063a32821800022812fb9517f001330901502002062632821800022832fb9517f0013309015020040612328218000228c8fa9517f00133090150200606fe31821800022828fb8217f8017b17c000821700027b17b80082161001821708017b17c8005106248215c80064573300080628eb54ff645750200806e554ff9566ff9555d8005206f08217000151070d8217c80050200a06265482170851070c82173050200c0617548217c0008218b8008210780382157003821668039511800332003307081028d00b3308ffab876d646750200e06e8533307e8250128761733072828b40b3307120e01330a102c01330b302c0133085695196703287b21951748037b172803330720077b17300333071832017b17f0014921f8010149211002951828037b180002492108020133082832019517f001287a0f33074032017b17f0014921f801014921100249210002084921080233085032019517f00128530f3307960401330ab02301330bd0250133082b95196703280021330760230128f90a645728130b6467280e0b82170851070c8217305020100625533307960401330ab02301330bd0230133082b9519670328c6203307d82f0128bf0a3307170501330ab02301330be823013308169519670328a5209567097b1618035517075e8d78067818f0013307981f01330a883001330b8031013308449519f001287a20330768310128730a3307170501330ab02301330be82301330816951967032859203307170501330ab02301330be8230133081695196703284020951718037b174803330720077b17500333071832017b17f0014921f8010149211002951848037b180002492108020133082832019517f001283f0e3307170501330ab02301330be823013308169519670328ec1f9511c07b10387b15307b16286485647650201206633f9766209866209755209855207b167b1508641750201406d2338377520791006485641750201606c2338377520781006486641750201806b233837752077183588369641733001a06951110fe7b10e8017b15e0017b16d801951188f87b1710017a18b8017a19bc019517d006330026032818397c1751072433082033052050201c06a44d51073f951801330920647650201e06535464672805330501645882103882153082162895114032003307960401330ab02301330bd0230133082b641928131f330720282d09951128fd7b10d0027b15c8027b16c0026485647650202006803e9766209866209755209855207b16b8017b15c0019517b80150202206ea32837752079d089517b80150202406da32837752078d089517b80150202606ca32837752077d084921480210951760029518480250202806772f82186002140700000000000000807b1808aa786e0882186802821770027b187b18a0017b17a8019517a00150202a068032837752074c078387492180023309087b1988024921900233055107d206978820988b20330c98001407f8ffffffffffff7f7b17103302044911300828178407015207fd06821b18c93b0b330c9800510b23038214800233076be67b03c954077b1b187b1328ae3772c8350aac5a0c07d6ca08330752080d0796a99800821610ac96020764a6510417964798008218307b18b8017b17c80133070828053307017b17c00195171801330808951ab80150202c069955ff821718015207ae06821720017b17307b1788027b168002330c98003302048213280196579800957694009a377b17203304013300012841018217e800977708821878d487078218f8009788108219f000979918d49808d487077b17f8008217c8009777088218e000d487078218d8009788108219d000979918d49808d487077b17f0009765088217a800d457078218980097881082169000976618d46808d487078218a0009788088219c000d498088219b800979910821ab00097aa18d4a909d49808978820d487077b17e800821738977708821858d48707821848978810821940979918d49808d48707821850978808821970d49808821968979910821a6097aa18d4a909d49808978820d487077b17e00082153082160001c8650595576cff9518b8013309800050202e0699518217e8007b57ec8217e0007b57f48217f8007a57fc8217f0007a57821588009555017b15900282132882148000d83400954401821820c8480895669800330204330901aa981dfe7b1600018219a801ac290afe821aa0017ca77ca8017b18e8007ca8027b18f8007ca8037b18f0009598fc95ab047b1ba0017b18a801ac28ddfd7ca8047b18e0007ca8057b18c8007ca8067b18d8007ca8077b18d0009598f895ab087b1ba0017b18a801531808affd7ca8087b18a8007ca6097ca80a7b1898007ca80b7b1890007ca80c7b18c0007ca80d7b18a0007ca80e7b18b8007ca80f7b18b000959bf095a8107b18a0017b1ba801330c8000accb64fd959b70ff95ac90007b1ca0017b1ba801330c08accb4dfd7b17787ca790007b17587ca791007b17387ca792007b17487ca793007b17407ca794007b17707ca795007b17507ca796007b17687ca797007b1760959768ff95a998007b19a0017b17a8019517b801330980007b148000502030062c50821780027b158800ab7594fd95178002502032063632821788027b1730287ffd8218800214070000000000000080aa78cf037b18b000821788027b17b80082170851070b821750203406144d51052703965798008216b800c867077b17c0003307870c017b17f800140700000000000000807b17d0003307b6067b17f000330720077b17c8007b15000128168216e800956698008217c00082150001aa76d70295171801330980006468502036067c4f81679000826880007a170c017b18100133080e50203806a3485107e4027b16e80033090e64758218f80050203a064c4f9517b80133090e645850203c06eb32645750203e06694c8218b8018217d000aa783a8215c8018217c001531504227c79017c7a7c7b027c7c03979908d4a90997bb1097cc18d4cb0bd49b0601510809502040062b4c561503073306017a169c019566017a16b0028216000133080e50204206184851075902647533090e8218f80050204406c54e33080e9519b002330a0464570a03013308f7ac870a3308ffab87b902645750204606d54b3308800050204806d74751071e026475951818013309800050204a06834e9517b80133098000645850204c0636138216b8018217c0017b17e00033080350204e069e475107ec016458936880004617023f4617013f46073f51061c3305037b1560027b1768027b157002648750205006634b282494658000821ae000936a80007b1860027b1568027b1a700250205206434b3305030195170c017b17b8018217f0007b17c001951710017b17c8018217c8007b17d001951760027b17d8013307b2017b17e0013307d82a017b1780027b1588024921a0029517b8017b1790027b1598029517a00195188002502054062d038217600251070d8217680250205606d34a8216a0018215a801821bb0013308490501330702330904645a0a640151060b645750205806ac4a95179c017b1780028217f0007b1788023307082b017b17b8014921c001014921d801951880027b18c8014921d00101951748029518b80150205a06b802821748027b17d8008215500281170c0182181001821958027b19e0007a17c0017b18b80195181801330980009517c40150205c06054d951760029518b80150205e06388182166802821a700264578218e00064690a03019578013309f8ae98728217600251070b646750206006004a8217d800510728fd645750206206f049281cfd8217b00051070d8217b80050206406dc49330733088210d0028215c8028216c0029511d80232004921b0004921b8000882170852079efc28a2fc33070e28870133078000288001330703287a019578097b177802551807a7008d8706781780023307ef0501330a782301330b182b0133080895198002282e178217800251070d8217880250206606634933075a1f01330a083001330b2830013308239519b00228041782172001821828013309a82f0128ea0033073309a82f0128e0003309a82f0128d8009578097b1760025518076a3308fdbf78066457502068060f497816b8013307ef0501330a782301330b302b013308089519b80128ac16951778027b17b0028217c8007b17b80233071832017b17800249218802014921a0029518b0027b189002492198020133082832019517800228ab04951760027b1780028217c8007b17880233071832017b17b8014921c001014921d801951880027b18c8014921d0010133082832019517b8012870043307960401330ab02301330bd0230133082b9519b002281d1633077d1f01330970300133081b28931652070764972806648728219511d064783307b02b017b1749110801491120491110084911186417281d049511b87b1764177b173833078e067b17403307c02b017b1708491110024911289517387b1718491120019517089511b88278288279207b18287b1920827818827910827a0882777b18187b19107b1a087b1764177b17303307e02b017b17384711409517303300da0628fa319511a87b10507b15487b1640648a828608828b7b171851065a330595b7086468018279c895059588ff9577105208f582a91851091957054582b708887701885810d28707520736975501015705dc005105327b197b1b087b1a10645850206a0667435107cd00821a10821b088219281782a9183305330701280d33053307012806330701017b15207b17284911305116011352061252090f33063305330801283b51092e3308802b0195172064a950206c06d50a5107383307120e01330a102c01330b302c0133085695193f28a81482b60882b8ac653a330501c85707646950206e06a449c865057b153001821730821828821920821a187ba7107ba8087ba98210508215488216409511583200951720648533086469502070062024645882153082172828b53307f82b012849fe64572863fe828928828720829c183308ce1e01330905320c827851080982770828684632009511e07b10187b15107b16086475827a108277c9a7076496ac972b7b1a825708c8a7076469502078060a498217c876067b56103307821018821510821608951120320064577b1864a8646950207a0698238218825a1028c59511d87b10207b15187b1610330980006475ae98268256108257ab761064577b1850207e066901821801825708c8670778789566012899008b870b48110c52071595170d9889068699c00078190c33090228528b871052072295170e98890c8699e00078190c97893498993a9599800078190d330903282d95170f9889128699f00078190c97892e98993a9599800078190d97893498993a9599800078190e33090401825610825a84883f86888000c96a0a7878ac9a2d7b19825708c8670795180c5020800615488217c87606017b56103307821020821518821610951128320064576468649650208206a422646982561028c59511e07b10187b15107b16086489828a08828818330b01aaba13520a13520810827a103306330501282b5108173308802b0182101882151082160895112028a5088298827a1082798286088285c9a909ac69317b17827808c8a80764586469646564a6502086068047c8560682177b7610330782101882151082160895112032007b17821764a86469502088060e228217827a1028bf9511b87b10407b15387b16306475827795780151084d977901e48908330908e4980657063e5107108258087b18187b1728330701017b17206417951918646850208a06392282175207238217087b57087b56821040821538821630951148320033073309482c0128cdfb8217088218103309482c0128bffb9511e87b177b180847111001641750208c063b2e009511a07b177b180864177b174033078e067b17489518087b18507b17583307482d017b1710491118024911309518407b182049112802951710649828b09511a07b177b18089517087b174033078e067b174864187b18507b17583307902c017b1710491118024911309518407b18204911280295171064982873ff8277019511b87b10407b15387b16307b18987904330814330ada0f01532971028000330814951b2e14094b598638d6c56d347b1910491108102733037b143300ffe0f505016472821710d6720798770b821908ca9709bf9206976930989932ca39099894119899108499fe07964c64bfc606976631986630c8a9097c9c7c9901c8a6067c65017c6678b9ff78bcfe78b50178b69588fc95bbfcac20ac5417633697793098993296997b14989911969b64bfb7079777319877309588fec8a7077c7b017c77951c1cc88c0c78cb0178c764970155170a149588ff95191cc889098677307897281c9777019588fec8a7077c79017c77951a1cc88a0a78a90178a701951a1cc88a0a9a8b1433080182173309502090061382104082153882163095114832009511987b10607b15587b16506473807034649c7b184884080133022b3309000011840704c8b80552070849114828376457510c23330664c4821948c8c905649c017dc98999c085990195cc01c89606ab5cf2280764c4330601c87605644c3309000011018237db82097b1b307b1a2851072e823608ae65288407085207577c3838330201c956077b1c207b1918ad829b005208a9007b1710648728a40082352082362864576468821a4864cb502092066e013308015207b700826c186457821828821930821060821558821650951168320c7b16388137307b17107c38387b1808823720823828481330307b13461338017b17406486821a4864cb502094061f01520767821738c95705955501019555ff5105c900826920330830821740b4900296065107eb2842521802129578019877019888017b18102806491110018238207b18408235288138307b1838957601019566ff510628825920821740821838b4900298065107ec330801016487821060821558821650951168320082164064676458821918821a48821b2050209a068e003308015207d7825a186467821828821930b4a0029c063308015207c18217109a777b17483306ff01821748c867075117ff45825920821740821838b490029e069566015107e52839826a18821740821828821930b4a002a00633080152077eff330882168217107a6730821708786738286aff821610821710d87608285eff821710d876082855ff9511d87b10207b15187b161064a6647c5139000011286485828a2064c764987b1b087b1cb4a002a206821c6458821b08647933070152092051061a828a1864c7646864b9821020821518821610951128320a33070182102082151882161095112832009511a87b10507b15487b1640647c827a827b106486520a0a84b80151081e0384bb01510b910064c082cb186494c869093308510b46330c3303e0003302f0006467646528149556010195cc01c98505c956086465aacb27aa95557d565906e98466ff00ac360bac260d95560428dd95560228d895560328d3646701aa96317d6b64496476640c590b0c84b7ff00330be0000151089d02ae989502c886077d77330bc0afb78d02520a1128900264496476640c01510a860282c3087b19187b161055192027330251091ac89608646a017da78977c085770195aa01c87202ab8af2ac32aa01285502646a9565078455f8c89606c956068468073309aaa5147dab89bbc085bb0195aa01c8b909ab5af27b1c087b13330c510821846bf8c8b50ac85b0bc8b808017dab89bbc085bb0195aa01c8bc0cab8af2986303140a0101010101010101330bff00ff001408ff00ff00ff00ff007b182814000000000001000100140801000100010001007b1820c89c022830821530c89505821638c96303846c03821b28d2b407984808d2b808c88707821820ca8707987730c87202520c97005103820064593307c000e673077b17389778037b18303304531304ba8485e007c8950564980182808507ff987707980b06828c08d4b707d2a707c8470785cbff98bb0782861098cc06d4cb0bd2ab0b856cff98cc07986606d46c0c828618d2ac0cc8cb0bc8b707856bff98bb07986606d46b0bd2ab0b958820c87b04ab58aa2853ff821918821610821c088213ac325d28090133088467fc00977703c8790397cc03821918821610018237953308857bff98bb07987706d4b707d2a70795ccf8c87808520ce6821a28d2a807988808d2a808c88707821820ca8707987730c87202821c088213ae32b3007cca38c923085a1a010c510a1a491120281a521a02129587019888019877017b172028087b182033080182c7207b173882c52881c7307b1730958601019566ff51061a825920821738821830b49002a4065107ec330701288300825a18821738821810821918b4a002a606647833070152086a8217209a777b17283306ff01821728c867073308ffaa8740825920821738821830b49002a8069566015107e32834ab987bfddb6809db6606520a82fd82c82882c720828c186468821050821548821640951158320c821620821720d876072809821720d876070182105082154882164095115832009511c064783307e20e017b17304911382b9517307b17491108014911204911100849111864172838f88279088277829c18320c9511807b10787b15707b166849115820330a03781a608296204911284911387b17487b18505106f70082972851075b0182957b19088298107b18109578ff9788039888039588017b18955908977803977706c98705956618017b1920829951091b821a508217488218208288f882aa18b4a002ac06520738017b15188267107b17587c69188268f8826778196051082152180112977704821810c8870782785108073308280e8277083308012806330801018269e87b18287b1730511902198268f05219011e978704821810c8870782785108073309280e827808330901280633090101826708977704821a10c87a0a82a782aa087b19387b1840951828b4a002ae068215185207a7008219209599109555c8956638520543ff2860829718510768829510977804c858087b18207b190882969577ff9777049877049577017b17956608018269510917821a508217488268f882aa18b4a002b0065207528257825908951828b49002b206520742955510956610821720ab75ce821908821a829708ac7a0d282d330a829708540726829797a804c88709821a50821748829882990882aa18b4a002b40651070833070128053307018210788215708216689511800032009511c07b10387b15307b162880707b18980b0433080a3309da0f01532b7102da0033080a951b261402000000005917b7d17b1210491108102733047b143303ffe0f50533066401970c20821710d67c0c98cc2d821708ca7c05bf500a97a530985532ca45059857119855108455fe07ca6707bf7a0a97aa3198aa30c895057c57017c55c89a0a7ca20178b7ff78b5fe7ca778b201830a9588fc78b795bbfc64c0aca3a7541c635897c730987732967a7b1498aa1196a764bf7c0c97cc3198cc309588fec89c0c7cc7017ccb951c1ec88c0c78c70178cb531a0a2b97aa019588fec8a9097c97017c99951a1ec88a0a78a70178a9281d640c561063b083ca551a0add9588ff95171ec8870786a930787901951a1ec88a0a9a8b0a330801821733095020b806faf68210388215308216289511403200951178ff7b108000808934849a10520a1484992052094a821080009511880028a7f5330a810095197f0164ab987c0484770f88720a867a30957757db2a0778979599ff95baff64c7520ce295b7fe56278000659aab8100641ac87a0a283d330a810095197f0164ab987c0484770f88720a867a30957737db2a0778979599ff95baff64c7520ce295b7fe56278000359aab8100641ac87a0a01330902648733082523015020ba0643f6821080009511880032003309002d0133088000288cf43309002d01330880002880f49511d87b10207b15187b16107b1751098501330a330b0fe4b90b95bbf1958c0784ccf8c98c079584089a93330cee11017b1c08280995aa01ae9a5b01c8a8057d5557054ebfa7058455075205ebaeba26c8a8058255c8a4068266140c8080808080808080d46505d25c0c520c0995aa10acbae0ae9aa2009aa5c88a0a017dac570c93009555ff95aa01ab53f42807018455ff00821c08c85c0c7cc6330c04aac650330c03aac626330201330c02abc6f60095a001ae90eb00c8080c7dcc3305013306bfafc6c90028ea0095a601ae96d200330201c886067c665125e000426c605225ed0052330c9faf0c6528b80095a601ae96ae00c886067c663302015125f0002b6c665225f4005a330c8faf6c602894009a5a01ac9a27ff287a846ce0003305a000aa5c2a287d956c70ff531c30402873955c1fff33060cac6c0e845cfe003305ee00ab5c5f330cc0afc05995a002ae904fc8080c7dcc581cbf34284c955c0fff561c0241330cc0afc63a95a502ae9530c885057d5c3305bfadc52e95a003ae901fc8080c7dcc5a1cbf25950a01ac9aadfe330a82177b78087b7910282333022810330501280b33050228063305030182177b7a08787210787511330a01017b7a8210208215188216109511283200951168ff7b1090007b1588007b168000647c7c77087b1b107b1a08330a0133050152077b016492648382c68067347cc9098478047b1c1852088d00826828330a4f0f01826720828b183308520f01da9a0885990364257b13b4b002bc0682186459821c18330501330a0152073201826a2882672082aa18b4a002be06330a01821c1833050152071701826828826720828a1833084d0f01330902b4a002c006330a01821c183305015207f4008217086468821910b49002c206330a01821c18647528db00520932826828826720828a183308540f013309037b126435b4a002c40664538212330501821c18330a015207ae008167340182682082692833050178153f7b18207b192895183f7b18308168307c6938826a826b08826c108266187a17747a18707819787b1a407b1b487b1c507b16589517207b17603307d02c017b1768951720643864295020c6066e52074733084d0f019517203309025020c8065b520734951840821708821910b49002ca06520723821868821760828a183308570f01330902b4a002cc066475821c18330a012809821c18330a010178c508461c090164c78210900082158800821680009511980032009511807b10787b15707b166833063304491148330b0a0a0a0a14000a0a0a0a0a0a0a0a140a00010101010101017b1a60827a107b1a40827a7b1a388277087b17307b18509588ff7b1833077105017b17089a977b171033050a7b19287b1020283e8217c867077c779577f6887701018218407887821730827a18c94609821850c84808821738b4a002d006645482192882165882102033050a5207610182174884770152075301ae690a280301ac690001c96902821350c8630333070fac2723aa69ea003308821c10c86c0c643a017cabaa5b3a9588ff95aa01ab8cf528cd00953b0784bbf8c93b08510831330a01c8a3077c77aa571a95aa01aba8f57b14487b1658952af0ae8a2228599a8a01c8a608958601ae98992881007b14487b1658952af095bb08280695bb080182bcf882b6d30c04d30606821560c96507d4670714068080808080808080c94505d45c0cd2c707d26707ab670c95881095bb10ae8acdaa288d00330ac88307c9890b821658c96b0b82144833050a01c8a70c7cccaa5c0b95aa01ababf52821c88a0ac8a608958601ae9819ffc8a3077c77ab5710ff330764657b165828126496017b165833070164456496aa943f7b17488217407c77510724821730827a183309048217388218087b15186445b4a002d2066454821518520717ab467bfe33072883fe649682144828b933072806330701018210788215708216689511800032009511d87b10207b15187b16108276107c69827b827508510938825c18330a71050133090464b77b180833087105017b1bb4c002d606821b82180851071433070182102082151882161095112832009587f68877017867825c2064b7821020821518821610951128320c6489828a08828818330b01aaba11520a1152080e33093308012871fd51080b3308d02c01281cf682998298829908285cfd9511907b177b18087b19107b1a1864177b17503307b8077b17589517107b17603307aa067b17683307b02c017b1720491128024911409518507b18304911380295172064b828f5ed9511a07b177b180864177b174033078e067b17489518087b18507b17583307882d017b1710491118024911309518407b182049112802951710649828b7ed9511b07b177b180864177b17403307b8077b174833070009017b1710491118014911309518407b18204911280195171064982882ed00951168ff7b1090007b1588007b1680007c7a10827c330501520a1701648b827608806834848a047b17187b1c10520a4f7b1b7b190888c201330b520f0182692833085c0f01826720829318dbcb08330b02c92b09b43002dc06821c1064788217185208ce0082176468821908b49002de06821c10647582171828b600520c337b1b7b1908826828826720828a1833085d0f01330902b4a002e0066478821718821c1052088d00816834821908821b01826720826a2833050178153f7b17207b1a2895173f7b17308167307c6a388263826c088262108266187a18747a1770781a787b13407b1c487b12507b16589517207b17603307d02c017b176895184064b7b49002e206520723821868821760828a183308570f01330902b4a002e4066475821718821c102809821718821c100178751095c8017b78821090008215880082168000951198003200951170ff7b1088007b1580007b16787c7a08330b01330501520a1301648c82768068347c7a09848b047b1710520b49510a32826828826720828b183308520f017b190833090264c5b4b002e606645c330b0182190864788217103305015208ce0064c76468b49002e806330b01647582171028b900520a36826828826720828a183308600f017b19083309013305017b1cb4a002ea06821c8219086478821710330b015208880081683401826720826a283305017815377b17187b1a209517377b17288167307c6a38826b8263088262108266187a186c7a1768781a707b1b387b13407b12487b16509517187b17583307d02c017b176095183864c7b49002ec06520723821860821758828a183308570f01330902b4a002ee066475821710330b012809821710330b0101787508461709018210880082158000821678951190003200951160ff7b1098007b1590007b16880064758217e8007b17708217f0007b17688257288216f8007b1660821600017b16588216c8007b16508273188217d0007b17488217d8007b17408217e0007b17388217a8007b17308217b0007b17288217b8007b17208217c0007b17188217a0007b17108257207b1c087b1b64a6b43002f0067b1578781780004621810095177864688219821a08821b105020f206c0f7951778821830821928821a20821b185020f406abf7951778821850821948821a40821b385020f60696f7951778821870821968821a60821b585020f80681f77c1981007c188000d4890751094284880152083c8217787c783484880452081a827828827720828a1833085a0f013309023300fa06320a827828827720828a183308590f01330901b4a002fa068477018210980082159000821688009511a00032009511c87b10307b15287b16205618271d978a02330bf00101c8ba0a81aac8ba0a320a33085c3028d201330a5caba80a33085c5c28c501330a0003aca804018499015109fd0088892f1801330a11949a95ac0897cb023309541a01c89b0b81b68a8b0b8a660bd86b06da6c0a95a604976c02c89c0c81cc8acc0bd8cb0cdac60a95a602976c02c89c0c81cc8acc0bd8cb0cdac60a95a601976c02c89c0c81cc8acc0bd8cb0cdac60a95a601976c02c89c0c81cc8acc0bd8cb0cdac60a97ac02c89c0c81cc8acc0bd8bc06d3cb0b88bb01c86a0ac8ba0a330c21acacf40297ab02c89b0b80b99896153302ef02511a210d80bc0498c215510a0c80bafc84aaffff1f01856bffc82b0b510b30330cbfa80b330aef02e4a6039522ff330adc1a0101aa639f02c86a097c99be9c0caccb0c956601ab62ee64260184660152060a01551820db008b8914330a300901c8a9097c99868b0178191797892c98993cc8a9097c9c97893098993cc8a9097c9669b9989902781c18781619978b3498bb3cc8ab0b978c3898cc3cc8ac0c84880fc88a0a9598fe4611167cbb7ccc7caa471114781b1a781c1b781a1c46111d7d951a14c88a0b330c5c78bcc8a9094619ff7546097b811914811a187f1b1c28020133085c6e2828849900015109dc0133085c27281a33085c72281433085c74280e97992f5909c30133085c22017a784717084717064717043308000279780a01821030821528821620951138320053187f99018b89105209be00330629016e823303df1601330a2f1701330c511801330928330b220128c1008b8914330a300901c8a9097c99868b0178190b97892c98993cc8a9097c9c97893098993cc8a9097c9669b9989902781c0c78160d978b3498bb3cc8ab0b978c3898cc3cc8ac0c84880fc88a0a9598fe46110a7cbb7ccc7caa471108781b0e781c0f781a104611117d951a08c88a0b330c5c78bcc8a9094619ff7546097b811908811a0c7f1b10017a797a7a04797b0878780a33080a78780b282fff8b89115209633306e6016e823303d11301330a291401330cf9140133092c330bd000017b16647564276486643833000a079511d07b10287b15207b16187b1c083303821c307b1c987008979901c8980c8476ff007b1a1028ef03646864796457510904fe286f848afeff1f513a1eb802f9fd8489e0ff1f5139e0a602edfd8389c048fd5619f9e1fd83895031fd5619f1d7fd838a1014fd330bf0acabcafd83890008fd56295df6c1fd83890000fd56291dfab6fd8389b0ecfc5619faabfd838900fff1330aaf22f5ac9a9cfd5538f0010e98fd3309800078797a78042850fe3309c82e013308ef026437283ae63309b02e0133082264a7282de600951130ff7b197b1a08532801014f7d7300013302bf330c03ad32197d73ff00330c025a13bf0f7d7cfe0089ccc085cc010195c3fd00c8370c7dcc581cbfcd007b17107b1318330205330cee12017b1c207b1228ae9817285b7b17107b1818330c017b1c20491128ac984aaca847ae9aa50064177b17800033078e067b1788009518087b1890007b1798009517107b17a0003307b8077b17a8009518207b18b0007b17b8003307a82d017b17503307042840d89807db790a7b1a409517407b17800033078e067b1788009517107b1790003307b8077b1798009518207b18a0007b17a8003307382e017b1750330703017b1758491170951880007b18607b176895175064b828d3e43309643a33000c0728f1fe510912ae890fc8970c7dcc89ccc0dbc90a017b1a306489ae8a3a330903e49a029522fd95ac01ae2c0f3309682e01642764c828a1f6c92c09c87a0a3303c00151090e7dac9599ff95aaffad3cf5c8290901c8970a52090bab891364b7280becae891c7da25a12bff328167da88487ff005708177a173c3308012876aa89dc648a33000c07286afe7cac0184781f84cc3f5427df00337ca20297c306842c3fd4c30c5327f0002a7ca70397883d98882b97cc0684773fd4c707d4870751370000119f2811978706d4c707280997870cd4c707017a173c3308015327800017987a0b330802510a0c9877108e7895880301c898087b19407b18489517307b17800033078e067b17880095173c7b17900033070e077b1798009517407b17a00033071a077b17a8009517107b17b0003307b8077b17b8009518207b18c0007b17c8003307e82d017b17503307052893fe9511c87b10307b15287b1620828528828920825a2064763308277b190864977b1ab4a0021007647833070152085d816895171433090101951614502012078df97c1714522780002581181882150864578219b49002140752072d33082764578219b49002160728227c181e7c191f825a18c98909c868088215086457b4a00218075107d93307010182103082152882162095113832009511e87b10107b15087b1664758277648650201c079eed52071c826828826720828a183308de0e01330902b4a0021e075107133307018210108215088216951118320082570864688210108215088216951118285ced7c847c8901958802c89302ab0421ac32850064b4ac2b8b00c8a3030151091a7c359533019599ffab65f52855ac40146423abc8cf280c6423644b821a10abc8c3821c088214c8c404330801017dc995ca0157090d64acbf970759071e2826aa4a86007cca0195cc0284997f979908d4a909bf970757070e858801ab4cd2280533080184870182102882152082161895113032003309982e016437642828fef3642764489511a07b177b180864177b174033078e067b17489518087b18507b17583307682d017b1710491118024911309518407b1820491128023308982e0195171028abe13307802e012845e99511b87b10407b15387b1630827a7b1898ab043308143309da0f01532b71028300330814951b2e14074b598638d6c56d347b1710491108102733007b143303ffe0f5053305640164a4821710d67a0a98aa0b821708ca7a0cbfc406976c3098cc32ca0c0c98c71198cc1084ccfe07ca5707bf7606976631986630c89c0c7cc77ccc01c896067c62017c6678bcff78b7fe78b20178b69588fc95bbfcac43ac541a633697a73098773296777b14987711967b64bfba0a97aa3198aa309588fec89a0a7cab017caa951c1cc88c0c78cb0178ca647a01551a0a149588ff95171cc8870786a9307879281c97aa019588fec8a9097c97017c99951a1cc88a0a78a70178a901951a1cc88a0a9a8b14330801821733095020220741e28210408215388216309511483200951178ff7b108000817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000289a9b8100641ac87a0a3309026487330825230150202607dfe1821080009511880032003309002d01330880002828e09511d064783307f02e017b174911080149112049111008491118641728f4df9511b87b10407b15387b16306475827a95a70151077e97a8018fa9da970898893a330752096e330904e498069769063308f1140bf8ffffffffffff7fac9b55510a1482570897aa067b17187b1a2833070828053307017b17206417330808951a18502028073529ff82175207198217087b57087b5682104082153882163095114832008217088218103309002f01281fdb3309002f012817db9511b87b10407b15387b16306475827a95a70151077197a8018fa9da970898893b3307520961330904e49806976905570955510a1482570897aa057b17187b1a2833070128053307017b17206417330801951a1850202a07a928ff82175207198217087b57087b5682104082153882163095114832008217088218103309182f012893da3309182f01288bda9511e07b10187b15107b16086475827a108277c9a7076496ac97297b1a825708c8a707646950202c077c258217c876067b5610821018821510821608951120320064577b1864a8646950202e070c8218825a1028c89511b87b10407b15387b1630c88909ac895064758277977801330a08e4a909e4980657063d5107108258087b18187b1728330701017b172064179519186468502030073882175207238217087b57087b56821040821538821630951148320033073309302f0128cdd98217088218103309302f0128bfd99511d07b10287b15207b1618829a087b1710510a3282971051072c7b1782957b180850203207f31d51074964766458821950203407a424645750203607d021821508280d648550203807cf1d647601886701330801db66088219107b98087b95107b978210288215208216189511303200330682150828d97c779777033309282301c879098299330af03201828b28c8a707827a82872082bc1864a8320c9511907b10687b15607b1658828a97a902330b900201c8b9098199828608828810c8b9097b1720330b330c3209330c646b2807646b648c017b18107b16183307013306f73308330964a5330042070a12016478ae6746570807017b1808510853648650204407151d51071e017b1733086469502046070f26645a9757023308d80201c887078177c88707330b330c64693207330c821b18283f5218ffcc00140700000000000000808218207b8728a200645a9757023308200301c887078177c88707330b330c3207330c821b182849821b18821c10013305f78217647633080a12015317f7295217ffa500140800000000000000808217207b78646782106882156082165895117028632064652833821b18821c10013305013307013306f7330833090a12015317f71a5217ff8200140800000000000000808217207b7828118217208218087b787b75087b78100182106882156082165895117032003307f02f0128c7d733074032017b17284911300149114849113808491140330850320195172828dfdb646728c0d733074032017b17284911300149114849113808491140330850320195172828b9db33074032017b1728491130014911484911380849114033085032019517282898db951178ff7b1080007b15787b1670648664756487502054075901837752070f826b08978720987a20aeab20140700000000000000807b57018210800082157882167095118800320083885108d50064a77b157b1610826a330c3306491120016478017b182833070040e67805c96c077b1b30ae5722c85609ac69d0005709cc007b1a08510c168217207b17587b1c68330701280ac8560728343307017b1760951740330801951a587b1938502056078f24ff8217405207aa008217487b1720821738647c821b30821a08017b1738ac678400ac5b64821720c8670764a8645964a67b1c18502058078621821c18821b30c95b0bc8560a8217107b7a821828c958087b7b088219386496520859ff140700000000000000808215aa7c12ff280b330c330949112001017b5c8217207b57087b59102805ff8215510cf3fe82172050205a075f1e28e6fe33073309a82f012802d63309902f016467821838284eda8217488218503309a82f0128e7d59511c87b10307b15287b1620827a08330901510a1a01827b7cb895a2ff95b5017b757b720851080101330cff00abc85f330c09accaf9007cb8027cb9017cbc037cb604978808d4980897cc10976618d46c0cd4c8087cb9067cbc057cb6077cb508979908d4c909976610975518d46505d45909979920d4980895aaf795bb097b7b7b7a089887208e7928a3006c8a570a0a3306491118284c84a84033060151083984a82033060251083084a81033060351082784a80833060451081e84a80433060551081584a80233060651080c84a801330607520868491118ae6208330901284c7b170895171864587b1a1064697b1250205c070c208217c96707c865058218087b857b87088567079b77ff821818821910e07907976903cf9707c878089887208e7928089887208e7901649782103082152882162095113832003307782f012889e09511b87b10407b15387b16306475827a95a7015107830097a801e47807330804e487063308b800d68609330752096a9669b8003308f1140bf8ffffffffffff7fac9b56510a1582570896a8b8007b17187b182833070828053307017b17206417330808951a1850205e072322ff82175207198217087b57087b5682104082153882163095114832008217088218103309c02f01280dd43309c02f012805d49511c87b10307b15287b1620827a08330901510a1101827b7cb895a6ff95b5017b757b7608510863330cff00abc861330c09accaf10033097cb8027cbc017cb6037cb504978808d4c808976610975518d46505d485027cbc067cb6057cb5077cb80897cc08d46c0c975510978818d45808d4c808978820d4280895aaf795bb097b7b7b7a08289e0064892899006c8a570a0a3309491118284c84a84033090151083984a82033090251083084a81033090351082784a80833090451081e84a80433090551081584a80233090651080c84a80133090752085e491118ae960833090128427b170895171864587b1a107b16649650206007291e3309c865058218087b858217c967077b87088567079b77ff821818821a10e07a07976603cf6707c8780801649782103082152882162095113832003307782f0128b0de9511b87b10407b15387b16306475827a95a7015107830097a801e47807330804e4870633089800d68609330752096a966998003308f1140bf8ffffffffffff7fac9b56510a1582570896a898007b17187b182833070828053307017b17206417330808951a18502062074a20ff82175207198217087b57087b5682104082153882163095114832008217088218103309c02f012834d23309c02f01282cd29511887b10707b15687b16607b17103305f77b1864877b190864983309330a330b0a010164765517f724570660510632646850206407501651078200647533086469502066074a1f281b5216ff48140700000000000000808218107b87281f3305010182178218086459330a646b0a018217107b75087b767b7610018210708215688216609511783200330738310128a5d19567097b1618551707278d78067818203307981f01330a883001330b503101330844951920287ce764672897d19517187b1750330720077b175833071832017b1720491128014911409518507b18304911380133082832019517202880d59511887b10707b15687b166064753307ff7b18087b1910330a330b330c0a020164763308f7ae86415106507b1557068d006468502068075f155107890064753308646950206a07591e3307ff821808821910645a330b646c0a0282177b75087b767b761028205216ff2c140700000000000000807b57280e49053307017b57084915100182107082156882166095117832009567097b16185517072f8d78067818203307981f01330a883001330bc83101330844951920288ce63307b031012885d06467289fd09517187b1750330720077b175833071832017b1720491128014911409518507b18304911380133082832019517202888d4951158ff7b10a0007b1598007b169000647533084c50206c07851451070702647633094c330850206e077e1d3307ff64680a04015517f7f4017c67197c68187c691a7c6a1b977708d4870797991097aa18d4a909d497077c681d7c691c7c6a1e7c6b1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17787c67117c68107c69127c6a13977708d4870797991097aa18d4a909d497077c68157c69147c6a167c6b17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17707c67097c68087c690a7c6a0b977708d4870797991097aa18d4a909d497077c680d7c690c7c6a0e7c6b0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17687c67017c687c69027c6a03977708d4870797991097aa18d4a909d497077c68057c69047c6a067c6b07978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17609567207b17504911582c95175050207007e6fa7b18285207cc0095175050207207d6fa7b18205207bc0095175050207407c6fa7b18185207ac0095175050207607b6fa7b181052079c0095175050207807a6fa7b180852078c0095175050207a07b4f883777b1852077b821778821870821968821a607b17487b18407b19387b1a30646750207c07be16821748821840821938821a307b57187b58107b59087b5a8217287b57208217207b57288217187b57308217107b57388217087b574082177a57488210a00082159800821690009511a800320033074c2846ce3308ffab871b646750207e075e163307dc1f013309d8300133081f288ee49578097b1750551807228d87067817603307981f01330a883001330b98310133084495196028e0e39517507b1730330720077b173833071832017b176049116801492180009518307b187049117801330828320195176028e8d151092a827b0001330aff0001acba1e7c8cc87b0b78bc827b000195880195bb019599ff7b7b00015209e5330732009511a0fe7b1058017b1550017b17330a2e2001330701330b15330833090a6433050149210801330843200195170833090f50208207a164177b174001330786077b17480133070009017b1710017b15180149213001951740017b1720017b15280195170895181001502084073401821b0801330701951a08330833090a6400951168ff7b1090007b1588007b1680008285288276828720825a1833080d0f0133090c7b17187b1a10b4a00288073308015207d8007b168267089578109579147b17503307b8077b17587b18603307b6067b17687b19707b17783307602c017b1720491128034911409518507b18304911380395192082161864677b1508645850208a078dd8510708330801287d3308190f013309026467821a10b4a0028c073308015207668217827a82a77b175082a9087b195882a8107b186082ac187b1c6882ab207b1b7082aa28330b017b1a78aab913821808520913520c103309330801281b821808510c10951950646750208e071fd828128278827908016467821a10b4a002900764780164878210900082158800821680009511980032006489828a08828818330b01aaba0b520a0b520808283e51080b3308482f0128d2d7829982980851082c829b827a00013309ff0001aca91e7cbcc87a0a78ac827a000195bb0195aa019588ff7b7a00015208e532009511f8481104552880000e9519057818042880008b890b52091c951906988a0686aac000781a0484883f8688800078180528608b8910520929951907988a0c86aae000781a04978a3498aa3a95aa8000781a0584883f868880007818062834951908988a1286aaf000781a04978a2e98aa3a95aa8000781a05978a3498aa3a95aa8000781a0684883f8688800078180701827a0001951b043308ff0001aca81d7cbcc87a0a78ac827a000195bc0195aa017b7a000164cbab9ce6330795110832006489828a08828818330b01aaba0b520a0b520808283e51080b3308482f0128bdd6829982980851082c829b827a00013309ff0001aca91e7cbcc87a0a78ac827a000195bb0195aa019588ff7b7a00015208e5330732009511e8fe7b1010017b1508017b1600014921c8009517e0009518c8005020960732f18218e00014070000000000000080aa7882028217f00053170861028479f8511908590251191054028479fe5119184c02957be6531b044402957be2531b043c02957ade531a223402957abc531a122c0251195627029579a85319061f02957aa2531a101702957a92531a100f0295778253170807028217e8007c7c187c79197b19607c721a7c701b7c731c7c741d7c796b7c7a6a7c7b1e7b1bb0007c7b6c8a9908d4a9097c7a1f7b1aa8008aba107c7b207b1ba0007c7b217b1b98007c7b427b1b387c7b437b1b287c7b547b1b307c7b557b1b207c7b567b1bc0007c7b577b1bb8007c7b5c7b1b707c7b5d7b1b687c7b6d7c757a7b15587c757b7b15507c757c7b15487c757d7b15407c7582007b1590007c7583007b1588007c7584007b1580007c7585007b15788abb18d4ba0ad49a065108247b121864057b13107b14087b1c502098075611821c8214088213106450821218018467015207480151064e0133070810c3670752074b018215b8009755088218c000d45808510843018217a8008a77088219a0008a9910821a98008aaa18821bb000d4b707d4a909d49707c3870852082201821888008a8808821980008a9910821a788aaa18821b9000d4b808d4a909d49808ae780701821768977708821870d487079678abaa6e8856285555fb00821860978808d4c808978901c898086e88ab87ee008218508a88088219488a9910821a408aaa18821b58d4b808d4a909d4980852288000d60039096c27035629ff00d500970908973a10974b18d42909d4ba0a821b2897bb08821c2097cc08d4a909821a38d4ba0a821b30d4cb0b3d096827033d0a6c27033e0b6027033c0758270382101001821508018216000195111801320033075a1f01330a083001330b2830013308239519ff0028c7dd33077d1f01330940300133081b283dde330704230133081d2857330780320128cfeb3307c8220133083c2845330798320128bdeb330793220133083528333307602201330833282933072e2201330832281f330701220133082d28153307ca2101330837280b3307882101330842017b17e0007b18e8003307522001330ae03101330b00320133081f9519e0002837dd9511907b10687b15607b1658828a2882797b19408276088285207b1a2082a92033082264577b1910b490029c073308015207670251061e027b153302330a3303a149110801000133002233045c82154064687b1618280fc95808c89a0a8216185108c0013307c885097b193801c875097c9b95bc81ac3c12aa0b0faa4b0c957701ab78ec2898017d988486ff007b123057080b9599017b1928285d7c9b0184681f84bb3f330cdf00ae6c337c9c0297bb0684cc3fd4cb0b330cf000acc62e959c047b1c287c990397883d98882b97bb0684993fd4b909d48906281d9599027b1928978806d4b806280f9599037b192897880cd48b0601c8a70595174c646882190850209e07bce07c174c522780001664593303a182123033002233045c28d9007c17577c1856bf87078477ff0082123033045c5217010f64593303a133002228b700ac257d01821840c82808510216821718ae720c7d875a17bf0b286401ab72610151051c821718ae7512821740c857077d775a17bf0b284801ab754501821720827a18c9250982177b1a30b4a002a007520716017c174c52278000188118508217821910b49002a20751072328fa007c18567c1957c9890995174cc878088217821a30b4a002a4075207de00330701532680001798680b33070251080c9867108e77957703016459c857023303a133002233045c01821838821528330a0133078000ac7654fe98670b330a0251074afe9866108e6a95aa03283efec88a0a01ac2a8c008215510230ae6236821740c827077d7764285817bf76510a21ae6a29821740c8a7077d7764825817bf6264a6281b330828173308520ae53306280e6468aa62d928486482ab6a44821720827a18c98609821740c878086457b4a002a6073308015207133308226457821910b49002a8076478016487821068821560821658951170320033080128ed330b302d018217406468642933000c0728dbe2330b182d018217408218186429645a33000c0728c5e2951130ff7b10c8007b15c0007b16b800827508827955250304a0005105da018287207b17308287287b17283307b4077b1720140720000000080000007b171833070009017b1710017b19389517387b17708217207b177849218000024921900049219800024921a0008217187b17a8004621b000038217107b174049114801951880007b1860491168019518707b18504911580164969519408217308218285020ac07cfcf520754019555ff9569015205992843017b15088287207b17308287287b1728330600023307b4077b1720140720000000080000007b171033070009017b17187b196495017b15389517387b17708217207b177849218000024921900049219800024921a0008217107b17a8004621b000038217187b174049114801951880007b1860491168019518707b1850491158019519408217308218285020ae0730cf5207b5009566ff95550152069b821728827a183308212301330903821730b4a002b0076478330701520891008217088218c88707957500fe33060002140720000000080000007b1710017b15389517387b17708217207b177849218000024921900049219800024921a0008217107b17a8004621b000038217187b174049114801951880007b1860491168019518707b1850491158019519408217308218285020b2078bce5207109566ff95550152069c33072806330701018210c8008215c0008216b8009511d0003200951178ff7b10800082777c77330a810095197f0164ab847a0f98770488a20a86ac3095aa57db2c0a789a9599ff95baff5207e495b7fe56278000289aab8100641ac87a0a330902648733082523015020b607efc7821080009511880032003309002d01330880002838c6827a827908648764a82847ca9511b8fe7b1040017b1538017b1630017d79330b1fad9b075209a7037d7a015a1a1f08520a9c037d7b0233061fadb607520b8f037d7c035a1c1f08520c84037d720433061fad2607520277037d74055a141f0852046c037d730633051fad350752035f037d76075a161f08520654037d75087b15c0005a151f0c8215c000520541037d75097b15b8005a151f0c8215b80052052e037d750a7b15b0005a151f0c8215b00052051b037d750b7b15a8005a151f0c8215a800520508037d750c7b15a0005a151f0c8215a0005205f5027d750d7b1598005a151f0c821598005205e2027d750e7b1590005a151f0c821590005205cf027d750f7b1588005a151f0c821588005205bc027d75107b1580005a151f0c821580005205a9027d75117b15785a151f0b821578520598027d75127b15705a151f0b821570520587027d75137b15685a151f0b821568520576027d75147b15605a151f0b821560520565027d75157b15585a151f0b821558520554027d75167b15505a151f0b821550520543027d75177b15485a151f0b821548520532027d75187b15405a151f0b821540520521027d75197b15385a151f0b821538520510027d751a7b15305a151f0b8215305205ff017d751b7b15285a151f0b8215285205ee017d751c7b15205a151f0b8215205205dd017d751d7b15185a151f0b8215185205cc017d751e7b15105a151f0b8215105205bb017d751f7b15085a151f0b8215085205aa017b1888a801889a01d4a80888ca0188bb018e99d4ba0a85bb03db890bd4a808884901882a01d4a90985aa05db8b0ad49808886901883b01d4b90985bb07db8a0bd498088219c000889901848a01859c09dbab0c821ab80088aa01d4a909d498088219b000889901848a01859b0bdbac0b821aa80088aa01d4a909d498088219a000889901848a01859c0ddbab0c821a980088aa01d4a909d4980882199000889901848a01859b0fdbac0b821a880088aa01d4a909d4980882198000889901848a01859c11dbab0c821a7888aa01d4a909d49808821970889901848a01859b13dbac0b821a6888aa01d4a909d49808821960889901848a01859c15dbab0c821a5888aa01d4a909d49808821940889901821a4888aa01821b5088bb01d4ba0ad48a0a84880185bb17db8c0b84a801859c19db8b0c821838888801d49808d4a808821920889a01821928889901821b3088bb01d4b909d4980684880185b91bdb8c0984680185ab1ddb890b8218088e8895881f33091e821c10dbc809821818888801d4a808d46808848801db8b0964789517c80064855020bc07ffcd8217c80051076282186457017b17c8004921d000089517c8007b1720013307aa077b1728013307d032017b17f0004921f80002951720017b1700018287208288284921080101492110019519f0005020be07e8c98210400182153801821630019511480132008217d0008218d8007b17e0007b18e8009517e0007b1720013307b8077b1728013307b032017b17f0004921f80002492110019519200182188287208288287b19000149210801019519f0003300be072881c99511807b10787b15707b1668648564763307800033081f6469015107137d9a9599019577ffada8f5510af228293309800001c876087c8851080b957701ab97f52805647901641764685020c207f8cc821751073e7b164911080864177b17583307aa077b17603307d032017b1728491130029517587b1738825720825828491140014911489519283300c40728f2c88217088218107b17187b18209517187b17583307b8077b17603307b032017b1728491130024911489519588257208258287b1938491140019519285020c407b4c88210788215708216689511800032009511807b10787b15707b1668648564763307900033081f6469015107137d9a9599019577ffada8f5510af228293309900001c876087c8851080b957701ab97f52805647901641764685020c80719cc821751073e7b164911080864177b17583307aa077b17603307d032017b1728491130029517587b1738825720825828491140014911489519283300ca072813c88217088218107b17187b18209517187b17583307b8077b17603307b032017b1728491130024911489519588257208258287b1938491140019519285020ca07d5c78210788215708216689511800032003307011409e0ffffff1f000000ae8907330732009511d87b10207b15187b161095861f3305080003955948278499fc013e04000003977420994420819ad44a047a943a0400000384aaff00520ae59557002082770006986605510734011407ffffffff00000000aa760d015648e0ffff3f090183620164675328c10f2a69278d7918c6920a84aa7f9099ffe092098e99977707c97a0ac8a9099597ff079577800401330b0020c8b509829a38078b7906d09a0c84cc01510c1d979c03955b0020c8cb0b82bb0806330cffcf7c07d2b7075207219599019b97ffd2a7075107d4006a7997970395580020c8870782770806016a748a9706d44707977702c8750b80bc80ca04836798aa01aea76b7b12089557002082730006bf3c008b0805c868028568ffbea808817744077b188e88c82808838a01aea72433076577c9370797a825988220ae270bc97207657751072b955700207a7a44070180ca0881cc8407e0c837077aba510aa300481a0c821408821028d000330701955800204628480782102082151882161095112832007b18085020cc078b011408ffffffff0000000064793307aa86d38218085209c1fe28c964675428c00f1f69278d7818c6820884887f977707c978089587ff0795778004018b7906d09a0884880151081e979803955b0020c8b80882880806330bffcf7b07d287075207fdfe9599019b97ffd2a7075207dffe2872ff979a0333020020c8250bc8ab0b82b808063303fedc430ad2a8087bb8080682140882105208169552002082283807dc9309d298087b283807019748019588019779209899207a9c7a9804957320976a05c8a30951008b00640754107f2a330700000002e67007697a8dab18c6b70784777f97aa07c9a7079577ff079577800495770101837bff8bb70684b23f97bb02c8b50c80cb7ac9970c01979a2098aa207aa47aac047aab08481a0c510b077ab90c01977a03955c0020c8ca0a82ac08069b2801d4c8087ba80806955b002082b838079b7701d487077bb738076404019557002081784007817a0006970705be9707978805bea808ae87149777209877207a749737209877202868fe973720987720285ffe33087127033307657795881f8488e0ae8714c9780765793307510962fe520809285bfe51084333076577c9870756171f0d9a79206597510745fe3303080003953b002081ba4007482b4407013307000000027bb80006aca71c510a12552a80001383a4ff282833072813fe3304281f64a70169798d9a18c6a70784777f979907c997079577ff0795748004018b4b0697b70395390020c87909829c0806330701cf470ad4ca0a7b9a08069532002082293807812a4007cfb70bd4b9097b29380797aa0148087a8a0448180848180c974402c843037a3828a5fd3303080003953948278499fc330a01013e0400000397a420994420819bd44b047a943a0400000384bbff00520be5510794029511d87b10207b15187b16109570e09709209537002082750006989920809a04819bbf500c98a901541c1fd60097ba05bfa00c97ca2098aa2080a4048446015206bf009848013306000000027b185644000000041651042a6486552400010a958bff282069688d8518c6560684667f978807c986069568ff07958b80042805330b0181a87b180897b52098551ec83505815280a608abc20d7a5651061648160c284780aa0c7aa60851063f7a6a0c28398bb606976a0395380020c8a808828a080684bc3f3305fedcc50cd2ca0a7b8a0806520a169532002082283807dc650ad2a8087b2838070195380020828500068218be8909974404bf4000821b0801817a400797aa05be5a02979c05be0c0cae2cd10097ca2098aa2080a6048467015207c1007b1b08986b013305000000025646000000041651062a64b5552600010a95b4ff282069578d7818c6850884887f977707c978089587ff0795748004280533040197472098771ec87305815780a608abc7137a5651062248160cbeb909821b08285e80aa0c7aa60851064f7a6a0cbeb909821b08284a8b4a0697a703330c0020c8c308c887078278080684453f3306fedc5605d258087b78080652081d953c002082c73807dca608d287077bc73807beb909821b082809beb909821b080151098900649a54197f2a330700000002e6790769788d8a18c6a70784777f978807c987079577ff07957a800495aa010183a8ff8b8a06848c3f978802c8380880867a809798019707209877207a7b7a78047a760848170c5106077a600c0197a70395380020c88707827808069bcc01d4c8087b78080695360020826738079ba801d487077b67380701979705be0707ae270c9777209877207a790182102082151882161095112895370020462748073200953700204627480732009511e87b10107b15087b16531910698d7a84a207c82706ae671a642c647a648b017cb578a595aa0195ccff95bb01520cf3c82804c929028423f8844807c8360a51083eaea64e9748038480388445f8825b8d88848c38955808018285d00b0bcfc509d4b9097b69956608958808645baca6eb281f647ac8970bacb7242831aea61364480182897b69956608958808aca6f6c83408842907c89a0baeba137c8b78ab95aa019599ff9588015209f3821010821508821695111832009511d87b10207b15187b1610c9870aae9a8500c8970c330a10c89804aca91b0184c20784c0f89a23aec01ac898059555ff017c5695cbff78c6ff9555ff64bcacb0f3c83404c929097b19088499f8844b079a9a7b1ac9900c510bb100ae0ccc00974903849338844bf882b58d9984993895bbf864060182bacf9505d03a02d452059568f87b65f895bbf8648664a5ac8ce82897005319106b8d7a84a207c82705ae571a642c647a648b017cb678a695aa0195ccff95bb01520cf3c82804c929028423f8844807c8350a51088a00aea599009748038480388446f8826b8d88848c38956808018286d00b0bcfc609d4b9097b59955508958808646baca5eb2869647ac8970bacb76e287bae0c1f821908c898089588f8640901828a959bf87b9af89588f864b9acbcf38218c8800cc8840482190884990701c99c08aec848954bff017cb995caff78c9ff95bbff64acaca8f32831aea51364480182897b59955508958808aca5f6c83408842907c89a0baeba137c8b78ab95aa019599ff9588015209f38210208215188216109511283200531910558d7a84a207c8270baeb713642c647a0178a895ccff95aa01520cf8c92909849af8c8ba0aaeab22978238140c0001010101000000d6c202972c20d42c0c017bbc95bb08acabfb849907c89a0bacba0d2815647ac8970baeb70e78a89599ff95aa015209f832005109167c7a7c8babba139599ff9588019577015209f033073200c9ba07320021449248924a9224292529499224294992a42493a49288881455921409295145442480520009204a22499294449224495242924892a4942488228a484494049024521111a98888448988a822228088882401948a884800250144a988885444442a22228924499224499224499212404914228088882401942844440450921021222a218a484492882422454422225223122122222212a3488a440489882412914244a2a4445222224a444489880812058982880822228888880891444424228088484544240a1140444492004a1422808888240194444992944452224989101285082022224900250a1140444492004a00252925414a9224499294004a0154194a0025494450442208a002684444894422224924912289482252231221222222899822492291481249249244128924494a2422892442125191442229914422914422112989884842124944121111112426495212045022491149a228120144120194244992244992244992882492004a92440489085188108588081122491240024825494a045049929494a40a2a93a48888240920a5004a929442842892a44c22124992489148225223122122229229498914944492244948282929499224499224491249922449922449924492244992244992249124499224499224924422422489888244005186244902484992441011514488428448124045449494942449922449922491244992244992244924499224499224491249922449922449922422892492242521128944228944222989449248241289444a22499224499224499104d088884a924494880aa21001248054248944929224499244241289249124124992482291481289442249124992244992244992884844442448228288288988489248249224490248005152529224499224499224499224499224499224499224499224499224499224499224499204504992244992244992949424154050992425491240a2a4a424499224499224499224499224499224499224499224499224499224499224499224499224494a49244d92922449929424499224499224499224494952414a9224490009204a4a4a92244992244992244992244992244992244992244992244992244992244992244992002a49922449922449929292a402082a93a48888049000a29224298924499224499254122529499224495242008d88288a9424499224499224499224494a9224499224499224aaa04422912449000920952429114025495252922aa84c92225292144a9224249594224b92a42409a254644992942441942904224a34a54a42529225291051880811494a4921214988a4409224499224244a121292a422494a490a4991244a044a8152924492244942424999242424492a45920aa048524848924881a4442224914492905022490a0949122990942491244992905052260989324948489292a4922409094992449324059224932409092d49aa841492924a924a922424244992449324059224932409092d494a925292425224891288122825492449922424949449424292a4522524422222254944145992242525214a459624494949883285404421224444a4248988224b92a4a42444a9c8922429290951a6108828448488520124494849921452084912299094249124499290505226090949529294922424244992449324059224932409092d494a924a9224242449124d921448924c922424b42429494a9224242449a249920249924992848496242549459a92a49892d40a94a24442224922894892241212499248204994a49414129284480a244992244942a2242121492a495229252121494a024812492029492249922421a1a44c1212922425492549121292248926490a2449264912125a9294422449294921214988a44088a4409224499224244a1212922445244994a0482489249224a42425494a912549929292204a459624494a4a82285308449410892449529246535225492a69490a4414224244449224228a2c4992929210a5224b92a4a42444994220a210112222922411516449929494842815599224252521ca1402118588109124154012404a24a494240124929444521249810248a49424294952a09424294952a09224492449495292420a25891281a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a4144852929090242549892a41922495a4404a922425494152522029490a2124114248224952494a498194020994920aa4aa929249529294249524294912409224a929094a52929484089124952429499224494d49509292a424448824a99424899448002989243549822429492a928a22492a4952922480244952531294a42429091122492a91244949920092244952531294a42429091122492a499248892491a4a624284949521222449224952449924c4941494a920a9184482a92a4244942429224a29124052249264912125a9294242549121292248926490a2449264912125a92540049125292248554084912299094249124499290505226090949524444152801b488888a88109124801440525226914424922489a44892489244a46a44228888442453a42449494912492251498924084992244992244992242989442a229152942449922449922411112905922429492249492422aa1849924424112935229120229232224925812449929214894424aa484425484512491248128912499224492422495292a44029a2880452240288882825a1a21011210a1111224488124049114944940490a44412114992448a244a12911a91082222112999922491489248241249922449922489244992245212499244a22289241289444a22499224912449a4242292204912498a288a44001111a52c22a2880824224421429490425252492a2429a4902491024989444822892421a14492145248924881a42489244992848492324948944942429294a422490a292449a440522211924822494828912429a490249102494912499224090925659290289384842429292212492224920889244242482a909424a9a424a9400124809214400550004b5252529292a4004a4a52524a929424920a525292022593a414485281920aa41042121111298054529254a00450002b22a29444a22452228944922491229124223592081249924c494a228124499224218992929224499224499224912449922449922449244992244992244912499224499224499224120992a42401244ac9a4248904928428448888880248024809299524491222424444444488444224111222224424110891281022512044a240884481108902215281882412914492902849489224420a8908914420229148124942a2244924899048222489901092244922a49088104981104904221289249124244a92444821112192022192084424124922494894241152484488240211894492481212454412492224920849222442485620494292241152484488240211894492481212254922a49088104981104904221289249124244a92481252484488a440882481104904221289249124244a924448211121920221920844241249224948942411524844882402118944924812122549841412112289404422912492844449122185884424028912891011492409092592241152484488240211894492481212254922a49088104981104904221289249124244a9208292422441281884422492409899224420a8908914420229148124942a224892421854484480a84481208914420229148124942a22489904244221281448944888824928484124992082924224412818844224924098992848c882849489424244a12122509899284444942a224215192902849489424244a1212c524215192902849489424244a1212250989629290284948484444495292420a25214a8124494a9224244a1212922445441225918424492422492042142521134992481291944022499290844c44a2242111894245442451124948924422920422449292908924492489484a204992242421139128494844a25091a42485149284480a244992244942a2242121494a52424a4942929424855408494224059244224924098992842429490aa9109284480a244992244942a22421494a12122509094952929412494292a490244948214988a440882481249148124942a22449482149882481249148124942a224495248214988a40884a40221920492244992242151929028494894242424494a924a92242593242545529224299924499292499248929249922429992489242999244992924992489292499224299924892429992449929249924892924992242999244992944c9248929249924892924992a444529292a49249122989a42449c924899224a90249264955054a8aa40a2429502a50924a202549259092241489842244925404229124a92449922449924412811229495244444444444444448408111122021111929a02212210014412111111288a24224a9208201240149188241149929288241211911a910822898888c48822492422222222222222129188442422118948442212918844242211894844a244249148222222229292244992244992249224499224499224892449922449922449224992244992241149924424254992244992244924499224499224491249922449922489489224922449922449922489484a922449922449924892244992244992249224499224491291244924499224499224491291884849922449922449124992244992244992449224499224499224912449922449922449442051449444441245242202111111111111119124898848449244449226220211111129222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222202111109209214c84822221245924822110821242222804444a24023114004108180282291541289a440454444444402455224222949119188241149929288244a44a4a81189209288882862144991884024228944a2888804118944442022224204914412119188482212490051144124252202914444442022224284880811414484a4680a44244991849428224992244992244992489224499224499224922449922449922489942449922449922411111111111111888888a826499224104948815455201189444424924444444444840811112122888890d4144820294a922424354952202229522289249144129188888888102122424410111111d124492402111148441252d144480924210542922a222292244992244992248948922449922449922422499224499224499288942449922449922411116992884812911411111111111111111122444488082222243505229288888888a4401211914431123111498124228954a0882222922449922449922489489224499224499224224992244992244992889424499224499224111111111111214244848820224252532022a2492222492449922449922449124992244992244992449224499224499224919224499224499224a249444444881011112282880849d148244992244992244912499224499224499244922449922449922491922449922449928824a249029184140849aa888888888824491189884912111111214244848820222224355220926a8448444488101121228888a86a4481444a444412910249442451448c488891924022111a29092412414b442022111a118940444444442205222212291011918802111189281091888888881021224244101121a92910810811128d2411910492420aa4aa222a9a908888888888102122424410111111111122444488082222226a922452202202898888888888088c081111111121222244041111929a0291888844442422222244448828222244441011a99a02111111d1484424892449922449922449229224499224499224894892244992244992242225499224499224914444443489888888880811222244041111929a0221118986904892244992244992242249922449922449928824499224499224492252922449922449124944442d21222222440849948888880891404444222222224284880811414484a4a640024911254992244912929a481289882822118988888888888848202925499224499224492481a4482211491422458994118908444424909492244992244992248914912489880822221211818888881021222244041111929a0211442249922449922449924892244992244992249224499224499224899424499224492449121111888888881021222244041111929a02111121154d242212114544229124294952a088888888a8454422229240524881545544442d1211898888a84622428a2612492092420aa4aa2222898824922411918888081122224244101121a92910119104922222229288d424498188442022222222a24644222249444448d124492423222222222244888810114444488aa68c8888881021224244101121299a029126222222222242848808114144a48aa640442225492a50141111191111111121424444880822222435052222222210111141444448d1244944444a69920291888888888888888810212242441011a9a2291011111145554481482092420a84245520222222aa15681211111122444484882022425234052222229288888888189224a411111111111111112244448808222224350522a2494844a2114222225192244944115104a2882862922449922411912889442449922449922449129124499224499224494492244992244992241129499224499224492222222291261289442081488a922449922409494d241211092495244952a06a522081480a29109254110522924891482219111111112142444488082222243505221118112222222242444488082222243505222222a24944246544242222111111112222441411112222888890144d8188482009118920222146420289288814888854228988484444445044249324054a06904a352222222222222222222222a244a2245112118188924889249240222212058a28228aa811111111111189a8a02262225224128944229144241191144822124914442452449240249288242211051189189128908822119324494984484144242a88884482888822624402498848041109312221814814458b282111491249111122222946229128222292444492942492129188d48848041149444444230a8948a248241291442412494492241191888888288988484442224992244992244992242222a51451922449922449922411111115052289482211114962228ac248229288484444a4d48888444410111111458c884815818848124544001191349448928888444444241111912449922449922449229148249292244992244992244922499224499248922449922449124992449248241249444492888820111111814844220a94122311884420898888444a921428221188c48824121111954444442225490a149108442205119148414424228088884853248024450a12894420229208242205a28888281191428a08098944918244221108212422851411129288900821241209204a248924a908449202954444448810112144121112218444121112218444121112218444429142222222224488880821121111214244841009212422111111224444089188889010929224492242228490482242228490489224222442088924222442088984842422244208892422244208892422244208892422244208892422244208892422242284908824222442088984081122442222224488881022111111224444089188880811222284484444448408111121442222224488881022111111224444089188888808112222428844444444881011114224222242848808211211112142448410898888102122428844444488101121442222224488881022111111112222441411112144242222224284888810228908891042228908891042228908891042228908891042228908891042224944444484081111214442840811224912111111214244440891242222224284888810222184442424112129881042222584908844841012098988881021224288444444881011214422222244888810221111111122444484108988888810212222844844444821214922422284904844444484081111214422222222448888082112111111214244440891888888081122224288844444549024111194445052441001248948928888880811222244041111929a82002211112142049000128948484828808420292291882849a218111188482222884892228488244992241289829224910821224244045092240920499224092049922401244992244988128904918888889824499294242222224992888824499288884892242222499224222292248988489224898888244922229224912422228888444444444488402222922449922449922489244992244992244922499224499224499248499224499224491251d542120549549088484204221141222222222249244a44128da2442491111111111122444448908488101124111121291a5190888408442452249148499282222248924492288d282251905441aa281a91484a249222498288840844245292243142924824a2308aaa8818914884880811414424a92988888888481488884824298988884022224284880811414484a4a62092884841129148a22092488448005124889124499224494a8c882445929448892222522411622492208944884992244992a4882411224992244992144922449224499224299244882449922449522411228920220a242222299188882492089182484444124922449224299244882449522489104992a42491240a124512459224499224298988928824495224914841944824828844111149222292481221929444445224922089884844a220a28888244944a2205124512449922449929288284924499224499222898814448944228848242222894822449224292289882449922449524412114992244992a448224412414422229212898848129184489224251211911211914412214622099248849824499224498a4812211111c9224984489224451221920489444424894822111149222292449424224992244992148988944844445212118988222211444444141125444422099208914882248248441089105110118914442452101191888288884444242389102391044924424c92244992242512119124118144442489884812918448922445122146449220918848894444248944222229918888648924028988484a2422524922120551a448244112119188444144912012298888448288448248248844824824884482482488448248248844824824884482442241229120914810892888448248248844824824482412442205119148109148108904914810892888448248a4202212092212092291209148108904894482482488448248248844824824884482482488440a2222518222120922912012092291201209221111119128a24022910892041111114592829024849048444488101121444284081122218444424a491284101209092124928844082191901042222184442222428488082112424824492a4892525054505450aa88888888884849822405d592a410424a1211514192444450445044508488200248441411415124a2482280244a24119124492245244a24223522118888444432a2248924914812914824924422914812894422491249922449922449924422111144229128128924892422224a2412494424128944121112492412894422898888884824128944229148242222228288882092082022222988884488002222228a888888828888041129824841414422222549924c108d4424881241223549144410450411051191208a88241151504444449011111111111121222222228844101121a929888808112222440411111111111141441051d424918288824422a888882224924492444884101289441042482422a49090442405114248242222448888102211112142448410092124124292aa148a444a2a919048124992242949252489244929292d499224493252521049a554ca2491544a4841420849529a826492242549a98292424a92904a529294524a0a4a4a92aa8252924449a9a04949922492922492445212492249245322a524419992a42a2825494992a494902429a5a4a42a482949aa0a4a49524992244932a5a0242549852421295130a5449284449248a5249192904812294d92921024012444b02449922491242949494a124924499294a44692549294a4495221494a9252929024954a5a92a49ac29224499224112949a52091922429499244915290241349428a24299324992409c992a45492204949485232892421294942a29224499224492a289542a524a992a42649495229894429a5882a93949448524a4d552246a444528d2451d29224919224a524492a49b22449020802480001242549922449125124491169499224499224499224499224292992244b9224499224495224911449529a24c92449489224a12425932449488a2492942425299924a59048914849294922499244a224495292644a92244492a42492944aca249148522a2993a42409892489922449495296240929094992945452929484a4268992a492102401244490499224491249929424a594124924499294a44892925451244993a482242922491291c89224292909518a2c4992929210650a414421122249529424490a299192242905902489b24892484a9224122989249242214922251492348a48114512912449514492a4244991a8a44a294b92254911112549a24a9248484252a1924492902492848424911292922421152a4964922449529224499284a442421292242449482829992822a224a904010490a42449212549a5644a9224942491441249922869499224694a9224354992a44c499200922449a22449a624258914959a5492922449a1244aa932499424a9242125144a922425a524a924494a1252494a522249242492444a4a892409892491925222219124524644a4444992948484244948894a48494a9290501249932449929224499224248524090925259322228a8824894a922449480a2525512125491292144a4964922449529224499284a4902421a1a4642222514444914822914822914824128944920a8948254192244892044992202249922449489424242422a2248984942249242211521252922429499292242949929224254992484a449284494a2461a424692224a42449492a49922449522549242991248922a2284ac492442649211185101249484992925492244992a44a924852224996244912851012ab8a2449294924551402211042082111821084104204a224440929a38448128948460a49429292244914118948442422242549442422121192492429154952322549122a49a62499a4a4a848528a240949129224495052320949522649442422121189884484244992a4a442498904094985a44249921492495292aa2049424292945252529244912949295592922525459224496a92a450a99412491212492224214952490892001222c8922449924492a4242529492491244952921a4952495292264905495244912549525212a2a4104414222914899424a924252549024892243525819294242521214952494a4a9224495253122849495212129224a59492829224a92a949224a9244992644aa1242549852421499224a904a9828254d024494a924a25a490945492949092a4aa6a9254d11249415441092955a510404a0929552999aa11024825492a99a41a2180545232490a098944425242229190844422218948aaa02449008d242a2a55349124894892a4d424492091a46422499520495292140114552c498a0429249424214992442525228924499224499224499292524a49922449922449922425a5829294244992545a92149224954892488904902492d494044a5292948484244912959444122549922449922449929494524a92244992244992242929159454922449b2242924492a9124911209204924a9290994a4242909094992a452d52229882a28024865a94aca24292489248490a4249190481221499292542d4951105590a4aa9422802ac924298924849024a4249190481221898828419482d49024499224499224499224499224499224499224499224499224494992244992244992241224122412241224122412549224490a92244949922449898828490a4248924842084992484844122149c4942249a422a290548990842222444444248248aa111129094942a224892492844492484a4152480949952449922449524a525059428b88a824494a484911538a249192902449228994249124924849248924914446624a914429952449092929624a912452111111114400112121242424242484848484844892244922492289449224492291244992248988888824494a0a4aca44442212894824124912914812492249a22049522449922441089224499224092184888842088984908414125248482185145248212342088924494a92a48444540992949244c99424498a24294924492249922429494a924909124a922489244a1249922291242912494a8884949024494a888424294b92492292484946495291241555a54a2549524852424b925248454252918888144492440248c824498810899044249228491049922489480009a94992102112218948249124882449484289948800324912224422241189249204495219115144459624494912a2a410441422a512115112894812894812894812894844442222121191888844442422221111898848444412892412492492482491482291442289441289241249249248249148229144a22449499224499224912491249224924492449248924812499224499224499224499224499224499224499224494a9224498a82488d10111122442421228888282222222244882889109124a94853921453925a41294a242492249248922491904892448224519254a429498a2949ada014251212491249244992484824492241922801a49224213224a51089480410012994242949c448244922912449a2446249524a129124894a52499212312545521a91242508a092a850922462922412492249249144924889486292942451042949129149924a52926422914444128d882449529224a192a422492552848810441252a996248998442249444412294952422443520a91489224222909499244820229a4922411b524292929494a2291249188244694242349224922090229a49224113549494952922425914492442292484a4611a424494426492a49499289441211494c923449228a284951926a254992244952c99294944a4a2b4992549224a524492489a4244b52922495882495b424492a8992542b49922411492a599292524949d292949264922529a59524492a4992a428492d49922480244b925452925249924a", - "01000000000000000000000000000000000000000000000000000000000000": "08a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad808a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8", - "03000000000000000000000000000000000000000000000000000000000000": "01b5af8edad70d962097eefa2cef92c8284cf0a7578b70a6b7554cf53ae6d5122200000000000000000000000000000000000000000000000000000000000000000000", "0c000000000000000000000000000000000000000000000000000000000000": "00000000000000000000000000", - "07000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "0a000000000000000000000000000000000000000000000000000000000000": "0000", - "06000000000000000000000000000000000000000000000000000000000000": "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25bb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aae88bd757ad5b9bedf372d8d3f0cf6c962a469db61a265f6418e1ffed86da29ec", - "ff000000000000000000000000000000000000000000000000000000000000": "bc804dccb6bae35cd48cc1e11e426e7ba218727e2ba7dd43e8163f4f5d06333fffffffffffffffff0a000000000000000a00000000000000c97102000000000004000000", + "0e000000000000000000000000000000000000000000000000000000000000": "000000000000000000000000", + "09000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "01000000000000000000000000000000000000000000000000000000000000": "08a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad808a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8a8ae322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea77f839702e1ad8", "00fe00ff00ff00ff5b06efb68e00d58f668d6c2f5e34bdfe52c18d1176b230": "4e00136a616d2d6e756c6c2d617574686f72697a657206302e312e32330a4170616368652d322e30012550617269747920546563686e6f6c6f67696573203c61646d696e407061726974792e696f3e801e006827000000002000e200ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffec00ffffe600ffffe400ffffe400ffffea00ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffe400ffffee00ffffe400ffffe400ffffe400ffffe400ffffe800ffff6e00ffff6e00ffff6e00ffff6c00ffff6a00ffff6c00ffff6a00ffff6e00ffff6e00ffff6e00ffff6e00ffff6e00ffff6a00ffff6a00ffff6e00ffff6a00ffff6e00ffff6a00ffff3400ffff3400ffff3400ffff3200ffff2e00ffff3200ffff2e00ffff3400ffff3400ffff3400ffff3400ffff3400ffff2e00ffff2e00ffff3400ffff2e00ffff3400ffff2e00fffff0fffefff0fffefff0fffeffeefffeffe8fffeffeefffeffe8fffefff0fffefff0fffefff0fffefff0fffefff0fffeffe8fffeffe8fffefff0fffeffe8fffefff0fffeffe8fffeff63616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c75652f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f6d656d2e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f736c6963652e72734e756c6c20417574686f72697a65722c205b5d2c20206761732c2020706172616d2c20202862797465732020746f6b656e417574683d3c3e417574686f72697a6174696f6e206661696c656473657276696365732f6e756c6c2d617574686f72697a65722f7372632f6c69622e72736361706163697479206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72736d656d6f727920616c6c6f636174696f6e206f6620206279746573206661696c65642f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f737472696e672e72736120666f726d617474696e6720747261697420696d706c656d656e746174696f6e2072657475726e656420616e206572726f72207768656e2074686520756e6465726c79696e672073747265616d20646964206e6f742f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f666d742e72732e2e00000029207768656e20736c6963696e67206072616e676520656e6420696e6465782001000000000000000000000000000000303132333435363738396162636465663a63616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c756570616e69636b6564206174203a0a696e646578206f7574206f6620626f756e64733a20746865206c656e20697320206275742074686520696e646578206973203a202f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6e756d2e727330303031303230333034303530363037303830393130313131323133313431353136313731383139323032313232323332343235323632373238323933303331333233333334333533363337333833393430343134323433343434353436343734383439353035313532353335343535353635373538353936303631363236333634363536363637363836393730373137323733373437353736373737383739383038313832383338343835383638373838383939303931393239333934393539363937393839392f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6d6f642e727372616e676520737461727420696e64657820206f7574206f662072616e676520666f7220736c696365206f66206c656e67746820736c69636520696e64657820737461727473206174202062757420656e6473206174202f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f6d6f642e727301010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000202020202020202020202020202020202020202020202020202020202020303030303030303030303030303030304040404040000000000000000000000005b2e2e2e5d626567696e203c3d20656e642028203c3d20606279746520696e64657820206973206e6f742061206368617220626f756e646172793b20697420697320696e736964652029206f662060206973206f7574206f6620626f756e6473206f6620602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f756e69636f64652f7072696e7461626c652e7273000601010301040205070702080809020a050b020e04100111021205131c140115021702190d1c051d081f0124016a046b02af03b102bc02cf02d102d40cd509d602d702da01e005e102e704e802ee20f004f802fa04fb010c273b3e4e4f8f9e9e9f7b8b9396a2b2ba86b1060709363d3e56f3d0d1041418363756577faaaeafbd35e01287898e9e040d0e11122931343a4546494a4e4f64658a8c8d8fb6c1c3c4c6cbd65cb6b71b1c07080a0b141736393aa8a9d8d909379091a8070a3b3e66698f92116f5fbfeeef5a62f4fcff53549a9b2e2f2728559da0a1a3a4a7a8adbabcc4060b0c151d3a3f4551a6a7cccda007191a22253e3fe7ecefffc5c604202325262833383a484a4c50535556585a5c5e606365666b73787d7f8aa4aaafb0c0d0aeaf6e6fddde935e227b0503042d036603012f2e80821d03310f1c0424091e052b0544040e2a80aa06240424042808340b4e03340c813709160a08183b45390363080930160521031b05014038044b052f040a070907402027040c0936033a051a07040c07504937330d33072e080a0626031d080280d0521003372c082a161a261c1417094e042409440d19070a0648082709750b423e2a063b050a06510601051003050b5908021d621e48080a80a65e22450b0a060d133a060a06141c2c041780b93c64530c48090a46451b4808530d49070a80b6220e0a06460a1d03474937030e080a0639070a813619073b031d55010f320d839b66750b80c48a4c630d843010160a8f9b0582479ab93a86c68239072a045c06260a460a28051381b03a80c65b654b0439071140050b020e97f80884d6290aa2e781330f011d060e0408818c89046b050d030907108f6080fa0681b44c4709743c80f60a73087015467a140c140c570919808781470385420f1584501f060680d52b053e2101702d031a040281401f113a050181d02a80d62b040181e080f7294c040a04028311444c3d80c23c06010455051b3402810e2c04640c560a80ae381d0d2c040907020e06809a83d80411030d0377045f060c04010f0c0438080a0628082c04023e81540c1d030a0538071c06090780fa840600010305050606020706080709110a1c0b190c1a0d100e0c0f0410031212130916011704180119031a071b011c021f1620032b032d0b2e01300431023201a704a902aa04ab08fa02fb05fd02fe03ff09ad78798b8da23057588b8c901cdd0e0f4b4cfbfc2e2f3f5c5d5fe2848d8e9192a9b1babbc5c6c9cadee4e5ff00041112293134373a3b3d494a5d848e92a9b1b4babbc6cacecfe4e500040d0e11122931343a3b4546494a5e646584919b9dc9cecf0d11293a3b4549575b5c5e5f64658d91a9b4babbc5c9dfe4e5f00d11454964658084b2bcbebfd5d7f0f183858ba4a6bebfc5c7cfdadb4898bdcdc6cecf494e4f57595e5f898e8fb1b6b7bfc1c6c7d71116175b5cf6f7feff806d71dedf0e1f6e6f1c1d5f7d7eaeaf4dbbbc16171e1f46474e4f585a5c5e7e7fb5c5d4d5dcf0f1f572738f747596262e2fa7afb7bfc7cfd7df9a00409798308f1fcecfd2d4ceff4e4f5a5b07080f10272feeef6e6f373d3f42459091536775c8c9d0d1d8d9e7feff00205f2282df048244081b04061181ac0e80ab051f08811c03190801042f043404070301070607110a500f1207550703041c0a090308030703020303030c0405030b06010e15054e071b0757070206170c500443032d03010411060f0c3a041d255f206d046a2580c80582b0031a0682fd03590716091809140c140c6a060a061a0659072b05460a2c040c040103310b2c041a060b0380ac060a062f3180f4083c030f033e0538082b0582ff1118082f112d03210f210f808c04829a160b158894052f053b07020e180980be22740c80d61a81100580e109f29e033709815c1480b80880dd153b030a06380846080c06740b1e035a0459098083181c0a16094c04808a06aba40c170431a10481da26070c050580a61081f50701202a064c04808d0480be031b030f0d2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f756e69636f64652f756e69636f64655f646174612e7273617474656d707420746f2063616c63756c617465207468652072656d61696e646572207769746820612064697669736f72206f66207a65726f0003000083042000910560005d13a0001217201f0c20601fef2c202b2a30a02b6fa6602c02a8e02c1efbe02d00fe20369eff6036fd01e136010a2137240de137ab0e61392f18e139301ce14af31ee14e4034a1521e61e153f06a61544f6fe1549dbc615500cf615665d1a15600da215700e0a158aee2215aece4e15bd0e8615c2000ee5cf0017f5d00700007002d0101010201020101480b30151001650702060202010423011e1b5b0b3a09090118040109010301052b033b092a180120370101010408040103070a021d013a0101010204080109010a021a010202390104020402020303011e0203010b0239010405010204011402160601013a0101020104080107030a021e013b0101010c01090128010301370101030503010407020b021d013a01020201010303010407020b021c02390201010204080109010a021d0148010401020301010801510102070c08620102090b0749021b0101010101370e01050102050b0124090166040106010202021902040310040d01020206010f01000300041c031d021e02400201070801020b09012d030101750222017603040209010603db0202013a010107010101010208060a0201301f3104300a040326090c02200402063801010203010105380802029803010d0107040106010302c6400001c32100038d016020000669020004010a200250020001030104011902050197021a120d012608190b01012c03300102040202020124014306020202020c0108012f01330101030202050201012a020801ee010201040100010010101000020001e201950500030102050428030401a5020004410500024f04460b31047b01360f290102020a033104020207013d03240501083e010c0234090101080402015f030204060102019d01030815023902010101010c0109010e07030543010206010102010103040301010e025508020301011701510102060101020101020102eb010204060201021b025508020101026a0101010208650101010204010500090102f5010a040401900402020401200a280602040801090602032e0d010200070106010152160207010201027a06030101020107010148020301010100020b023405050317010001060f000c030300053b0700013f0451010b020002002e021700050306080802071e0494030037043208010e011605010f000701110207010201056401a00700013d040004fe0200076d07006080f0002f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e72734572726f722f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6d706163742e72736372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f686f73745f63616c6c732e7273686f73742063616c6c2072657475726e7320636f727265637420747970653b207165646974656d206d75737420626520617661696c61626c653b2071656450616e69632068616e646c65722063616c6c65642150616e6963206d6573736167653a2050726f746f636f6c20706172616d65746572732061726520696e76616c69646372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f737461727475702e72736372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f726573756c742e72734f7572206f776e2041504920696d706c2068617320726573756c74656420696e20737563636573732076616c7565207768696368206973206f7574206f662072616e67652e6372617465732f6a616d2d74797065732f7372632f73696d706c652e727360726563656e745f626c6f636b5f636f756e7460206d6179206265206e6f206c6172676572207468616e2060426c6f636b496e6465783a3a4d415860202832353529607472616e736665725f6d656d6f5f73697a6560206d75737420626520657175616c20746f7220604d454d4f5f4c454e602028313238296076616c5f636f756e7420213d20636f72655f636f756e74202a2056414c535f5045525f434f524560202833296076616c5f636f756e746020646f6573206e6f7420646976696465206279206056414c535f5045525f434f524560202833296065706f63685f7461696c5f737461727460206d757374206265206c657373207468616e206065706f63685f706572696f646060726f746174696f6e5f706572696f646020646f6573206e6f742064697669646520696e746f206065706f63685f706572696f64606062617369635f70696563655f6c656e6020646f6573206e6f742064697669646520696e746f20605345474d454e545f4c454e602028342c313034296062617369635f70696563655f6c656e60206973206e6f74206576656e2e2e2e2230782b2a300000000000000000000000000000000000000000000001000000000000009200000000000000a30101000000000054000000000000002000000009000000f70101000000000077000000000000009f000000190000006e020100000000001200000000000000800201000000000003000000000000008302010000000000060000000000000089020100000000000800000000000000990201000000000006000000000000009f020100000000000600000000000000a5020100000000000100000000000000a6020100000000001400000000000000ba020100000000002300000000000000160000000d00000094000000000000001800000000000000080000000000000096000000000000009c00000000000000a400000000000000dd020100000000001100000000000000ee0201000000000079000000000000002b02000011000000de030100000000001500000000000000f3030100000000000d0000000000000067030100000000007700000000000000b60100000d00000000040100000000007800000000000000ea010000170000000000000000000000000000000000000001000000000000009200000000000000ce0401000000000075000000000000007e0200000e000000000401000000000078000000000000008d0500001b000000010000000000000000000000000000008805010000000000010000000000000088050100000000000100000000000000c2050100000000002000000000000000e205010000000000120000000000000001000000000000000000000000000000f4050100000000000200000000000000f60501000000000078000000000000005b0000001700000036070100000000007800000000000000a70900002600000036070100000000007800000000000000b00900001a000000ae070100000000001200000000000000c007010000000000220000000000000058050100000000001000000000000000c0070100000000002200000000000000e2070100000000001600000000000000f8070100000000000d0000000000000083090100000000000e0000000000000091090100000000000400000000000000480501000000000010000000000000009509010000000000010000000000000096090100000000000b00000000000000a109010000000000260000000000000091020100000000000800000000000000c70901000000000006000000000000009509010000000000010000000000000096090100000000000b00000000000000cd0901000000000016000000000000009509010000000000010000000000000005080100000000007800000000000000f20000002c000000e30901000000000082000000000000001a00000036000000e30901000000000082000000000000000a0000002b0000000e1001000000000085000000000000004e000000280000000e1001000000000085000000000000005a00000016000000931001000000000039000000000000000000000000000000080100000000000008000000000000003c010000000000004e010000000000005001000000000000a61401000000000060000000000000002d0100003d00000043140100000000005e00000000000000920400001e00000043140100000000005e000000000000006c0400000f00000043140100000000005e000000000000009e0400001000000006150100000000002700000000000000c90000001c000000000000000000000000000000000000000100000000000000920000000000000006150100000000002700000000000000d10000002e00000006150100000000002700000000000000db0000002e00000006150100000000002700000000000000ee0000002700000006150100000000002700000000000000f3000000260000000000000000000000100000000000000008000000000000005201000000000000ae1501000000000024000000000000000400000014000000f5150100000000004500000000000000d2150100000000002300000000000000560000000d0000003a160100000000001e000000000000009a0100000c0000003a160100000000001e000000000000009d0100000c0000003a160100000000001e000000000000000002000001000000f4170100000000000100000000000000f4170100000000000100000000000000f5170100000000000200000000000000f1170100000000000300000000000000f5170100000000000200000000000000f7170100000000000100000000000000f8170100000000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000001000000000000000000c000008000000f969000080be02c0db5c02003800ad01da03100452048404aa04de0460067d0b280c7a0c640ed90e100f54102711c1128b13381492142616681608172d19d019431b531b631bbf1c301e521e6f1e8a1eb11ee01e571f861fa71fbf1f3e20602074209920a920c120f120fe200c211e213621462165219021ad211c223c224b225b22f72204231223fb24562565256d257a2508265a268b26c326da26ed26fa2625274b27522779271a283f284628af28d52821296829e429f82ab82b072c292c6b2c982cae2cd82cf12c492d9c30b630e4305c31db316932c132d13210332a33503407357f376238a538c838dc38723a783a863a8c3a923adf3b7f3cdf3e053f1f3f363f453f603f753f8c3fa53fee410643604366436b4384439b43ab43c543fb4302440944424449441a45b145e94544466347e147f147fa471148b6485249ea49754aa64a0a4b424b534b814bd94bf34b474c594cc44c844dda4d074ee14e6b4f844fa34f5b506e50af505451f35116529852be52125328533453dd532c546b54b5546e56280f827a08827910648764a828b52d951170fa7b1088057b1580057b1678057b18f8017b17f00149216003951720029518600350100400438218200214070000000000000080ab780628242382173002551708072820228479f85219080728162252191007280f228479fe52191807280522957ae6551a080728fb21957ade330b22aeba0628ef215219440728e8219579ba5519120728de219579a85519060728d4219579a25519100728ca219579925519040728c02195798e55190c0728b6219577825517080728ac21821728027c7c187c76197c791e7b19d8017c791f7b19d0017c79207b19c8017c79217b19c0017c796b7c7a6a7c72427c7b6c8a9908d4a9097c74438aba107c73447c70457c7b567b1be8017c7b577b1be0017c7b5c7b1b98017c7b5d7b1b90017c7b6d7c756e7b1568017c756f7b1560017c75707b1558017c75717b1550017c757a7b1588017c757b7b1580017c757c7b1578017c757d7b1570017c7582007b15b8017c7583007b15b0017c7584007b15a8017c7585007b15a0018abb18d4ba0ad49a055108357b1248017b16300164367b1c40017b1438017b10280150100624578210280182143801821c4001646382163001821248010184570151070628ab2152050628af2133070810c3570751070628aa218217e0019777088218e801d4780852080628a0218217d0018a77088219c8018a9910821ac0018aaa18821bd801d4b707d4a909d49707c38708510806287d218218b0018a88088219a8018a9910821aa0018aaa18821bb801d4b808d4a909d49808ac7806285f218217900197770882189801d487079678abaa6e885428555508284d21976808d4c808978901c898086e88aa8706284321821780018a7708821878018a8810821970018a9918821a8801d4a707d49808d487075127800008282321390864270353280001082820218215600197550882195801979910821a500197aa18974b08970c0882186801d45808d4a909d42b0ad43c0bd498083d0a6427037b1748013e0b5827038217f8019777209877207b19a0003d086027035207062814201408ffffffff000000008219f001d298087c89510958330aff00958801aba9535517090728ed1f7c87017c897c8a027c8b03977708d4970797aa1097bb18d4ba0ad4a7077c89057c8a047c8b067c8807979908d4a90997bb10978818d4b808d49808978820d487072886003307288a006c9657060b330549212002285184694033050151093c84692033050251093384691033050351092a84690833050451092184690433050551091884690233050651090f846901330507510906285a209577ff49212002ae570628481f951720026459501008c3578557079b77ff82182002e07607975503cf5707c8870701987810510806281d1f791706024921300308951760039518300350100a283f8218600314070000000000000080ab780628d41f7b18f801821770035517200728fd1e821568039577e09558207b1820057b172805951720029518200550100cb3408216200214070000000000000080ab760628ca1e82173002821828027b1708057b1800058217f80151070a645750100e4d5482170005821808057b1710027b1818027b1608024921a003079517b8039518a0035010108e3e8216b80314050000000000000080ab560628421f8218c0038217c8037b18087b18d0037b17d803951720029518d003501012274082192002ab590628761d8218d803821728027b17785518040728591d7b1988009587fc5517200728461d7b16688217d0037c797b19607c79017b19487c79027b19587c79037b19509588dc9579247b19d0037b18d8037c78057c79047c7a067c7b07978808d4980897aa1097bb18d4ba0ad4a8087c79097c7a087c7b0a7c7c0b979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1820027c780d7c790c7c7a0e7c7b0f978808d4980897aa1097bb18d4ba0ad4a8087c79117c7a107c7b127c7c13979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1828027c78157c79147c7a167c7b17978808d4980897aa1097bb187c7919d4ba0ad4a8087c7a189799087c7b1a7c7c1bd4a909821a30027b1a4097bb1097cc18d4cb0bd4b909979920d498087b1830027c781d7c791c7c7a1e7c7b1f978808d4980897aa1097bb18d4ba0ad4a8087c79217c7a207c7b227c7723979908d4a90997bb10977718d4b707d49707977720d487077b173802951760039518d003501014a53e82186003140700000000000000807b188000ab780628d61b8217200282182802821968037b19707b17e0037b18e80382183002821a38028217d803821970037b19387b18f0033302207b1af803ae2706288c1b8218d003957be07c8a017c8c7c86027c850397aa08d4ca0a976610975518d46505d45a0a7c8c057c86047c85067c890797cc08d46c0c975510979918d45909d4c909979920d4a9097b1900057c89097c8a087c8c0a7c860b979908d4a90997cc10976618d46c0cd4c9097c8a0d7c8c0c7c860e7c850f97aa08d4ca0a976610975518d46505d45a0a97aa20d4a9097b1908057c89117c8a107c8c127c8613979908d4a90997cc10976618d46c0cd4c9097c8a157c8c147c86167c851797aa08d4ca0a976610975518d46505d45a0a97aa20d4a9097b1910057c89197c8a187c8c1a7c861b979908d4a90997cc10976618d46c0c7c861d7c851c958a20d4c909976608d465057c8c1e7c861f7b1ad0037b1bd80397cc10976618d46c0cd45c0c97cc20d4c9097b191805551b2007285c1a7ca9197cab187cac1a7ca61b979908d4b90997cc10976618d46c0cd4c9097cab1d7cac1c7ca61e7ca51f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b1938057ca9117cab107cac127ca613979908d4b90997cc10976618d46c0cd4c9097cab157cac147ca6167ca51797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b1930057ca9097cab087cac0a7ca60b979908d4b90997cc10976618d46c0cd4c9097cab0d7cac0c7ca60e7ca50f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b1928057ca9017cab7cac027ca603979908d4b90997cc10976618d46c0cd4c9097cab057cac047ca6067caa0797bb08d4cb0b97661097aa18d46a0ad4ba0a97aa20d49a0a8479e07b1a200552194007283b197c8a597c8b587c8c5a7c865b97aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b5d7c8c5c7c865e7c855f97bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a48037c8a517c8b507c8c527c865397aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b557c8c547c86567c855797bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a40037c8a497c8b487c8c4a7c864b97aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b4d7c8c4c7c864e7c854f97bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a38037c8a417c8b407c8c427c864397aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b457c8c447c86467c854797bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a300352196007281c187c89797c8a787c8b7a7c8c7b979908d4a90997bb1097cc18d4cb0bd4b9097c8a7d7c8b7c7c8c7e7c867f97aa08d4ba0a97cc10976618d46c0cd4ca0a97aa20d4a9097b1978037c89717c8a707c8b727c8c73979908d4a90997bb1097cc18d4cb0bd4b9097c8a757c8b747c8c767c867797aa08d4ba0a97cc10976618d46c0cd4ca0a97aa20d4a9097b1970037c89697c8a687c8b6a7c8c6b979908d4a90997bb1097cc18d4cb0bd4b9097c8a6d7c8b6c7c8c6e7c866f97aa08d4ba0a97cc10976618d46c0cd4ca0a97aa20d4a9097b1968037c89617c8a607c8b627c8c63979908d4a90997bb1097cc18d4cb0bd4b9097c8a657c8b647c8c667c866797aa08d4ba0a97cc10976618d46c0cd4ca0a97aa20d4a909847afc330b80007b196003abba0628f7167c8980007b19307c8981007b19187c8982007b19287c8983007b192095777cff958884007b18d0037b17d8039517d003501016f43a837751070628ba168387492140053309017b19480549215005510758033303978820988820821bd8038216d0034921680101280d82185001c9c8085108cb02821a400533070002e6780cc93a077b1850017b1c6001aec77dc8c3057b13b801ae350628ae1698583b330751080628ad1697590559090628b6167b1bf801510a1797aa05821768017b1720027b1a300233070128053307017b17280295175805330801951a20025010188d3b82175805510706286a16821760057b1768017b1748057b1540058213b801821bf801821c6001019735059ac77b175801330201330701284d956620821768018215f001c857079518200233092050101a234f8213b8019533017b135005821c60018212e801d8c20795552095220182185801c82808821bf80195bbe0330901aa980aff330820ac8bc1017b1bf8017c67117c68107c69127b15f0017c6a13977708d4870797991097aa18d4a909d497077c68157c69147c6a167c6b17978808d4980897aa1097bb18d4ba0ad4a808978820d478047c671f7b17e0017c671e7b17d8017c671c7b17d0017c671d7b17c8017c671b7b17c0017c671a7b17b8017c67187b17b0017c67197b17a8017c67077b17a0017c67067b1798017c67047b1790017c67057b1788017c67037b1780017c67027b1778017c677b1770017c60017b12e8017c620f7c650e7c6c0c7c6b0d7c6a0b7c67097c68087c690a7b143002977708d4870797991097aa18d4a909d4970797bb08d4cb0b975510972218d45208643cd4b808978820d487077b17280297030882177001d473078218780197881082198001979918d49808d487078218880197880882199001d4980882199801979910821aa00197aa18d4a909d49808978820d487077b1720028217a8019777088218b001d487078218b8019788108219c001979918d49808d487078218c8019788088219d001d498088219d801979910821ae00197aa18d4a909821a4005d49808978820d487077b1738027b1cb801abac09fe9517400550101cbc3b821748057b17680128f4fd84770151070628b71382185001c9c80852083dfd7b13b801821840057b1bd803140700000000000000807b16d003ab7806289c138216b8015106557b18f8019766059566e0140700000000000000807b17900082156801015106619558203309206457648550101ee84d83789566e05708e98218f8014911100152081e2824492190004921b801491110012851492190004911100151080c82176801501020c1494921b8011407000000000000008082189000ab7829281713821768017b17108217f8017b1790001407000000000000008082189000ab780628f412821718058218100582190805821a00057b17f8047b18f0047b19e8047b1ae004821720058218280582193005821a38057b17c0047b18c8047b19d0047b1ad804821730038218380382194003821a48037b17a0047b18a8047b19b0047b1ab804821778038218700382196803821a60037b1798047b1890047b1988047b1a80048217e0048218e8048219f004821af8047b1760047b1868047b1970047b1a78048217d8048218d0048219c804821ac0047b1758047b1850047b1948047b1a40048217b8048218b0048219a804821aa0047b1738047b1830047b1928047b1a2004821798048218900482198804821a80047b1718047b1810047b1908047b1a00049517d0035010221d36837751070628c31139075827038388ae870628b6114921e0049517e8044921e804084921f0045108350b1407f8ffffffffffff7f7b179800140700000000000000807b1770011407fcffffffffffff7f7b1798011407feffffffffffff7f7b17a001018217f004821ae0047b18b80033098000e69809c97a087b199001ae987382159001c87505ae750628d311985839330751080628d211975907821b9800ae9b0628d711510a178217e80497aa077b1720027b1a300233070828053307017b17280295176003330808951a20025010248e3682176003510706288f11821768037b17e8047b15e004330c3308012808330c330801017b1878018218d803531804110a8219d0037c977b1768017c97017b1750017c97027b1760017c97037b175801958afc9597047b17d0037b1ad803330b20acbadc097b1c48019588dc9599247b19d0037b18d8037c78017c797c7a027c7b03978808d4980897aa1097bb18d4ba0ad4a8087c79057c7a047c7b067c7c07979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1860037c78097c79087c7a0a7c7b0b978808d4980897aa1097bb18d4ba0ad4a8087c790d7c7a0c7c7b0e7c7c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1868037c78117c79107c7a127c7b13978808d4980897aa1097bb18d4ba0ad4a8087c79157c7a147c7b167c7c17979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1870037c78197c79187c7a1a7c7b1b978808d4980897aa1097bb18d4ba0ad4a8087c791d7c7a1c7c7b1e7c771f979908d4a90997bb10977718d4b707d49707977720d487077b177803951720029518d00350102644328218200282177001aa789a087b1888018217d803821828027b188001330808ac8773088218d003821930027b1940017c897b1920017c89017b1900017c89027b1910017c89037b1908017c89047b1938017c89057b1918017c89067b1930017c89077b1928019589087b19d0039579f87b19d803330a08aca91b087c89087b19e0007c89097b19c0007c890a7b19d0007c890b7b19c8007c890c7b19f8007c890d7b19d8007c890e7b19f0007c890f7b19e8009577f09588107b18d0037b17d8039517d003501028e63283775207c60739096027038387ac79bb074921000549210805024921100551078c0233029788209888204921e80102330c242817848801520860078218c801c96808330c2451087202821a00053307c701e67806c92a077b18c8017b16e001ae676dc86205ac25d80dd6c50833075208d90d965924821ba001ac9bce0d6426510a1796a7248218e8017b1820027b17300233070228053307017b17280295172005330802951a200250102a7d338217200552077c0d821728057b17e8017b1708057b15000564628216e001019625249a677b17d801330c013308012856974708d4670898770f9788319888318219e801c8590979987897029597039518200233092050102c0b478212f8019522017b1210058216e001821cf001d86c0895cc018217d801c8c707955524330901aa970eff8219d803531920fffe8217d003959ae0957b207b1bd0037b1ad803330b02acbae5fe7c76209598de9579227b19d0037b18d8037c78017c797c7a027c7b03978808d4980897aa1097bb18d4ba0ad4a8087c79057c7a047c7b067b1cf0017c7c07979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1820027c78097c79087c7a0a7c7b0b978808d4980897aa1097bb18d4ba0ad4a8087c790d7c7a0c7c7b0e7c7c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1828027c78117c79107c7a127c7b13978808d4980897aa1097bb187c7915d4ba0ad4a8087c7a149799087c7b167c7c17d4a9097c742197bb1097cc18d4cb0bd4b909979920d498087b1830027c78197c79187c7a1a7c7b1b978808d4980897aa1097bb18d4ba0ad4a8087c791d7c7a1c7c7b1e7c771f979908d4a90997bb10977718d4b707821a0005d49707977720d487077b1738027b12f801aba253fe951700057b14d00150102e2a328214d001821708057b17e8012836fe4921b0004921f801330702281b8218000582177001aa7809057b18b0007b12f80182170805017b17a8009517d00350103009308377330c245207d6048387492100053309047b190805492110055107c2023304978820988820330204281784880152089d048218b001c93808330c245108b402821b00053307c701e67803c94b077b18b001ae3769c83405ac452a0bd6c508330752082b0b965924821a9801ac9a200b7b13a8016446510b1396b7247b1220027b17300233070428053307017b17280295172005330804951a2002501032ad30821720055207ce0a821228057b1208057b15000564648213a801019645249a363300013308017b16c001287b81173c02c852087a871c811738027a8718811734027a8714811730027a871081172c027a870c811728027a8708811724028219e001979908821af001d4a909821ae80197aa107a870481172002821bd80197bb18d4ba0ad4a9097a877a89209544017b141005d83008950001c80607955524330901aa97f1fe8219d803531920e2fe8217d003959be0957c207b1cd0037b1bd803330a04acabc8fe7c78207b18f0017c78217b18e0017c78227b18e8019598dc9579247b19d0037b18d8037c78017c797c7b027c7c03978808d4980897bb1097cc18d4cb0bd4b8087c79057c7b047c7c067c7a07979908d4b90997cc1097aa18d4ca0ad4a909979920d498087b1820027c78097c79087c7a0a7c7b0b978808d4980897aa1097bb18d4ba0ad4a8087c790d7c7a0c7c7b0e7c7c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1828027c78117c79107c7a127c7b13978808d4980897aa1097bb187c7915d4ba0ad4a8087c7a149799087c7b167c7c17d4a9097c7a237b1ad80197bb1097cc18d4cb0bd4b909979920d498087b1830027c78197c79187c7a1a7c7b1b978808d4980897aa1097bb18d4ba0ad4a8087c791d7c7a1c7c7b1e7c771f979908d4a90997bb10977718d4b707821a0005d49707977720d487077b173802aba420fe951700057b14c8017b15d00164356406501034bf2f646064538215d0018214c8018216c0018212080528f3fd8217d803330802ac87f80133053304330004281d8215000582177001aa75e3018217d80382100805330802ac8710028218d0037c897b19f001958902821a60037b19d0039577fe7b17d8037b1a30038217680382197003821a78038216f004821be0047c88017b18e8017b1738037b1940037b1a4803abb6219517e0047b10e0017b15d8016445501036b82f64548215d8018210e001018217500197770882186801d487078218600197881082195801979918d49808d478028218000197880882172001d4780882191001979910821a080197aa18d4a909d498088219180197990882173801d47909821a300197aa10821b280197bb18d4ba0ad4a909979920d489038219c0009799088217e000d47909821ad00097aa10821bc80097bb18d4ba0ad4a909821ad80097aa088217f800d47a0a821bf00097bb10821ce80097cc18d4cb0bd4ba0a97aa20d4a909821be804821ae80197aa088217f001d47a0a976c07c8cb0b821788017bb7821780017bb708821740017bb7108217b0007bb7188217a8007bb7208217f8017bb7287bb5307bb0387bb440821c30038215380382174003821848037bbc487bb5507bb7587bb8607bb3687bb9707ab27879ba7c821c480195cc019566017b16f00482179001d87c08ab7c32f68218b80028528217000551072d8217080533003a289c3d8217000551070c821708055010388e3d8217b00051070c8217a80050103a7e3d8217880151070c8217800150103c6e3d821778018477018218b80052078e0682179001c9780852083ef5280d5105c4640733003828453d9518e8048287821ce0048288087b17c004140700000000000000807b18c804ab7c1828700664788277088288330c7b17c8047b18c00401821748977708821860d48707821858978810821950979918d49808d47806821718977708821830d48707821828978810821920979918821ac804821bc004d49808d478057b1aa8047b1ba0048217e0038218e8038219f003821af8037b1760037b1868037b1970037b1a7803821760048218680482197004821a78047b1730037b1838037b1940037b1a4803821758048218500482194804821a40047b1738057b1830057b1928057b1a2005821720048218280482193004821a38047b1700057b1808057b1910057b1a1805821700048218080482191004821a18047b17e0047b18e8047b19f0047b1af8048217685107148217087b1cf80150103e123c821cf80101821788007b1720028217787b1728028217407b173002821780007b1738028217707b1740028217387b174802821760038218680382197003821a78037b1750027b1858027b1960027b1a6802821790007b1770028217107b1778028217b8017b178002821748038218400382193803821a30037b17a0027b1898027b1990027b1a8802821720058218280582193005821a38057b17a8027b18b0027b19b8027b1ac002821700058218080582191005821a18057b17c8027b18d0027b19d8027b1ae0028217f8048218f0048219e804821ae0047b1700037b18f8027b19f0027b1ae8028217a0048218a8047a1508037b1c10037b1718037b1820037a1628030a7b172005951706027b1760033307dc007b176803951720057b177003330706017b177803951708027b178003330772017b178803951820027b1890037b17980333075018017b173003492138030549215003951760037b174003492148030495170005951830035010407b0782150805821b10058216000533070233083309645a0a640151060a64575010427f3a8215300282171802ab75ec04821628028218100264676459501044523e83775207d5047b16e0047b15e804951760039518e004501046f8218216600351065601821778038219680351075001570573055105ce027b19f80164585010482c3651075c058219f8017b1530037b17380349214003ac95be023308017b18f8017b17f001c887076468649650104abd3c8218f801c86808c985057b184003541502b3028217f00101c88707461702bd00461701bf004607ef8217e0048219e8049585037b1540037b1720057b192805282b82173003c9570754170289008217f00101c85707461702bd00461701bf004607ef9555037b15400301951760039518200550104c2b218218600351086f8217300382166803821978037b19f801c95707ac6725821738037b17f001c85707646950104e173cc865057b1540038217f80152079028b6951730037b18f0016458646950105015058218f0018215400328c5951730033309036458501052fd048215400382173803286bff8217300382183803821940037b1720057b1828057b193005281f3309330601017b1628057b193005140700000000000000807b17200501951720057b17e0043307027b17e8043307a018017b1760034921680302492180039517e0047b1770033306017b16780395173003951860035010546d05821540035705c303821730037b17e801821738037b17f80151051164585010567d345107ad0364760164678218f8016459501058293b8217200514080000000000000080d48707ab87d7008217200251070c8217280250105a38388217380251070c8217400250105c28387b16d8017b15e0018217700251070c8217780250105e103882162003821718037b17f0015106418217f001957520280c9566ff9555800051062e8257e051070b8257e8501060e0378257f851070a8257501062d3378257105107d8825718501064c53728cd8217100351070c8217f001501066b337821708028215e0018216d80151070c821710025010689b378217e80151070c8217f80150106a8b37646764588210880582158005821678059511900532008217280550106c6c3782172002520727ff282cff3307017b1530037b17380349214003ae954afd951730033308649550106e2c036459821840038217380382153003282efd951730033309035010700f0382184003821738032843fd82172805821830053309a01c0128c80233073309a01c0128be023309a01c0128b60282172805821830053309a01c0128a60233073309a01c01289c023309a01c012894028216f0048217e8047b17f8015206cd008217e00451070c8217f801501072b5368217900051071c82171033007428a3368217400551070c8217680150107495368217800051070b8217705010768636821988000151090b821778501078763633072d1501330ae81c01330b081d013308239519770528881433072d1501330ae81c01330b081d0133082395197705286f1433073309a01c0128f901281282176005821868053309a01c0128e7013309a01c0128df0133073309a01c0128d501281282176803821870033309a01c0128c3013309a01c0128bb018217f801957520280d9566ff95558000510627ff8257e051070b8257e850107ada358257f851070a825750107ccd358257105107d782571850107ebf3528cc3307780101330a001801330b20180133082b9519770528cf1333072d1501330ae81c01330b081d013308239519770528b6133307c018017b1760034921680301492180034921700308492178033308d018019517600328ea053307201d0128f8143307d4170133081d28573307c81d0128901f330798170133083c28453307e01d01287e1f33076317013308352833330730170133083328293307fe1601330832281f3307d1160133082d281533079a1601330837280b3307581601330842017b1720027b18280233078f1501330a681d01330b881d0133081f951920022807133307501d012870143307381d012868143307381801288e006457281233077819012882003307701c0128150d9511b87b1764177b17383307ae007b174033074019017b1708491110024911289517387b1718491120019517089511b88278288279207b18287b1920827818827910827a0882777b18187b19107b1a087b1764177b173033076019017b17384711409517303300e000284a26520707649728066487288b9511d0647833071819017b174911080149112049111008491118641728a0049511b87b10407b15387b1630c88909ac895064758277977801330a08e4a909e4980657063d5107108258087b18187b1728330701017b172064179519186468502080003882175207238217087b57087b568210408215388216309511483200330733092819012870ff82170882181033092819012862ff9511d07b10287b15207b1618829a087b1710510a3282971051072c7b1782957b180850208200882f5107496476645882195020840039366457502086006533821508280d648550208800642f647601886701330801db66088219107b98087b95107b978210288215208216189511303200330682150828d99511a87b10507b15487b1640648a828608828b7b171851065a330595b7086468018279c895059588ff9577105208f582a91851091957054582b708887701885810d28707520736975501015705dc005105327b197b1b087b1a10645850208a00d62e5107cd00821a10821b088219281782a9183305330701280d33053307012806330701017b15207b17284911305116011352061252090f33063305330801283b51092e3308e8180195172064a950208c00120b5107383307780401330a901901330bb0190133085695193f28871082b60882b8ac653a330501c85707646950208e001335c865057b153001821730821828821920821a187ba7107ba8087ba9821050821548821640951158320095172064853308646950209000fafd645882153082172828b5330778190128c6fd6457284afd828928828720829c183308a11401330905320c827851080982770828d73132009511e07b10187b15107b16086475827a108277c9a7076496ac972b7b1a825708c8a70764695020980079348217c876067b56103307821018821510821608951120320064577b1864a8646950209a0072fd8218825a1028c59511d87b10207b15187b1610330980006475ae98268256108257ab761064577b1850209e006901821801825708c8670778789566012899008b870b48110c52071595170d9889068699c00078190c33090228528b871052072295170e98890c8699e00078190c97893498993a9599800078190d330903282d95170f9889128699f00078190c97892e98993a9599800078190d97893498993a9599800078190e33090401825610825a84883f86888000c96a0a7878ac9a2d7b19825708c8670795180c5020a00084338217c87606017b5610330782102082151882161095112832006457646864965020a2007efc646982561028c59511e07b10187b15107b16086489828a08828818330b01aaba13520a13520810827a103306330501282b5108173308e8180182101882151082160895112028e2088298827a1082798286088285c9a909ac69317b17827808c8a80764586469646564a65020a600ef32c8560682177b7610330782101882151082160895112032007b17821764a864695020a800e8fb8217827a1028bf9511b87b10407b15387b16306475827795780151084d977901e48908330908e4980657063e5107108258087b18187b1728330701017b1720641795191864685020aa0013fc82175207238217087b57087b56821040821538821630951148320033073309c81901284afb8217088218103309c81901283cfb9511e87b177b18084711100164175020ac007221009511a07b177b180864177b17403307ae007b17489518087b18507b17583307981a017b1710491118024911309518407b182049112802951710649828b09511a07b177b18089517087b17403307ae007b174864187b18507b17583307101a017b1710491118024911309518407b18204911280295171064982873ff8277019511b87b10407b15387b16307b18987904330814330a6e0601532971027f330814951b2e14094b598638d6c56d347b1910491108102733037b143300ffe0f505016472821710d6720798770b821908ca9709bf9206976930989932ca39099894119899108499fe07964c64bfc606976631986630c8a9097c9c7c9901c8a6067c65017c6678b9ff78bcfe78b50178b69588fc95bbfcac20ac5417633697793098993296997b14989911969b64bfb7079777319877309588fec8a7077c7b017c77951c1cc88c0c78cb0178c764970155170a149588ff95191cc889098677307897281c9777019588fec8a7077c79017c77951a1cc88a0a78a90178a701951a1cc88a0a9a8b14330801821733095020b0001382104082153882163095114832009511987b10607b15587b16506473807034649c7b184884080133022b3309000011840704c8b80552070849114828376457510c23330664c4821948c8c905649c017dc98999c085990195cc01c89606ab5cf2280764c4330601c87605644c3309000011018237db82097b1b307b1a2851072e823608ae65288407085207577c3838330201c956077b1c207b1918ad829b005208a9007b1710648728a40082352082362864576468821a4864cb5020b2006e013308015207b700826c186457821828821930821060821558821650951168320c7b16388137307b17107c38387b1808823720823828481330307b13461338017b17406486821a4864cb5020b4001f01520767821738c95705955501019555ff5105c900826920330830821740b49002b6005107eb2842521802129578019877019888017b18102806491110018238207b18408235288138307b1838957601019566ff510628825920821740821838b49002b8005107ec330801016487821060821558821650951168320082164064676458821918821a48821b205020ba008e003308015207d7825a186467821828821930b4a002bc003308015207c18217109a777b17483306ff01821748c867075117ff45825920821740821838b49002be009566015107e52839826a18821740821828821930b4a002c00033080152077eff330882168217107a6730821708786738286aff821610821710d87608285eff821710d876082855ff9511d87b10207b15187b161064a6647c5139000011286485828a2064c764987b1b087b1cb4a002c200821c6458821b08647933070152092051061a828a1864c7646864b9821020821518821610951128320a33070182102082151882161095112832009511a07b177b180864177b17403307ae007b17489518087b18507b17583307b81a017b1710491118024911309518407b18204911280295171064982896fb9511a87b10507b15487b1640647c827a827b106486520a0a84b80151081e0384bb01510b910064c082cb186494c869093308510b46330c3303e0003302f0006467646528149556010195cc01c98505c956086465aacb27aa95557d565906e98466ff00ac360bac260d95560428dd95560228d895560328d3646701aa96317d6b64496476640c590b0c84b7ff00330be0000151089d02ae989502c886077d77330bc0afb78d02520a1128900264496476640c01510a860282c3087b19187b161055192027330251091ac89608646a017da78977c085770195aa01c87202ab8af2ac32aa01285502646a9565078455f8c89606c956068468073309aaa5147dab89bbc085bb0195aa01c8b909ab5af27b1c087b13330c510821846bf8c8b50ac85b0bc8b808017dab89bbc085bb0195aa01c8bc0cab8af2986303140a0101010101010101330bff00ff001408ff00ff00ff00ff007b182814000000000001000100140801000100010001007b1820c89c022830821530c89505821638c96303846c03821b28d2b407984808d2b808c88707821820ca8707987730c87202520c97005103820064593307c000e673077b17389778037b18303304531304ba8485e007c8950564980182808507ff987707980b06828c08d4b707d2a707c8470785cbff98bb0782861098cc06d4cb0bd2ab0b856cff98cc07986606d46c0c828618d2ac0cc8cb0bc8b707856bff98bb07986606d46b0bd2ab0b958820c87b04ab58aa2853ff821918821610821c088213ac325d28090133088467fc00977703c8790397cc03821918821610018237953308857bff98bb07987706d4b707d2a70795ccf8c87808520ce6821a28d2a807988808d2a808c88707821820ca8707987730c87202821c088213ae32b3007cca38c923085a1a010c510a1a491120281a521a02129587019888019877017b172028087b182033080182c7207b173882c52881c7307b1730958601019566ff51061a825920821738821830b49002c4005107ec330701288300825a18821738821810821918b4a002c600647833070152086a8217209a777b17283306ff01821728c867073308ffaa8740825920821738821830b49002c8009566015107e32834ab987bfddb6809db6606520a82fd82c82882c720828c186468821050821548821640951158320c821620821720d876072809821720d876070182105082154882164095115832009511c0647833078905017b17304911382b9517307b174911080149112049111008491118641728fbf78279088277829c18320c9511807b10787b15707b166849115820330a03781a608296204911284911387b17487b18505106f70082972851075b0182957b19088298107b18109578ff9788039888039588017b18955908977803977706c98705956618017b1920829951091b821a508217488218208288f882aa18b4a002cc00520738017b15188267107b17587c69188268f8826778196051082152180112977704821810c8870782785108073308280e8277083308012806330801018269e87b18287b1730511902198268f05219011e978704821810c8870782785108073309280e827808330901280633090101826708977704821a10c87a0a82a782aa087b19387b1840951828b4a002ce008215185207a7008219209599109555c8956638520543ff2860829718510768829510977804c858087b18207b190882969577ff9777049877049577017b17956608018269510917821a508217488268f882aa18b4a002d0005207528257825908951828b49002d200520742955510956610821720ab75ce821908821a829708ac7a0d282d330a829708540726829797a804c88709821a50821748829882990882aa18b4a002d40051070833070128053307018210788215708216689511800032009511c07b10387b15307b162880707b18980b0433080a33096e0601532b7102da0033080a951b261402000000005917b7d17b1210491108102733047b143303ffe0f50533066401970c20821710d67c0c98cc2d821708ca7c05bf500a97a530985532ca45059857119855108455fe07ca6707bf7a0a97aa3198aa30c895057c57017c55c89a0a7ca20178b7ff78b5fe7ca778b201830a9588fc78b795bbfc64c0aca3a7541c635897c730987732967a7b1498aa1196a764bf7c0c97cc3198cc309588fec89c0c7cc7017ccb951c1ec88c0c78c70178cb531a0a2b97aa019588fec8a9097c97017c99951a1ec88a0a78a70178a9281d640c561063b083ca551a0add9588ff95171ec8870786a930787901951a1ec88a0a9a8b0a330801821733095020d800bcf68210388215308216289511403200951178ff7b108000808934849a10520a1484992052094a8210800095118800286af5330a810095197f0164ab987c0484770f88720a867a30957757db2a0778979599ff95baff64c7520ce295b7fe56278000659aab8100641ac87a0a283d330a810095197f0164ab987c0484770f88720a867a30957737db2a0778979599ff95baff64c7520ce295b7fe56278000359aab8100641ac87a0a0133090264873308f517015020da0005f6821080009511880032003309501a0133088000284ff43309501a01330880002843f49511d87b10207b15187b16107b1751098501330a330b0fe4b90b95bbf1958c0784ccf8c98c079584089a93330c7d08017b1c08280995aa01ae9a5b01c8a8057d5557054ebfa7058455075205ebaeba26c8a8058255c8a4068266140c8080808080808080d46505d25c0c520c0995aa10acbae0ae9aa2009aa5c88a0a017dac570c93009555ff95aa01ab53f42807018455ff00821c08c85c0c7cc6330c04aac650330c03aac626330201330c02abc6f60095a001ae90eb00c8080c7dcc3305013306bfafc6c90028ea0095a601ae96d200330201c886067c665125e000426c605225ed0052330c9faf0c6528b80095a601ae96ae00c886067c663302015125f0002b6c665225f4005a330c8faf6c602894009a5a01ac9a27ff287a846ce0003305a000aa5c2a287d956c70ff531c30402873955c1fff33060cac6c0e845cfe003305ee00ab5c5f330cc0afc05995a002ae904fc8080c7dcc581cbf34284c955c0fff561c0241330cc0afc63a95a502ae9530c885057d5c3305bfadc52e95a003ae901fc8080c7dcc5a1cbf25950a01ac9aadfe330a82177b78087b7910282333022810330501280b33050228063305030182177b7a08787210787511330a01017b7a82102082151882161095112832009511907b177b18087b19107b1a1864177b1750330770017b17589517107b17603307ca007b17683307301a017b1720491128024911409518507b18304911380295172064b82816f29511a07b177b180864177b17403307ae007b17489518087b18507b17583307d81a017b1710491118024911309518407b182049112802951710649828d8f19511f07b10087e79989a046487532a71025b96a84703988813968a1027bfa909979a3098aa3296aa7b1498ab1198aa1084aafe0796bb64bfb909979931989930330b6e0601c8ba0a7cac7caa01c8b9097c9b017c99781a05781c04781b07781906330a016489283c330a0533086e06015319642e989a0296aa7b1498ac1196ca64bfa909979931989930c889097c9a017c99781a07781906330a0364c90155190a1495abff951803c8b8088699307889281f97993198993095abfec898087c89017c88951a03c8ba0a78a90178a801951a03c8ba0a9abb0533080133095020de00aaf282100895111032009511b0647833075015017b174911081b64177b1740330770017b174833076805017b1710491118014911309519407b19204911280195171028b2f0009511c87b10307b15287b16205618271d978a02330b000001c8ba0a81aac8ba0a320a33085c3028d201330a5caba80a33085c5c28c501330a0003aca804018499015109fd0088892f1801330a11949a95ac0897cb023309cc1001c89b0b81b68a8b0b8a660bd86b06da6c0a95a604976c02c89c0c81cc8acc0bd8cb0cdac60a95a602976c02c89c0c81cc8acc0bd8cb0cdac60a95a601976c02c89c0c81cc8acc0bd8cb0cdac60a95a601976c02c89c0c81cc8acc0bd8cb0cdac60a97ac02c89c0c81cc8acc0bd8bc06d3cb0b88bb01c86a0ac8ba0a330c21acacf40297ab02c89b0b80b99896153302ef02511a210d80bc0498c215510a0c80bafc84aaffff1f01856bffc82b0b510b30330cbfa80b330aef02e4a6039522ff330a54110101aa639f02c86a097c99be9c0caccb0c956601ab62ee64260184660152060a01551820db008b8914330a780501c8a9097c99868b0178191797892c98993cc8a9097c9c97893098993cc8a9097c9669b9989902781c18781619978b3498bb3cc8ab0b978c3898cc3cc8ac0c84880fc88a0a9598fe4611167cbb7ccc7caa471114781b1a781c1b781a1c46111d7d951a14c88a0b330c5c78bcc8a9094619ff7546097b811914811a187f1b1c28020133085c6e2828849900015109dc0133085c27281a33085c72281433085c74280e97992f5909c30133085c22017a784717084717064717043308000279780a01821030821528821620951138320053187f99018b89105209be00330629016e823303730d01330ac30d01330ce50e01330928330b220128c1008b8914330a780501c8a9097c99868b0178190b97892c98993cc8a9097c9c97893098993cc8a9097c9669b9989902781c0c78160d978b3498bb3cc8ab0b978c3898cc3cc8ac0c84880fc88a0a9598fe46110a7cbb7ccc7caa471108781b0e781c0f781a104611117d951a08c88a0b330c5c78bcc8a9094619ff7546097b811908811a0c7f1b10017a797a7a04797b0878780a33080a78780b282fff8b89115209633306e6016e823303650a01330abd0a01330c8d0b0133092c330bd000017b1664756427648664383300f0009511d07b10287b15207b16187b1c083303821c307b1c987008979901c8980c8476ff007b1a1028ef03646864796457510904fe286f848afeff1f513a1eb802f9fd8489e0ff1f5139e0a602edfd8389c048fd5619f9e1fd83895031fd5619f1d7fd838a1014fd330bf0acabcafd83890008fd56295df6c1fd83890000fd56291dfab6fd8389b0ecfc5619faabfd838900fff1330aaf22f5ac9a9cfd5538f0010e98fd3309800078797a78042850fe3309181c013308ef0264372837ed3309001c0133082264a7282aed00951130ff7b197b1a08532801014f7d7300013302bf330c03ad32197d73ff00330c025a13bf0f7d7cfe0089ccc085cc010195c3fd00c8370c7dcc581cbfcd007b17107b1318330205330c7e09017b1c207b1228ae9817285b7b17107b1818330c017b1c20491128ac984aaca847ae9aa50064177b1780003307ae007b1788009518087b1890007b1798009517107b17a000330770017b17a8009518207b18b0007b17b8003307f81a017b17503307042840d89807db790a7b1a409517407b1780003307ae007b1788009517107b179000330770017b1798009518207b18a0007b17a8003307881b017b1750330703017b1758491170951880007b18607b176895175064b828d0eb3309643a3300f20028f1fe510912ae890fc8970c7dcc89ccc0dbc90a017b1a306489ae8a3a330903e49a029522fd95ac01ae2c0f3309b81b01642764c8287df9c92c09c87a0a3303c00151090e7dac9599ff95aaffad3cf5c8290901c8970a52090bab891364b72845f3ae891c7da25a12bff328167da88487ff005708177a173c3308012876aa89dc648a3300f200286afe7cac0184781f84cc3f5427df00337ca20297c306842c3fd4c30c5327f0002a7ca70397883d98882b97cc0684773fd4c707d4870751370000119f2811978706d4c707280997870cd4c707017a173c3308015327800017987a0b330802510a0c9877108e7895880301c898087b19407b18489517307b1780003307ae007b17880095173c7b1790003307f4007b1798009517407b17a000330700017b17a8009517107b17b000330770017b17b8009518207b18c0007b17c8003307381b017b17503307052893fe9511c87b10307b15287b1620828528828920825a2064763308277b190864977b1ab4a002f600647833070152085d8168951714330901019516145020f8008df97c1714522780002581181882150864578219b49002fa0052072d33082764578219b49002fc0028227c181e7c191f825a18c98909c868088215086457b4a002fe005107d93307010182103082152882162095113832009511e87b10107b15087b1664758277648650200201d8f452071c826828826720828a183308430501330902b4a002040151071333070182101082150882169511183200825708646882101082150882169511182896f47c847c8901958802c89302ab0421ac32840064b4ac2b8a00c8a3030151091a7c359533019599ffab65f52854ac40146423abc8cf280c6423644b821a10abc8c3821c088214c8c404330801017dc995ca0157090d64acbf970759071d2825aa4a4c7cca0195cc0284997f979908d4a909bf970757070e858801ab4cd3280533080184870182102882152082161895113032003309e81b016437642828dbf63309e81b01642764482811ed3307d01b0128b9f082890851096a019511e07b10187b15107b1608330b7b18828833027d080133047d0901330380003306c0280c95bc040164cbae9c0f01c8b80c7dc595bc015905f28450ff00c802057c555115044451150321521502ef00d89c05c8c80ada540a7caa84aac000ab3adb0095bc0228c3d89c0ac8c805daa4057c555120e000326c555220ed004258159fa00028b600d89c0ac8c805daa4057c555120f0001f5220f400386c5a581a8f41289800845ae000512aa00076288c00955a70ff531a302a288100950a1fff33030cae3a4933038000ad6555286d950a0fff561a02676c5a3306c0af6a5e95bc02d89c0ac8c805daa4057c5a84aac000ab3a4995bc03d89c0ac8c805daa4057c5a84aac000aa3a1fff2830840afe00522aee002a330380003306c0af651e95bc02d89c0ac8c805daa4057c5a84aac000ab3a0995bc0328f2fec8c806c9c909821a7ba67ba908c8b809c9bc0c7b787b7b087b79107b7c188210188215108216089511203200490732009511b87b10407b15387b1630827a7b1898ab0433081433096e0601532b71028300330814951b2e14074b598638d6c56d347b1710491108102733007b143303ffe0f5053305640164a4821710d67a0a98aa0b821708ca7a0cbfc406976c3098cc32ca0c0c98c71198cc1084ccfe07ca5707bf7606976631986630c89c0c7cc77ccc01c896067c62017c6678bcff78b7fe78b20178b69588fc95bbfcac43ac541a633697a73098773296777b14987711967b64bfba0a97aa3198aa309588fec89a0a7cab017caa951c1cc88c0c78cb0178ca647a01551a0a149588ff95171cc8870786a9307879281c97aa019588fec8a9097c97017c99951a1cc88a0a78a70178a901951a1cc88a0a9a8b14330801821733095020080106e882104082153882163095114832009511d064783307301c017b17491108014911204911100849111864172824e69511907b10687b15607b1658828a97a902330ba00001c8b9098199828608828810c8b9097b1720330b330c3209330c646b2807646b648c017b18107b16183307013306f73308330964a5330010010a12016478ae6746570807017b1808510853648650201201431151071e017b173308646950201401ad18645a9757023308e80001c887078177c88707330b330c64693207330c821b18283f5218ffcc00140700000000000000808218207b8728a200645a9757023308300101c887078177c88707330b330c3207330c821b182849821b18821c10013305f78217647633080a12015317f7295217ffa500140800000000000000808217207b78646782106882156082165895117028911464652833821b18821c10013305013307013306f7330833090a12015317f71a5217ff8200140800000000000000808217207b7828118217208218087b787b75087b78100182106882156082165895117032003307d01c012803e03307a01d017b172849113001491148491138084911403308b01d01951728289ee464672866df3307a01d017b172849113001491148491138084911403308b01d019517282878e43307a01d017b172849113001491148491138084911403308b01d019517282857e4951178ff7b1080007b15787b1670648664756487502022015801837752070f826b08978720987a20aeab20140700000000000000807b57018210800082157882167095118800320083885108d40064a77b157b1610826a330c3306491120016478017b182833070040e67805c96c077b1b30ae5722c85609ac69cf005709cb007b1a08510c168217207b17587b1c68330701280ac8560728333307017b1760951740330801951a587b19385020240105028217405207aa008217487b1720821738647c821b30821a08017b1738ac678400ac5b64821720c8670764a8645964a67b1c1850202601b515821c18821b30c95b0bc8560a8217107b7a821828c958087b7b08821938649652085aff140700000000000000808215aa7c13ff280b330c330949112001017b5c8217207b57087b59102806ff8215510cf4fe821720502028018e1228e7fe33073309a01c01283fde3309881c016467821838280ee38217488218503309a01c012824de9511c87b10307b15287b1620827a08330901510a1a01827b7cb895a2ff95b5017b757b720851080101330cff00abc85f330c09accaf9007cb8027cb9017cbc037cb604978808d4980897cc10976618d46c0cd4c8087cb9067cbc057cb6077cb508979908d4c909976610975518d46505d45909979920d4980895aaf795bb097b7b7b7a089887208e7928a3006c8a570a0a3306491118284c84a84033060151083984a82033060251083084a81033060351082784a80833060451081e84a80433060551081584a80233060651080c84a801330607520868491118ae6208330901284c7b170895171864587b1a1064697b1250202a013b148217c96707c865058218087b857b87088567079b77ff821818821910e07907976903cf9707c878089887208e7928089887208e7901649782103082152882162095113832003307701c012886e99511c87b10307b15287b162082ab0864866478510b3982a7105107387b177b181882a77b17107b1908649850202c01fd0c51073d6475821810821950202e01ad1382171050203001d810282e51091e28055109197b18186498649550203201cd0c821818645964752817646528133305821908821818280982190882181801885701db55067b86087b89107b8782103082152882162095113832009511b87b10407b15387b16306475827a95a7015107810097a8018fa9da9708330704e47806330824d6860933075209679669243308fd140bfeffffffffffff7fac9b54510a1482570896a8247b17187b182833070228053307017b17206417330802951a185020340100ff82175207198217087b57087b5682104082153882163095114832008217088218103309b81c0128b1db3309b81c0128a9db9511b87b10407b15387b16306475827a95a7015107810097a8018fa9da9708330704e47806330824d6860933075209679669243308f9140bfcffffffffffff7fac9b54510a1482570896a8247b17187b182833070428053307017b17206417330804951a185020360164fe82175207198217087b57087b5682104082153882163095114832008217088218103309b81c012815db3309b81c01280ddb9511b87b10407b15387b16306475827a95a70151077d97a8018fa9da9708988939330752096d330904e498069769073308f1140bf8ffffffffffff7fac9b54510a1482570897aa077b17187b1a2833070828053307017b17206417330808951a1850203801ccfd82175207198217087b57087b5682104082153882163095114832008217088218103309b81c01287dda3309b81c012875da9511b87b10407b15387b16306475827a95a70151077097a8018fa9da970898893b3307520960330904e49806976905570954510a1482570897aa057b17187b1a2833070128053307017b17206417330801951a1850203a0141fd82175207198217087b57087b5682104082153882163095114832008217088218103309b81c0128f2d93309b81c0128ead951092a827b0001330aff0001acba1e7c8cc87b0b78bc827b000195880195bb019599ff7b7b00015209e5330732009511a0fe7b1058017b1550017b17330a6b1501330701330b15330833090a6433050149210801330880150195170833090f50203e01a164177b174001330742017b17480133076805017b1710017b15180149213001951740017b1720017b15280195170895181001502040013401821b0801330701951a08330833090a6400951168ff7b1090007b1588007b1680008285288276828720825a183308b4050133090c7b17187b1a10b4a00244013308015207d8007b168267089578109579147b1750330770017b17587b18603307d6007b17687b19707b17783307e019017b1720491128034911409518507b18304911380395192082161864677b150864585020460193e5510708330801287d3308c005013309026467821a10b4a00248013308015207668217827a82a77b175082a9087b195882a8107b186082ac187b1c6882ab207b1b7082aa28330b017b1a78aab913821808520913520c103309330801281b821808510c10951950646750204a0125e528128278827908016467821a10b4a0024c0164780164878210900082158800821680009511980032006489828a08828818330b01aaba0b520a0b520808283e51080b3308401c0128d8e4829982980851082c829b827a00013309ff0001aca91e7cbcc87a0a78ac827a000195bb0195aa019588ff7b7a00015208e532009511f8481104552880000d951905781804287f8b890b52091c951906988a0686aac000781a0484883f8688800078180528608b8910520929951907988a0c86aae000781a04978a3498aa3a95aa8000781a0584883f868880007818062834951908988a1286aaf000781a04978a2e98aa3a95aa8000781a05978a3498aa3a95aa8000781a0684883f8688800078180701827a0001951b043308ff0001aca81d7cbcc87a0a78ac827a000195bc0195aa017b7a000164cbab9ce6330795110832006489828a08828818330b01aaba0b520a0b520808283e51080b3308401c0128c4e3829982980851082c829b827a00013309ff0001aca91e7cbcc87a0a78ac827a000195bb0195aa019588ff7b7a00015208e5330732009511907b10687b15607b1658828a2882797b19408276088285207b1a2082a92033082264577b1910b4900254013308015207670251061e027b153302330a3303a149110801000133002233045c82154064687b1618280fc95808c89a0a8216185108c0013307c885097b193801c875097c9b95bc81ac3c12aa0b0faa4b0c957701ab78ec2898017d988486ff007b123057080b9599017b1928285d7c9b0184681f84bb3f330cdf00ae6c337c9c0297bb0684cc3fd4cb0b330cf000acc62e959c047b1c287c990397883d98882b97bb0684993fd4b909d48906281d9599027b1928978806d4b806280f9599037b192897880cd48b0601c8a70595174c646882190850205601cbe97c174c522780001664593303a182123033002233045c28d9007c17577c1856bf87078477ff0082123033045c5217010f64593303a133002228b700ac257d01821840c82808510216821718ae720c7d875a17bf0b286401ab72610151051c821718ae7512821740c857077d775a17bf0b284801ab754501821720827a18c9250982177b1a30b4a0025801520716017c174c52278000188118508217821910b490025a0151072328fa007c18567c1957c9890995174cc878088217821a30b4a0025c015207de00330701532680001798680b33070251080c9867108e77957703016459c857023303a133002233045c01821838821528330a0133078000ac7654fe98670b330a0251074afe9866108e6a95aa03283efec88a0a01ac2a8c008215510230ae6236821740c827077d7764285817bf76510a21ae6a29821740c8a7077d7764825817bf6264a6281b330828173308520ae53306280e6468aa62d928486482ab6a44821720827a18c98609821740c878086457b4a0025e013308015207133308226457821910b4900260016478016487821068821560821658951170320033080128ed330b801a01821740646864293300f20028eaeb330b681a018217408218186429645a3300f20028d4eb951130ff7b10c8007b15c0007b16b800827508827955250304a0005105da018287207b17308287287b172833076c017b1720140720000000080000007b171833076805017b1710017b19389517387b17708217207b177849218000024921900049219800024921a0008217187b17a8004621b000038217107b174049114801951880007b1860491168019518707b18504911580164969519408217308218285020640118e0520754019555ff9569015205992843017b15088287207b17308287287b17283306000233076c017b1720140720000000080000007b171033076805017b17187b196495017b15389517387b17708217207b177849218000024921900049219800024921a0008217107b17a8004621b000038217187b174049114801951880007b1860491168019518707b1850491158019519408217308218285020660179df5207b5009566ff95550152069b821728827a183308f11701330903821730b4a00268016478330701520891008217088218c88707957500fe33060002140720000000080000007b1710017b15389517387b17708217207b177849218000024921900049219800024921a0008217107b17a8004621b000038217187b174049114801951880007b1860491168019518707b18504911580195194082173082182850206a01d4de5207109566ff95550152069c33072806330701018210c8008215c0008216b8009511d0003200951178ff7b10800082777c77330a810095197f0164ab847a0f98770488a20a86ac3095aa57db2c0a789a9599ff95baff5207e495b7fe56278000289aab8100641ac87a0a33090264873308f5170150206e01fad7821080009511880032003309501a01330880002844d6827a827908648764a82890da951168ff7b1090007b1588007b168000827b10827608510b1f33079abcc86b099599ff017c9a520a16957701c87c0a9599ff520af233057b1b10280f7b1710c97b05ac7b5601647b0133071f645c64697b1b0801510c467d9a95990195ccffada7f5541520547b1618491120089517187b1740330762017b17483307301e017b1750491158024911709517408285208288287b1760330601284f7b18951718646864595020740152e1821718510792008218561520b47b16187b15209517187b1740330762017b17483307501e017b17503306019517407b17608285208288287b1658491170014911680195195064577b185020760140dd52078c0082170851073b9517107b17183307ae007b17203307601e017b1750491158024911709517187b17603306017b1668951950645782185020780101dd52074d330628488217208218287b17307b18389517307b1740330770017b17483307101e017b17504911580249117095194082188287208288287b19604911680195195050207a01b7dc64760164678210900082158800821680009511980032003309f81d01645764b828aed83307011409e0ffffff1f000000ae8907330732009511d87b10207b15187b161095861f3305080003955948278499fc013e04000003977420994420819ad44a047a943a0400000384aaff00520ae59557002082770006986605510734011407ffffffff00000000aa760d015648e0ffff3f090183620164675328c10f2a69278d7918c6920a84aa7f9099ffe092098e99977707c97a0ac8a9099597ff079577800401330b0020c8b509829a38078b7906d09a0c84cc01510c1d979c03955b0020c8cb0b82bb0806330cffcf7c07d2b7075207219599019b97ffd2a7075107d4006a7997970395580020c8870782770806016a748a9706d44707977702c8750b80bc80ca04836798aa01aea76b7b12089557002082730006bf3c008b0805c868028568ffbea808817744077b188e88c82808838a01aea72433076577c9370797a825988220ae270bc97207657751072b955700207a7a44070180ca0881cc8407e0c837077aba510aa300481a0c821408821028d000330701955800204628480782102082151882161095112832007b180850207c018b011408ffffffff0000000064793307aa86d38218085209c1fe28c964675428c00f1f69278d7818c6820884887f977707c978089587ff0795778004018b7906d09a0884880151081e979803955b0020c8b80882880806330bffcf7b07d287075207fdfe9599019b97ffd2a7075207dffe2872ff979a0333020020c8250bc8ab0b82b808063303fedc430ad2a8087bb8080682140882105208169552002082283807dc9309d298087b283807019748019588019779209899207a9c7a9804957320976a05c8a30951008b00640754107f2a330700000002e67007697a8dab18c6b70784777f97aa07c9a7079577ff079577800495770101837bff8bb70684b23f97bb02c8b50c80cb7ac9970c01979a2098aa207aa47aac047aab08481a0c510b077ab90c01977a03955c0020c8ca0a82ac08069b2801d4c8087ba80806955b002082b838079b7701d487077bb738076404019557002081784007817a0006970705be9707978805bea808ae87149777209877207a749737209877202868fe973720987720285ffe33086927033307657795881f8488e0ae8714c9780765793307510962fe520809285bfe51084333076577c9870756171f0d9a79206597510745fe3303080003953b002081ba4007482b4407013307000000027bb80006aca71c510a12552a80001383a4ff282833072813fe3304281f64a70169798d9a18c6a70784777f979907c997079577ff0795748004018b4b0697b70395390020c87909829c0806330701cf470ad4ca0a7b9a08069532002082293807812a4007cfb70bd4b9097b29380797aa0148087a8a0448180848180c974402c843037a3828a5fd3303080003953948278499fc330a01013e0400000397a420994420819bd44b047a943a0400000384bbff00520be5510794029511d87b10207b15187b16109570e09709209537002082750006989920809a04819bbf500c98a901541c1fd60097ba05bfa00c97ca2098aa2080a4048446015206bf009848013306000000027b185644000000041651042a6486552400010a958bff282069688d8518c6560684667f978807c986069568ff07958b80042805330b0181a87b180897b52098551ec83505815280a608abc20d7a5651061648160c284780aa0c7aa60851063f7a6a0c28398bb606976a0395380020c8a808828a080684bc3f3305fedcc50cd2ca0a7b8a0806520a169532002082283807dc650ad2a8087b2838070195380020828500068218be8909974404bf4000821b0801817a400797aa05be5a02979c05be0c0cae2cd10097ca2098aa2080a6048467015207c1007b1b08986b013305000000025646000000041651062a64b5552600010a95b4ff282069578d7818c6850884887f977707c978089587ff0795748004280533040197472098771ec87305815780a608abc7137a5651062248160cbeb909821b08285e80aa0c7aa60851064f7a6a0cbeb909821b08284a8b4a0697a703330c0020c8c308c887078278080684453f3306fedc5605d258087b78080652081d953c002082c73807dca608d287077bc73807beb909821b082809beb909821b080151098900649a54197f2a330700000002e6790769788d8a18c6a70784777f978807c987079577ff07957a800495aa010183a8ff8b8a06848c3f978802c8380880867a809798019707209877207a7b7a78047a760848170c5106077a600c0197a70395380020c88707827808069bcc01d4c8087b78080695360020826738079ba801d487077b67380701979705be0707ae270c9777209877207a790182102082151882161095112895370020462748073200953700204627480732009511e87b10107b15087b16531910698d7a84a207c82706ae671a642c647a648b017cb578a595aa0195ccff95bb01520cf3c82804c929028423f8844807c8360a51083eaea64e9748038480388445f8825b8d88848c38955808018285d00b0bcfc509d4b9097b69956608958808645baca6eb281f647ac8970bacb7242831aea61364480182897b69956608958808aca6f6c83408842907c89a0baeba137c8b78ab95aa019599ff9588015209f382101082150882169511183200531910558d7a84a207c8270baeb713642c647a0178a895ccff95aa01520cf8c92909849af8c8ba0aaeab22978238140c0001010101000000d6c202972c20d42c0c017bbc95bb08acabfb849907c89a0bacba0d2815647ac8970baeb70e78a89599ff95aa015209f832005109167c7a7c8babba139599ff9588019577015209f033073200c9ba073200259588888888081140129144228924922491441249249144128924914824922449924422914822914824128944444444449244141121228a982489249148128944224992482412499248240a49929248241249424248241249224912224412210920522289a42449922449922449294549922449922449922492282491249344880811401211892422224400494444a41011112122440049442222422411894422899224491291244992244992244924499224499224491249922449922491244922499224499224499288081140241191888888442289a424499224499224892449922449922449224992244992244992489224499224494492241149922449922449922492244992244992248924499224499224492225499224499224492292244992244992244924499224499224491249922449922449924492244992244992241149922449922449922492244992244992248924499224499224492249922449922449924424914824121111a1a44844944484221191442491a424492411911a910891444444c4144989482444222249249248449248922449922449924824128944229148241289449148922489244992242589442291241289449224229148248944229144928888109124918888002289441240c424154a2252441491081140249188002289888888888888888888888888888888888888888888888888888888888888888850124a224244001140041001c488882412495292481289488d48844822a25446842812894412918824222549922449922449244992244992244912499224499224499244922449922449922411112222222211114522914824128944229148241289442291444428421121a224a44424119144249122112511911a9108111111c5144949924849244422228924112192884492889424499224922449124992244992244992449224499224499224892449922449922491242222424424224544448c08258a4444494a44121149249122115112911a91081111514c49144924128944221289242249492449224224118944229144a42449922449922489244992244992244922499224499224912449244992244992248924111151a1222212894a11119188221189888888444424220a458c44229124128944924824124992482412492291482449221249a248241289248988489224892422114522894488448844884444a294449108209252119324499224499288242222222222222222222222222222222222222222224984189148442412111111111189888888888888888888888888888888888888888818111111111111111122444488081191aa294444448522222244444444142222221a910a91448809211189882221268424468448444412914222524485882285884444445446041023122142442422424444440ad1a210012422112211222211222291124912924292502442442442244245441421228988880a45442221221121858444841412121111891049128910498849082124124248a4902242420a2922242452229284a4902414424824849048881021422424a4909042420a29a490420a19114248242424a44242528984449248922449492a214924552914899424492a4992249952284949522149489224492a41aaa0201534499292a452929492d2922449922423250591544aa54c1249a5841424849024a5294826495292942a2829a42409a924254929a5a4a0a424a92a28254994940a9a94244922294922492425912492443225524a12942949aa8252929424494a094992524a4aaa829492a4aaa094249524499224530a4a5292544812921205534a2449482489544a122909892491d2242909210920218225499224892449494a529248224992a4243592a492a4244d920a49529294928424a954d29224d51496244992248948492a05899424494992248a9482249948125224499924c92449489624a59204494a429292492409494912129524499224495241a9142a2549952435494a2991242191245292a4521289524a115526292991a4949aaa448c4889a41a49a2a425492225494a49925492644992041004900002484a9224499224a2489222d29224499224499224499224495252244996244992244992a448222992a4344992499290244942494a2649929014492429494a5232494a2191229192529244922489444992a424c99424498824494924299594492291a454522649491212491225499292a42c49125212922429a9a4242909494d122549252148024888209324499224922429494a2925924892242949912425a9a24892264905495244922422912549525212a214599224252521ca14828842244492a42849921452222549520a204912659124919424492452124924854292444a2824691491228a24224992a28824494992225149955296244b92529212492221912452524a244948248994a41422912492489290929424a98448244992923492a492242569921424a5509448482489649244424a912412910829092949929424494992942449499292244924252249c22425923052923411125292a4249524499224a9922492944892441151142562492293a4908842088924a42449492a49922449522549242991244b922489420889554592a49424922a0a811008218490084108420821025112a2849451422489442423852421494992248a8844242212119292242212118908c92492948a242999922409952453924c5252542429459284240949922428299984242993242212118988444422429224495252a1a4448284a44252a124490ac92429495590242121494a29292949a2c894a4942a49c99292922449929a2429544225249124550891b24849a48890a48894a450084952084522242291448a2849922249524411224992224952494a9224559254128224808408b22449922491242949494a124924499294a44692549294a44952419294429148499212529254554d922a5a2229882a2821a5aa1402482921a52a2553354200a924492593542304904a4a264921219148484a4824129290482424114915942409a0914445a58a269224114992949a240912494a2692540992242549114051c592a4489042424912922449545222924892244992244992242929a594244992244992244952522a284949922449a525492149924a524a0a520a1294920a525549c92429494a924aa424494902489224352541494a9292909024a9444a92942480244952531294a42429090949924a5252922480244952531294a42429090949924a5252922449929a92a024254949484822a6144922151185a44a84241411212222221144528d8848494812122549249124249244520a92424a48aa244992244992529282ca125a4444254952424a8a985224899484a42489245292449248222591249244121989294512a554922425a4a4882945924825494a92a48444540992949244c99424498a24294924492249922429494a924909124a922489244a1249922291242912494a8884949024494a888424294b92492292484946495291241555a54a2549524852424b925248454252918888144492440248c824498810899044249228491049922489480009a94992102112218948249124882449484289948800324912224422241189249204495219115144459624494912a2a410441422a5121191a49225494a493495294924924848244952a920512449242449928c54104912098924492a482549120989a42491a04544142a092095240991212985484422800848a124494922462249128924491225124b92529288244954924a929488292992d2882429410095448592241193249148124922892492444a441293a4248922484992884c925492922413892422926844244992922409952415492a912244842092904ab52449c424124922229248499212221992528844922411494948922412144821952489a825494949495212892489442431a224194912491249104821952489a8494a4a929224298924922412914452328a20254922324952494a924c249288486292a449125144498a92542b49922449924a96a4a454525a4992a492242945496a49922401245992a49292944a925402", - "003f00b000000000322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea": "0100000000" + "00fe00ff00ff00ff804dccb6bae35cd48cc1e11e426e7ba218727e2ba7dd43": "5000156a616d2d626f6f7473747261702d7365727669636506302e312e32330a4170616368652d322e30012550617269747920546563686e6f6c6f67696573203c61646d696e407061726974792e696f3e28330070270000000020000200ffff1c00ffff1400ffff1800ffff0c00ffff2200ffff3c00ffff1a00ffff4600ffff1000ffff4200ffff0800ffff0e00ffff2a00ffff0600ffff1600ffff0400ffff1e00ffff3e00ffff4c00ffff2000ffff2600ffff4800ffff5000ffffe200ffffec00ffffec00ffffec00ffffec00ffffe400ffffec00ffffec00ffffec00ffffda00ffffde00ffffda00ffffda00ffffda00ffffec00ffffec00ffffec00ffffec00ffffdc00ffffe800ffffec00ffffec00ffffdc00ffff6a01ffffb001ffff9c01ffffa201ffff8601ffffc801ffffdc01ffffa801fffff001ffff9601ffffea01ffff8401ffff8e01ffffd601ffff7e01ffffa001ffff7c01ffffba01ffffe401ffff0202ffffc201ffffce01fffffa01ffff0802ffffce01ffffde01ffffd601ffffd801ffffd001ffff2a02ffff6a02ffffa602ffff8e02ffff5e02ffff7002ffffb202ffffba02ffffde02ffffd402ffffd402ffffc802ffff5202ffff7a02ffff6402ffff4a02ffffb803ffff5002ffff4602ffff3402ffffc002ffffd402ffffbc02ffff9a02ffffa603ffffd603ffffcc03ffffd203ffffbc03ffffe003fffff603ffffd403ffff0004ffffc403fffffe03ffffb403ffffbe03ffffe403ffffb003ffffd003ffffae03ffffda03fffffa03ffff0604ffffde03ffffe203ffff0404ffff0a04ffff0c05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff1605ffff1005ffff0e05ffff0e05ffff1405ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff0e05ffff1805ffff0e05ffff0e05ffff0e05ffff0e05ffff1205ffffb004ffffb004ffffb004ffffae04ffffac04ffffae04ffffac04ffffb004ffffb004ffffb004ffffb004ffffb004ffffac04ffffac04ffffb004ffffac04ffffb004ffffac04ffff7604ffff7604ffff7604ffff7404ffff7004ffff7404ffff7004ffff7604ffff7604ffff7604ffff7604ffff7604ffff7004ffff7004ffff7604ffff7004ffff7604ffff7004ffff3204ffff3204ffff3204ffff3004ffff2a04ffff3004ffff2a04ffff3204ffff3204ffff3204ffff3204ffff3204ffff2a04ffff2a04ffff3204ffff2a04ffff3204ffff2a04ffff2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7665632f737065635f66726f6d5f697465725f6e65737465642e72734661696c757265206f6e6c7920696e2063617365206f6620626164206d656d6f7279206f722062616420736572766963652049443b20626f74682061726520676f6f643b207165642f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f686f73745f63616c6c732e727363616c6c65642060526573756c743a3a756e77726170282960206f6e20616e2060457272602076616c75652f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f6d656d2e72732829436f6d7061637420656e636f646573206c656e677468696e666f6d656d6f4974657268617368636f72656b65797369746572626f6f7472656164536f6d65206f6620203c3d207472756573656564202d3e204e6f6e654974656d202020206461746144726f704f6b4572724f706171756556616c4b65797365746564323535313962616e646572736e61746368626c73636f64655f6c656e496d706f727465646e625f6974656d736175746f5f6163636d657461646174616578706f7274656420286279746573204578706f727465645472616e7366657262616c616e63653f696d706f72746564526573756c743a207472616e736665724c6f6f6b65645570707265696d61676550726f7669646564636f64655f686173686d696e5f6974656d5f6761736d696e5f6d656d6f5f676173656e646f776d656e74726567697374726174696f6e437265617465536572766963655570677261646564657374696e6174696f6e616d6f756e746761735f6c696d69745a6f6d62696679656a6563746f72456a65637474617267657444656c6574654974656d7373746f726167655f6974656d73536f6c696369746c656e466f726765744c6f6f6b7570736572766963656561676572496d706f72746974656d734578706f7274426c6573736d616e6167657241737369676e717565756544657369676e6174655969656c64436865636b706f696e7450616e696350726f76696465736572766963655f6964636f756e7452616e646f6d53746f72616765526566696e6552616e646f6d53746f72616765416363756d756c61746542656e63686d61726b4d61705b5d2f55736572732f61646d696e2f616374696f6e732d72756e6e65722f5f776f726b2f706f6c6b616a616d2f706f6c6b616a616d2f6372617465732f6a616d2d74797065732f7372632f66697865645f7665632e7273436c6f6e65646974556e72657175657374457870756e67654e6f74596574556e72657175657374737563636573735f61667465724e6f74596574457870756e6765556e70726f766964656473696e6365556e72657175657374656470726f76696465645f73696e6365756e7265717565737465645f73696e63655265726571756573746564756e7265717565737465645f617472657265717565737465645f73696e63656b6579496e7075744f7574707574426f6f747374726170205365727669636520526566696e652c206852657475726e696e672000000029207768656e20736c6963696e672060207769746820636f64655f68617368204372656174656420736572766963652020696e746f20616363756d756c61746572616e676520656e6420696e646578204578706f7274656420736c6963653a2001000000000000000000000000000000736572766963655f726567697374727944657369676e61746564206b65797320303132333435363738396162636465664561676572206c6f6f6b75702c20676f742073657276696365732f626f6f7473747261702d736572766963652f7372632f6c69622e7273426f6f747374726170205365727669636520416363756d756c6174652c2068204020244465636f64656420696e737472756374696f6e3a20436f64652070726f766973696f6e20726573756c74656420696e20417474656d74696e67207472616e736665722c206761733d5472616e73666572206f66202077697468202067617320726573756c74656420696e204661696c656420746f20637265617465215570677261646564214761732072656d61696e696e673a20417474656d7074696e67207472616e736665723a20205a6f6d62696679696e6720736572766963652e20456a6563746f723a20235a6f6d6269666965644661696c656420746f207a6f6d62696679202d20696e76616c696420636f64655f686173683f4661696c656420746f207a6f6d62696679202d206c6167676172647320696e2073746f726167652f6c6f6f6b75703f456a656374696e6720736572766963652023206974656d732064656c65746564207375636365737366756c6c792c20206b657973206e6f7420666f756e6444656c6574696e67206974656d3a204661696c656420746f2072656d6f7665206974656d3a20536f6c69636974656420206f66206c656e67746820726571756573746564517565727920726573756c743a20202866693a20756e7265717565737465644c6f6f6b75702c20676f74206c6f6f6b65645f7570426c6573736564207365727669636573206d3a20232c20613a20232e20763a20232e2061613a2041737369676e696e6720636f72652020746f2071756575652041737369676e6564214661696c656420746f2061737369676e215969656c646564206861736820436865636b706f696e7465642150616e696320696e737472756374696f6e2065786563757465642150726f766964656420707265696d61676520746f20496d706f72746564206461746120696d706f72742d4578706f7274656420206974656d73636f756e745f72616e646f6d5f73746f726167656974656d2d2d62616c616e6365206c6f77496e737472756374696f6e206e6f742068616e646c65643a207472616e736665727265647472616e736665722d636f756e745265636569766564207472616e736665722066726f6d202077697468206d656d6f20000b000000000000000c000000000000000b00000000000000070000000000000006000000000000000e000000000000000d000000000000006361706163697479206f766572666c6f772f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f616c6c6f632e72736d656d6f727920616c6c6f636174696f6e206f6620206279746573206661696c65642f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f737472696e672e72736120666f726d617474696e6720747261697420696d706c656d656e746174696f6e2072657475726e656420616e206572726f72207768656e2074686520756e6465726c79696e672073747265616d20646964206e6f742f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f666d742e7273292e2e3a5b63616c6c656420604f7074696f6e3a3a756e77726170282960206f6e206120604e6f6e65602076616c756570616e69636b6564206174203a0a696e646578206f7574206f6620626f756e64733a20746865206c656e20697320206275742074686520696e646578206973203a20207b202c20207b0a2c0a7d207d28280a2c0a5d2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6e756d2e727330303031303230333034303530363037303830393130313131323133313431353136313731383139323032313232323332343235323632373238323933303331333233333334333533363337333833393430343134323433343434353436343734383439353035313532353335343535353635373538353936303631363236333634363536363637363836393730373137323733373437353736373737383739383038313832383338343835383638373838383939303931393239333934393539363937393839392f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f666d742f6d6f642e727366616c736572616e676520737461727420696e64657820206f7574206f662072616e676520666f7220736c696365206f66206c656e67746820736c69636520696e64657820737461727473206174202062757420656e6473206174202f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f7374722f6d6f642e7273010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002020202020202020202020202020202020202020202020202020202020203030303030303030303030303030303040404040400000000000000000000005b2e2e2e5d626567696e203c3d20656e642028606279746520696e64657820206973206e6f742061206368617220626f756e646172793b20697420697320696e736964652029206f662060206973206f7574206f6620626f756e6473206f6620602f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f756e69636f64652f7072696e7461626c652e7273000601010301040205070702080809020a050b020e04100111021205131c140115021702190d1c051d081f0124016a046b02af03b102bc02cf02d102d40cd509d602d702da01e005e102e704e802ee20f004f802fa04fb010c273b3e4e4f8f9e9e9f7b8b9396a2b2ba86b1060709363d3e56f3d0d1041418363756577faaaeafbd35e01287898e9e040d0e11122931343a4546494a4e4f64658a8c8d8fb6c1c3c4c6cbd65cb6b71b1c07080a0b141736393aa8a9d8d909379091a8070a3b3e66698f92116f5fbfeeef5a62f4fcff53549a9b2e2f2728559da0a1a3a4a7a8adbabcc4060b0c151d3a3f4551a6a7cccda007191a22253e3fe7ecefffc5c604202325262833383a484a4c50535556585a5c5e606365666b73787d7f8aa4aaafb0c0d0aeaf6e6fddde935e227b0503042d036603012f2e80821d03310f1c0424091e052b0544040e2a80aa06240424042808340b4e03340c813709160a08183b45390363080930160521031b05014038044b052f040a070907402027040c0936033a051a07040c07504937330d33072e080a0626031d080280d0521003372c082a161a261c1417094e042409440d19070a0648082709750b423e2a063b050a06510601051003050b5908021d621e48080a80a65e22450b0a060d133a060a06141c2c041780b93c64530c48090a46451b4808530d49070a80b6220e0a06460a1d03474937030e080a0639070a813619073b031d55010f320d839b66750b80c48a4c630d843010160a8f9b0582479ab93a86c68239072a045c06260a460a28051381b03a80c65b654b0439071140050b020e97f80884d6290aa2e781330f011d060e0408818c89046b050d030907108f6080fa0681b44c4709743c80f60a73087015467a140c140c570919808781470385420f1584501f060680d52b053e2101702d031a040281401f113a050181d02a80d62b040181e080f7294c040a04028311444c3d80c23c06010455051b3402810e2c04640c560a80ae381d0d2c040907020e06809a83d80411030d0377045f060c04010f0c0438080a0628082c04023e81540c1d030a0538071c06090780fa840600010305050606020706080709110a1c0b190c1a0d100e0c0f0410031212130916011704180119031a071b011c021f1620032b032d0b2e01300431023201a704a902aa04ab08fa02fb05fd02fe03ff09ad78798b8da23057588b8c901cdd0e0f4b4cfbfc2e2f3f5c5d5fe2848d8e9192a9b1babbc5c6c9cadee4e5ff00041112293134373a3b3d494a5d848e92a9b1b4babbc6cacecfe4e500040d0e11122931343a3b4546494a5e646584919b9dc9cecf0d11293a3b4549575b5c5e5f64658d91a9b4babbc5c9dfe4e5f00d11454964658084b2bcbebfd5d7f0f183858ba4a6bebfc5c7cfdadb4898bdcdc6cecf494e4f57595e5f898e8fb1b6b7bfc1c6c7d71116175b5cf6f7feff806d71dedf0e1f6e6f1c1d5f7d7eaeaf4dbbbc16171e1f46474e4f585a5c5e7e7fb5c5d4d5dcf0f1f572738f747596262e2fa7afb7bfc7cfd7df9a00409798308f1fcecfd2d4ceff4e4f5a5b07080f10272feeef6e6f373d3f42459091536775c8c9d0d1d8d9e7feff00205f2282df048244081b04061181ac0e80ab051f08811c03190801042f043404070301070607110a500f1207550703041c0a090308030703020303030c0405030b06010e15054e071b0757070206170c500443032d03010411060f0c3a041d255f206d046a2580c80582b0031a0682fd03590716091809140c140c6a060a061a0659072b05460a2c040c040103310b2c041a060b0380ac060a062f3180f4083c030f033e0538082b0582ff1118082f112d03210f210f808c04829a160b158894052f053b07020e180980be22740c80d61a81100580e109f29e033709815c1480b80880dd153b030a06380846080c06740b1e035a0459098083181c0a16094c04808a06aba40c170431a10481da26070c050580a61081f50701202a064c04808d0480be031b030f0d2f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f636f72652f7372632f756e69636f64652f756e69636f64655f646174612e7273617474656d707420746f20646976696465206279207a65726f617474656d707420746f2063616c63756c617465207468652072656d61696e646572207769746820612064697669736f72206f66207a65726f0000000003000083042000910560005d13a0001217201f0c20601fef2c202b2a30a02b6fa6602c02a8e02c1efbe02d00fe20369eff6036fd01e136010a2137240de137ab0e61392f18e139301ce14af31ee14e4034a1521e61e153f06a61544f6fe1549dbc615500cf615665d1a15600da215700e0a158aee2215aece4e15bd0e8615c2000ee5cf0017f5d00700007002d0101010201020101480b30151001650702060202010423011e1b5b0b3a09090118040109010301052b033b092a180120370101010408040103070a021d013a0101010204080109010a021a010202390104020402020303011e0203010b0239010405010204011402160601013a0101020104080107030a021e013b0101010c01090128010301370101030503010407020b021d013a01020201010303010407020b021c02390201010204080109010a021d0148010401020301010801510102070c08620102090b0749021b0101010101370e01050102050b0124090166040106010202021902040310040d01020206010f01000300041c031d021e02400201070801020b09012d030101750222017603040209010603db0202013a010107010101010208060a0201301f3104300a040326090c02200402063801010203010105380802029803010d0107040106010302c6400001c32100038d016020000669020004010a200250020001030104011902050197021a120d012608190b01012c03300102040202020124014306020202020c0108012f01330101030202050201012a020801ee010201040100010010101000020001e201950500030102050428030401a5020004410500024f04460b31047b01360f290102020a033104020207013d03240501083e010c0234090101080402015f030204060102019d01030815023902010101010c0109010e07030543010206010102010103040301010e025508020301011701510102060101020101020102eb010204060201021b025508020101026a0101010208650101010204010500090102f5010a040401900402020401200a280602040801090602032e0d010200070106010152160207010201027a06030101020107010148020301010100020b023405050317010001060f000c030300053b0700013f0451010b020002002e021700050306080802071e0494030037043208010e011605010f000701110207010201056401a00700013d040004fe0200076d07006080f00073657276696365732f626f6f7473747261702d736572766963652f636f6d6d6f6e2f7372632f6c69622e72732f55736572732f61646d696e2f2e7275737475702f746f6f6c636861696e732f6e696768746c792d323032342d31312d30312d616172636836342d6170706c652d64617277696e2f6c69622f727573746c69622f7372632f727573742f6c6962726172792f616c6c6f632f7372632f7261775f7665632e72732f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6465632e72734572726f722f55736572732f61646d696e2f2e636172676f2f72656769737472792f7372632f696e6465782e6372617465732e696f2d366631376432326262613135303031662f6a616d2d636f6465632d302e312e302f7372632f636f6d706163742e72736372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f686f73745f63616c6c732e7273686f73742063616c6c2072657475726e7320636f727265637420747970653b207165646974656d206d75737420626520617661696c61626c653b2071656443616e6e6f74206661696c2065786365707420666f72206d656d6f7279206163636573733b2077652070726f76696465206120676f6f6420616464726573733b2071656443757272656e742073657276696365206d7573742065786973743b207165644661696c757265206f6e6c7920696e2063617365206f6620626164206d656d6f72793b20697420697320676f6f643b2071656450616e69632068616e646c65722063616c6c65642150616e6963206d6573736167653a2050726f746f636f6c20706172616d65746572732061726520696e76616c69646372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f737461727475702e7273756e6b6e6f776e206572726f7220636f64653a206372617465732f6a616d2d70766d2d636f6d6d6f6e2f7372632f726573756c742e72734f7572206f776e2041504920696d706c2068617320726573756c74656420696e20737563636573732076616c7565207768696368206973206f7574206f662072616e67652e61737369676e64657369676e6174654f75744f66426f756e6473496e646578556e6b6e6f776e53746f7261676546756c6c426164436f72654e6f436173684761734c696d6974546f6f4c6f77416374696f6e496e76616c69646372617465732f6a616d2d74797065732f7372632f73696d706c652e727360726563656e745f626c6f636b5f636f756e7460206d6179206265206e6f206c6172676572207468616e2060426c6f636b496e6465783a3a4d415860202832353529607472616e736665725f6d656d6f5f73697a6560206d75737420626520657175616c20746f7220604d454d4f5f4c454e602028313238296076616c5f636f756e7420213d20636f72655f636f756e74202a2056414c535f5045525f434f524560202833296076616c5f636f756e746020646f6573206e6f7420646976696465206279206056414c535f5045525f434f524560202833296065706f63685f7461696c5f737461727460206d757374206265206c657373207468616e206065706f63685f706572696f646060726f746174696f6e5f706572696f646020646f6573206e6f742064697669646520696e746f206065706f63685f706572696f64606062617369635f70696563655f6c656e6020646f6573206e6f742064697669646520696e746f20605345474d454e545f4c454e602028342c313034296062617369635f70696563655f6c656e60206973206e6f74206576656e2e2e2e223078000b000000000000000c000000000000000b00000000000000070000000000000006000000000000000e000000000000000d0000000000000068030100000000008b000000000000003400000005000000000000000000000001000000000000000100000000000000e8010000000000003b040100000000005b00000000000000f5030000040000000000000000000000000000000000000001000000000000007206000000000000c10401000000000054000000000000002000000009000000701e0100000000005e00000000000000540400002b000000000000000000000020000000000000000100000000000000ba070000000000000000000000000000080000000000000008000000000000003601000000000000000000000000000080000000000000000100000000000000c007000000000000000000000000000008000000000000000800000000000000dc00000000000000e10e010000000000010000000000000071070100000000005500000000000000a600000020000000520f0100000000000200000000000000d31e01000000000060000000000000001401000036000000d31e0100000000006000000000000000150100001000000078080100000000001a000000000000009208010000000000010000000000000093080100000000000a00000000000000d008010000000000100000000000000040090100000000001200000000000000520901000000000025000000000000002600000019000000520901000000000025000000000000003a00000019000000520901000000000025000000000000003100000019000000f008010000000000100000000000000065050100000000000400000000000000520901000000000025000000000000002900000019000000520901000000000025000000000000003c0000001a00000052090100000000002500000000000000220000003400000052090100000000002500000000000000370000003400000077090100000000001e0000000000000095090100000000000300000000000000980901000000000002000000000000009a090100000000001500000000000000c008010000000000100000000000000092080100000000000100000000000000af090100000000001b00000000000000ca090100000000001800000000000000e2090100000000000c00000000000000ee090100000000000600000000000000f40901000000000011000000000000005209010000000000250000000000000072000000460000005209010000000000250000000000000060000000310000001f0a0100000000000f000000000000002e0a0100000000001500000000000000430a0100000000000100000000000000430a0100000000000100000000000000430a0100000000000100000000000000ff050100000000000800000000000000440a0100000000001e00000000000000c00a0100000000001200000000000000b008010000000000100000000000000001000000000000000000000000000000d20a0100000000001d00000000000000ef0a0100000000000f00000000000000fe0a0100000000000f000000000000000d0b0100000000001700000000000000240b0100000000000a000000000000002e0b0100000000000b0000000000000052090100000000002500000000000000bf0000003e00000052090100000000002500000000000000bd0000003600000052090100000000002500000000000000c20000003c000000420b0100000000000e00000000000000500b0100000000000600000000000000dd0e010000000000010000000000000052090100000000002500000000000000ca0000004000000052090100000000002500000000000000c900000035000000610b0100000000000c0000000000000052090100000000002500000000000000d000000042000000760b01000000000015000000000000008b0b0100000000000600000000000000910b0100000000000600000000000000970b01000000000006000000000000009d0b0100000000000f00000000000000ac0b0100000000000a0000000000000020090100000000001000000000000000d00b0100000000000d00000000000000ea0b0100000000001b0000000000000052090100000000002500000000000000fa00000019000000050c01000000000015000000000000004d0f010000000000020000000000000052090100000000002500000000000000d7000000420000001a0c0100000000000e00000000000000280c010000000000070000000000000052090100000000002500000000000000b50000002200000052090100000000002500000000000000b2000000510000002f0c0100000000000900000000000000380c010000000000060000000000000052090100000000002500000000000000ba0000004300000052090100000000002500000000000000080100002200000052090100000000002500000000000000040100004b000000520c0100000000000500000000000000570c010000000000010000000000000052090100000000002500000000000000110100001e000000630c0100000000001900000000000000520901000000000025000000000000004c0000005000000052090100000000002500000000000000850000004f00000052090100000000002500000000000000ff00000049000000950c010000000000170000000000000055050100000000000400000000000000ac0c0100000000000b0000000000000007060100000000000800000000000000520901000000000025000000000000002d0100000e00000052090100000000002500000000000000220100002f00000020210100000000002b210100000000003721010000000000422101000000000049210100000000004f210100000000005d2101000000000074060000000000001800000000000000080000000000000076060000000000007c060000000000008406000000000000f00c0100000000001100000000000000780d01000000000015000000000000008d0d0100000000000d00000000000000010d0100000000007700000000000000b60100000d0000009a0d0100000000007800000000000000ea010000170000000000000000000000000000000000000001000000000000007206000000000000680e01000000000075000000000000007e0200000e0000009a0d01000000000078000000000000008d0500001b00000001000000000000000000000000000000e00e0100000000000100000000000000e00e01000000000001000000000000001b0f01000000000020000000000000003b0f0100000000001200000000000000010000000000000000000000000000004d0f0100000000000200000000000000000000000000000018000000000000000800000000000000ce06000000000000d406000000000000d806000000000000620f01000000000078000000000000005b00000017000000a2100100000000007800000000000000a709000026000000a2100100000000007800000000000000b00900001a0000001f11010000000000120000000000000031110100000000002200000000000000e0080100000000001000000000000000311101000000000022000000000000005311010000000000160000000000000069110100000000000d00000000000000f3120100000000000e0000000000000059050100000000000400000000000000a00801000000000010000000000000000113010000000000010000000000000002130100000000000b000000000000000d130100000000002600000000000000d7050100000000000800000000000000331301000000000006000000000000000113010000000000010000000000000002130100000000000b00000000000000391301000000000016000000000000000113010000000000010000000000000076110100000000007800000000000000f20000002c0000004f1301000000000082000000000000001a000000360000004f1301000000000082000000000000000a0000002b0000007a1901000000000085000000000000004e000000280000007a1901000000000085000000000000005a00000016000000ff190100000000001900000000000000181a0100000000003900000000000000cb1d0100000000002c00000000000000cf00000017000000cb1d0100000000002c00000000000000ed00000013000000f71d01000000000079000000000000002b02000011000000000000000000000008010000000000000800000000000000800700000000000092070000000000009407000000000000d31e01000000000060000000000000002d0100003d000000701e0100000000005e00000000000000920400001e000000701e0100000000005e000000000000006c0400000f000000701e0100000000005e000000000000009e04000010000000701e0100000000005e00000000000000f70000000f000000331f0100000000002700000000000000c90000001c0000000000000000000000000000000000000001000000000000007206000000000000331f0100000000002700000000000000d10000002e000000331f0100000000002700000000000000db0000002e000000331f0100000000002700000000000000150100002b000000331f010000000000270000000000000021010000290000000000000000000000010000000000000001000000000000003a07000000000000331f0100000000002700000000000000e20200000a000000331f0100000000002700000000000000480300000a000000331f01000000000027000000000000006603000020000000331f0100000000002700000000000000a00300000a000000331f0100000000002700000000000000ae0300000e000000331f0100000000002700000000000000150400000a000000331f01000000000027000000000000002804000018000000331f0100000000002700000000000000260400000e000000331f01000000000027000000000000003e04000018000000331f01000000000027000000000000003c0400000e000000331f01000000000027000000000000005c0400000a000000331f01000000000027000000000000006a0400001c000000331f0100000000002700000000000000650400000e0000000000000000000000100000000000000008000000000000009a0700000000000071200100000000002400000000000000040000001400000095200100000000001400000000000000a92001000000000023000000000000002100000012000000cc200100000000004500000000000000a9200100000000002300000000000000560000000d000000a9200100000000002300000000000000820000000d0000006a210100000000001e000000000000009a0100000c0000006a210100000000001e000000000000009d0100000c0000002423010000000000010000000000000024230100000000000100000000000000252301000000000002000000000000002123010000000000030000000000000020210100000000002b210100000000003721010000000000422101000000000049210100000000004f210100000000005d2101000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000ff030000000000005000000000000000004b000008000000f065010083e603c1c033f600000d01001c01003e0100c301000c02002302003a0200610200970200ae0200bd0200d30200ea02000103001803007e0300890300bf0300ca0300ef03008d0400d804007105008005008f0500a10500b30500d005000e0600250600300600660600710600a70600fc06000707003d07004807007e0700a607002c0800430800e508003e0a007a0a00a20a00980b00690c005d0d009a0d00060f00270f00840f006112006c1200db1300ef13000416003e1800191a00dd1b007b1c00911c00691d00bc1d00e01d00051e00261e00711e007f1e00851e00a81e00d11e00df1e001e1f00421f00531f00581f005e1f00931f00af1f00f41f00182000342000742000b32000c12000f020002121005021006321008b2100ba2100f121002022003322005622007b22008b2200ca2200ee2200ff2200042300252300d62300fa2300ab2400cf2400d424002025003c25007c2500a02500ae2500da2500112600402600532600822600b32600e22600f526002227005027007f2700922700e22700652800742800992800ba28000529001329001829004529007d2900bd2900e12900f22900212a00522a00812a00942a00a22a00cd2a00ee2a00062b00512b00622b00722c00fa2c001e2d00cd2d00f12d00a02e000c2f00152f002f2f005f2f00612f00772f00a52f00a72f00d52f00f22f000030002830004730005b30006f3000833000973000ab3000bf3000d33000e73000fb30000f31002331003731004b31005f31007331008731009b3100af3100c33100d73100eb3100ff31001332002732003b32004f32006332007732008b32009f3200b33200d432000633002e33005e3300753300ef33000034000a3400223400c134004c3500ef35008a3600323700c337003e3800733800813800ae3800e13800113900243900513900823900b13900c43900103a002c3a006c3a00903a00bb3a00bb3b00293d00cd3d00073e006b3e00933e00063f00293f00b73f00794000fd4100824200aa4200d04200fc42001843005d43008343009f4300df43001e44002c44005a4400764400bb4400e444000045004045007f45008d45000446003e46001947003847005b47006f4700824700e54700a94800e74800514a00a94a00cf4a00144b00914b00bb4b00d44b00ed4b00064c001f4c00384c004f4c008d4c00a14c00c04c00e54c001f4d00824d00a44d00c74d00e14d001b4e003d4e00604e007a4e00b44e00d34e00f84e00324f00a54f00a84f00bc4f00db4f000050003a50005c50007f5000995000d35000fb50001e51003751005151008b5100ad5100d05100ea5100245200435200685200a25200c15200e652002053003f5300605300a85300b45300d35300f85300325400545400775400915400cb5400ea54000f55004955006855008d5500c75500ef55001256002b56004556007f56009e5600bf5600075700135700325700575700915700735900885900b05900cf5900195a002a5a00855a00e65a000f5b00565b00b15b00155c003d5c00845c00a95c00ca5c00155d00235d00405d005c5d00805d00a15d00d95d00f55d00195e003a5e006f5e00b85e00c65e00255f00465f005f5f00b55f00d75f003560005a60007060008960000261005064007964009a6400ac6400bc6400d56400f46400496500af6500d665004066006866007c6600b366008167009d6d00f06d00906e00c07000f370004371008e7100067200387200ed72003e7300807300a57300c873003674005874007d7400be7400de7400ea74001d7500737600b47600cf7600e57600fa76007377009b7700a57700ed77008c7800af7800d07800e37800e67800077900167900227900397900557900b47a00397b00617b009e7b00ef7b00157c00217c00ad7d00d57d00067e00777f00977f009e7f00d88000f380000f8100ad8100dd81003b82006782006a8200a08200f682002283004d83007e8300b18300d48300f883001c84002784007484009c8400a78400e98400028500198500248500848500d985001586002886002b8600628600b88600e086006c8700908700cb8700688900908900aa8900b78900378b00668b00798b00c68b00ee8b00f88b005b8c00c58c001f8d008a8d00a48d00298e003c8e004f8e00988e00c08e00ca8e00d18e000390005090007890009b90000591005e9100779100f291000d92002a9200c49200ec92000993005793006d9300df9300529400729400c49400e49400f094005695007295007e9500ac95002196004996006c9600869600a79600e896005c9700789700849700bf9700cd9700ea9700059800339800859800a59800b19800799900a19900d09900319a000d9c00179c008a9c00b59c00cd9c00d79c00f49c00199d00a19d00c99d00db9d00319e00599e001b9f003b9f00479f005d9f007d9f00a89f00bb9f00cb9f001ca00049a00007a1003ea10061a100cda1004ba20072a2009ea200d8a20004a30056a30073a3008fa300c0a300e0a3000fa40029a40028a50009a700cca70017a8004daa006faa0092aa00b3aa00c5aa0003ab001bab0092ab00aaab00c0ab00deab0049ac0065ac0082ac0099ac0007ad0023ad00daad00f0ad003ab200bbb80024bc0042bc0054bc006cbc00e0bc0007bd00ebbd000bbe006fbe0070bf0013c10056c100a8c200bcc200e1c200f6c200bcc300e2c3000cc4001cc4003ac4007fc40093c400dcc400eec40004c50047c50062c50016c6003bc6006bc600aec600eec60015c70036c7005dc7009bc70019c8005dc800adc900c2c900c5c900e7c9001fca0026ca00f1ca0096cb00b7cb00c8cb00e1cb004dcc007dcc0087cc00bdcc007acd009bcd00adcd00aece00b9ce00dbce002ccf0044cf0089cf00f0cf0060d0006ad00098d000b1d00036d1008ad100d9d100ffd10024d20044d20062d2007cd2009fd200b2d200f8d2004cd30077d3007ad30093d3000dd40032d40077d400a8d400abd40000d50026d50056d5006dd50083d5009fd500b7d500cbd500dfd500f3d50007d6001bd6002fd60043d60057d6006bd6007fd60096d600add600c4d600dbd600efd60003d70017d7002bd70042d70058d70074d7008cd700a0d700b4d700cbd700dfd700f5d70011d80029d8003dd80054d80068d8007fd80093d800a7d800bbd800cfd800e3d800f7d8000bd90021d9003fd90059d9006dd90081d90095d900a9d900e6d90001da0010da003fda0002db000bdc0024dc0034dc0044dc007edc008fdc00a0dc00eedc000cdd001cdd002cdd0045dd0076dd002bde005adf00c7e000dde00013e10077e10090e100a7e100b6e100bee100fce1001be2002ee20052e2005ce20070e20080e20095e200c4e200e4e20043e30054e3007be300b8e300eee300fce30027e40037e4004be400c4e40018e500cce6001ee7004fe70087e7009ee700b1e700bee700e9e7000fe80016e8003de800dee80003e9000ae90073e90099e900e5e9002cea00a8ea00bdeb007dec00ccec00eeec0030ed005ded0073ed009ded00b6ed000eee0023f1003df1006bf100e3f10062f200f0f20048f30058f30097f300b1f300d7f4008ef500ddf700fcf7001ff80039f80061f800cef800e1f800f2f8000bf90037f900c2f90015fb0044fb0079fb00adfb0099fc00f3fc000bfd0034fd009ffd00b8fd0030fe004dfe0079fe00e4fe00fdfe00a8ff00c6ff00dbff00f0ff000500015200018900019d00013302013902014702014d0201530201a00301400401a00601c60601e00601f706010607012107013607014d07016607017808019009019e0901f209018f0a011b0b01770b019a0b01e50b01400c014f0c01570c01640c01900c01e30c01e90c01ee0c01070d011e0d012e0d01480d017e0d01850d018c0d01c50d01cc0d019d0e01350f016d0f01c80f01e71001a11101ca12017a1301e31301f31301d41401e41401ae1501bf1501f21601021701121701221701321701421701691701c91701331801971801cf1801e018010e1901661901801901d41901e61901511a01121b018a1b01d11c01aa1e01d71e01b11f013b20015420017320012b21013e21017f2101242201c32201e622016823018e2301e22301f82301042401b727010b28016f2801be28013f29014e29019d29011e2a01c32b01280abc000028f0db000028cadc00828908330a08330b01aca992009511d87b10207b15187b161082837c307c32017c36027c3c037c3a047b1a087c35057c34067c3a077b1a953a087b8a959af87b8a08510a49330b972208d4020a97661097cc18d46c0cd4ca0a975508821c08d45c0c9744108216976618d44606d46c0c97cc20d4ca0a7c3c089599f79533097b837b89087b7a08787c10018210208215188216109511287b7b3200490701281e03951168fb7b1090047b1588047b168004828908647551097f07648482877c7a959bff9578017b4b08330b177b48acab670797aa02330b000001c8ba0a81aac8ba0a7b14d000320a645764488210900482158804821680049511980428b10e140710000000000000807b57284507645c140a0000000000000080330b2114051800000000000080aeb958067bc5282307330805ac8902077c72017c78027b18507c76037c7c04959afb9578057b483308047b4a08ac8ae1067c7a057b1a407c7a067b1a287c7a077b1a387c7a087b1a30959af7957b097b4b7b4a08531a04ba067c78097b18687c780a7b1888007c780b7b18607c780c7b18209599f395770d7b477b490864477b12187b16107b1c0850100a330e01821ad000837752077a067b15b00083874921c8019519d0013302087b12d0014921d8015107d508330c9788209888201407f8ffffffffffff7f7b174882146882106082168800287c06645764488210900482158804821680049511980428d719645764488210900482158804821680049511980428480a9517300364483300129511d0fe7b1028017b1520017b16180164857b1764873300e00128930d018218300314070000000000000080aa78e2058218400382193803821a30037b58187b59107b5a08140709000000000000807b5728ca05645764488210900482158804821680049511980428841714070f000000000000807b5728a405330a0514081800000000000080aea9e4047b58288e0564576448821090048215880482168004951198042853116457644882109004821588048216800495119804281c13645764488210900482158804821680049511980428d008330809ac8928057c78027c7a017c7b037c7c04978808d4a80897bb1097cc18d4cb0bd4b8087c7a067c7b057c7c077c760897aa08d4ba0a97cc10976618d46c0cd4ca0a97aa20d4a8089599f79577097b477b4908140714000000000000807b577b580828e3049517300364485010241a378218300314070000000000000080aa78ba048218400382193803821a30037b58187b59107b5a08140705000000000000807b5728a20495173003644850102849fc8217300352077504821738037c184003140915000000000000807b597b570878581028720437075827034921c8014921d001014921d8015107f5037b19a80033066e783302204921f000017b15b00028118215b0008218b800c938085108cb01821ac801330730e67803c96a077b18b8007b13c800ae3772c83605ac656707330c5001d6c5083307520865079659500157095d07510a1896a750018218f0007b1830037b17400333070128053307017b1738039517e001330801951a300350102c352f8217e00152070a078217e8017b17f0007b17d0017b15c8018214d0003302208213c80001966550019a377b17c00033000133070128457b18f000c858079518e0013309500150102e1b2c019566017b16d8018213c800821be000d83b0795bb018218c000c8b808955550018214d00033022064b05118011aff7b15e8007b16f800824908ac29c60082459598e09556207b467b4808ac28b5009598c0955b407b4b7b4808330a9000aca8a200959a30ff9558d0007b487b4a08330c8000acca8d009597b0fe955950017b497b470895173003330980007b10e0007b1bd800501030822b013309209517b0036458501032732b013309209517d0036468501034642b01330990009517f0038218d800501036522b019517e0019518300333095001501038402b018217c8018216f8008218f0008215e800ab7600ff9517c80150103a9a318218d00128effe847701520754028215b0008216f8008218b800c9380852083dfe8217c801140800000000000000808219a800821af000ab87f5012838026457644882109004821588048216800495119804282d12951730036448501040e13f8218300314090100000000000080aa9813028218400382193803821a30037b58187b59107b5a08140712000000000000807b5728fb0195173003644850104432348218300314070000000000000080aa78d2018218400382193803821a30037b58187b59107b5a0814070a000000000000807b5728ba01330805ac89990164537c72017c76027c7b037c7c049595fb9578057b48330a207b4508aca51e01959adb9575257b457b4a08510a0f017c75259599da9577267b477b490851059203521501f9004921f8000128890395173003644850104a9e408218300314090100000000000080aa983c018218400382193803821a30037b58187b59107b5a08140716000000000000807b5728240195173003644850104e5b338218300314070000000000000080aa78fb008218400382193803821a30037b58187b59107b5a08140713000000000000807b5728e300140717000000000000807b5728d4009599df9577217b477b490864c695c7083309205010524d29019555f67b6528b3007c7a027c7b017c7c037d760497aa08d4ba0a97cc10976618d46c0cd4ca0a9599fb9577057b477b4908140803000000000000807b587a5a082878140718000000000000807b37286a3306330a010137085827036e88e6860814090d000000000000807b597b57087b5a107b5818955720951800013309b800501054c7280128328217c8018215b00051070d8217f000501056e42501140718000000000000807b57280f140718000000000000807b57018210900482158804821680049511980432008477015207f50182185882179800c9780851085602821bc80133070004e67809c9cb077b18587b199800ae9772c89c05acc5200398583c330752083303975904821a48ac9a29037b1cf800510b1397bb047b12e0017b1bf00133070828053307017b17e80195170001330808951ae001501058dd2a821700015207e202821208017b12d0017b15c801821ad000821468821060821cf800821998000197c5049a977b1790003303013307012893009766088217f000d467078218e8009788108219e000979918d49808d487078218a000978808d4b8088219b800979910821ba80097bb18d4b909d498088219c000979908d4c909821bd80097bb10821cc80097cc18d4cb0bd4b909979920d49808c852097a977b9808821cf80095cc017b1cd80182179800d8730795330182189000c8380895551082168800511801cefe82a808330903ae89bcfe7b1cf80082a97c9b7b1bf0007c96017c9b027b1be8007c9b037b1be000958bfc959c047bac7bab08541b07797c9b047c97057b17a0007c97067b17b8007c97077b17a8007c9c087c97097b17c0007c970a7b17d8007c970b7b17c8008217c80195990c7ba99588f47ba8088218f800ab78f7fe9517c8017b1380007b1c787b1b7050105a232a821b70821c7882138000821060821468821ad0008212d00128c8fe84770182168800821cf800510713fe8217c801510790008217d00150105cad23012882004921f80001976608d4260797bb1097cc18d4cb0bd47b05643695370c33092050105e512601140708000000000000807b677a65088217f80078672c28a6fd82980882997b18c0017b19b801821468821060821c2082168800283a9519d00182988217c8018299087b18b801140800000000000000807b19c001821c20ab87168215b000140718000000000000807b572852fd821850978808821918d49808821910979910821a0897aa18d4a909d49808821928979908821a40d4a909821a3897aa10821b3097bb18d4ba0ad4a909976a08d44a0a970b1097cc188216c0018215b801d4cb0bd4ba0a821cb0007bc6187bc510140b0b000000000000807bcb7bc7087ac8207ac9247aca2895c72c951830033309ac005010605b250128c6fc8217e8018218f0013309a82f01282cda0033073309a82f012821da003309a82f012818da0033073309a82f01280dda0082170801821810013309a82f0128fcd9003309a82f0128f3d9009511e07b10187b15107b1608828908140a0000000000000080330b0414051100000000000080acb91f828b7cb27cbc017cb4027cb3039596fc95ba047b8a7b860855162012140518000000000000807b75283b97cc08d42c0c974410973318d44306d46c0c7b1c9599dc95bb247b8b7b89089578083309206476648764a85010628a24017b6582177a6728018210188215108216089511203200951188fe7b1070017b1568017b166001828b083309027b1718ae9b15140718000000000000808218187b8728da028287140affffffff000000007c797b19107c79017b190895bbfe9577027b877b8b087b173a0760270349211001492118010149212001330551075902d2a7073302017b1a207b18302810821a2001821728c9b70751072902821a10017b172833090002e6970bc95a077b1b40aeb75fc8b506ac56880298683b330752088a0297690557098302510a1397aa057b1240017b1a500133070128053307017b17480195172801330801951a400150106465268217280152073502821230017b1218017b161001821830821b40019756059ab77b1738330c01330701283dc8620795184001330920642550106659230164528215509555017b152001821b40821c48d8bc0795cc01821938c8c90995662082183051190139ff82890853192047018287959ae09579207b897b8a087c78017c797c7a027c7b03978808d4980897aa1097bb18d4ba0ad4a8087c79057c7a047c7b067b1c487c7c07979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1840017c78097c79087c7a0a7c7b0b978808d4980897aa1097bb18d4ba0ad4a8087c790d7c7a0c7c7b0e7c7c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1848017c78117c79107c7a127c7b13978808d4980897aa1097bb18d4ba0ad4a8087c79157c7a147c7b167c7c17979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1850017c78197c79187c7a1a7c7b1b978808d4980897aa1097bb18d4ba0ad4a8087c791d7c7a1c7c7b1e7c771f979908d4a90997bb10977718d4b707821a1001d49707977720d487077b1758017b1550aba58ffe95171001501068f32582121801287efe847701821a205107e8fd82171001510780fd642750106a001f012874fd8217100114080000000000000080aa8763fd2806330201013a08602703821908979908821b10d4b909d2a808e68508140a0c00000000000080821b187bba7bb7087bb2107bb81879b92095b72295185a3309b60050106c6f210182107001821568018216600195117801320082173001821838013309a82f012831d60033073309a82f012826d6003309a82f01281dd600828308330a20aea311140818000000000000807b7832009511f0fd7b1008027b1500027b16f801828b9532e095bc207b8c7b82087cbc017cb67cb5027cba0397cc08d46c0c97551097aa18d45a0ad4ca0a7cbc057cb6047cb5067cb90797cc08d46c0c975510979918d45909d4c909979920d4a9097b1940017cb9097cba087cbc0a7cb60b979908d4a90997cc10976618d46c0cd4c9097cba0d7cbc0c7cb60e7cb50f97aa08d4ca0a976610975518d46505d45a0a97aa20d4a9097b1948017cb9117cba107cbc127cb613979908d4a90997cc10976618d46c0cd4c9097cba157cbc147cb6167cb51797aa08d4ca0a976610975518d46505d45a0a97aa20d4a9097b1950017cb9197cba187cbc1a7cb61b979908d4a90997cc10976618d46c0cd4c9097cba1d7cbc1c7cb61e7cb51f97aa08d4ca0a976610975518d46505d45a0a97aa20d4a909330c087b195801acc209017cb4207cb9217b19f8007cba227cb9237b1900017cb2247cb0257cb5267cb62795b9287b899539d87b8908531908da007b1af0007cb9287b19d8007cb9297b19b8007cb92a7b19c8007cb92b7b19c0007cbc2c7cb92d7b19d0007cb92e7b19e8007cb92f7b19e0009539d095ba307b8a330a087b8908aca98f007cb9307b1998007cb9317b19787cb9327b1988007cb9337b1980007cb9347b19b0007cb9357b1990007cb9367b19a8007cb9377b19a00095b9387b899539c87b8908531908497b1c707cb9387b19507cb9397b19307cb93a7b19407cb93b7b19387cb93c7b19687cb93d7b19487cb93e7b19607cb93f7b19589539c095ba407b8a330c80007b8908aec922140818000000000000807b780182100802821500028216f8019511100232007b107b14087b16107b15187b12207b1728953740ff95b9c0007b897b87089517600133098000648564a850106e921e019517e0016458501070a5338218e00114090100000000000080ab9814140818000000000000808217287b7828938215f800975508821708d457078218f00097881082160001976618d46808d487078218978808821920d49808821918979910821a1097aa18d4a909d49808978820d487077b1700018217b8009777088218d800d487078218c8009788108219c000979918d49808d487078218d000978808821970d498088219e800979910821ae00097aa18d4a909d49808978820d487077b17f80082177897770882189800d487078218880097881082198000979918d49808d48707821890009788088219b000d498088219a800979910821aa00097aa18d4a909d49808978820d487077b17f000821730977708821850d48707821840978810821938979918d49808d48707821848978808821968d49808821960979910821a5897aa18d4a909d498089788208219f001821ae801821be001d478067b1918017b1a10017b1b08019517200195184001330920501072181d018215289557589518600133098000501074041d018217080182181001821918017b577b58087b5910821720018218280182193001821a38017b57187b58207b59287b5a30821700017b57388217f8007b57408217f0007b57487b565028e8fd9511c07b10387b15307b1628828308140a0000000000000080330b2014040700000000000080acb32a01828a9532e095ac207b8c7b82087cac017ca57ca6027cab0397cc08d45c0c97661097bb18d46b0bd4cb0b7cac057ca6047ca5067ca90797cc08d46c0c975510979918d45909d4c909979920d4b9097b19087ca9097cab087cac0a7ca60b979908d4b90997cc10976618d46c0cd4c9097cab0d7cac0c7ca60e7ca50f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19107ca9117cab107cac127ca613979908d4b90997cc10976618d46c0cd4c9097cab157cac147ca6167ca51797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19187ca9197cab187cac1a7ca61b979908d4b90997cc10976618d46c0cd4c9097cab1d7cac1c7ca61e7ca51f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b909330b087b1920aeb211140818000000000000807b7828767ca9217cab207cac227ca623979908d4b90997cc10976618d46c0cd4c9097cab257cac247ca6267ca52797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b199539d895aa287b8a7b8908957a08951808330920647664a714050700000000000080501076ef1a017b6582177b67280182103882153082162895114032009511a87b10507b15487b1640828208330b20140a01000000000000807b1a18acb25901828a9523e095ac207b8c7b83087cac017ca67ca5027cab0397cc08d46c0c97551097bb18d45b0bd4cb0b7cac057ca6047ca5067ca90797cc08d46c0c975510979918d45909d4c909979920d4b9097b19207ca9097cab087cac0a7ca60b979908d4b90997cc10976618d46c0cd4c9097cab0d7cac0c7ca60e7ca50f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19287ca9117cab107cac127ca613979908d4b90997cc10976618d46c0cd4c9097cab157cac147ca6167ca51797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19307ca9197cab187cac1a7ca61b979908d4b90997cc10976618d46c0cd4c9097cab1d7cac1c7ca61e7ca51f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b909330c087b1938acc3327ca0207ca9217ca6227cab237ca5247b15107ca4257ca5267b15087ca5277b1595a3287b839525d87b85085515081b8218189588177b78018210508215488216409511583200979908d4090997661097bb18d46b0bd4b909974408821b10d4b40b821c0897cc108216976618d46c0cd4cb0b97bb20d4b9097b19107ca9297cab287cac2a7ca52b979908d4b90997cc10975518d45c0cd4c9097cab2d7cac2c7ca52e7ca62f97bb08d4cb0b975510976618d46505d45b0b97bb20d49b059529d095aa307b8a7b8908957a08951820330920647664a7501078b518018217187b678217107b67287b6530284fff9511c07b10387b15307b1628828308140a0000000000000080330b2014040600000000000080acb32a01828a9532e095ac207b8c7b82087cac017ca57ca6027cab0397cc08d45c0c97661097bb18d46b0bd4cb0b7cac057ca6047ca5067ca90797cc08d46c0c975510979918d45909d4c909979920d4b9097b19087ca9097cab087cac0a7ca60b979908d4b90997cc10976618d46c0cd4c9097cab0d7cac0c7ca60e7ca50f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19107ca9117cab107cac127ca613979908d4b90997cc10976618d46c0cd4c9097cab157cac147ca6167ca51797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b19187ca9197cab187cac1a7ca61b979908d4b90997cc10976618d46c0cd4c9097cab1d7cac1c7ca61e7ca51f97bb08d4cb0b976610975518d46505d45b0b97bb20d4b909330b087b1920aeb211140818000000000000807b7828767ca9217cab207cac227ca623979908d4b90997cc10976618d46c0cd4c9097cab257cac247ca6267ca52797bb08d4cb0b976610975518d46505d45b0b97bb20d4b9097b199539d895aa287b8a7b8908957a08951808330920647664a71405060000000000008050107ada16017b6582177b6728018210388215308216289511403200951168ff7b1090007b1588007b168000828c08140a0000000000000080330b0414050200000000000080acbca50082837c397c34017c3b027c3a037b1a7895c6fc953a047b8a3302087b8608ac2683007c3a047b1a587c3a057b1a387c3a067b1a487c3a077b1a407c3a087b1a707c3a097b1a507c3a0a7b1a687c3a0b7b1a6095300c7b80649a95c9f47b89085319084264b07c390c7b19207c390d7b197c390e7b19107c390f7b19087c3b107c39117b19187c39127b19307c39137b192895c9ec9532147b827b89085529800024140518000000000000807b7501821090008215880082168000951198003200953994007b8995c96cff7b890864bc974b08d4ab08970910821478974418d49409d498087b1878821a3897aa08821858d4a808821948979910821a4097aa18d4a909d49808821950979908821a70d4a909821a6897aa10821b6097bb18d4ba0ad4a909979920d498087b18708218978808821920d49808821910979910821a0897aa18d4a909d49808821918979908d4c909821a3097aa10821b2897bb18d4ba0ad4a909979920d498087b18689578083309800064766487642850107c1615017b658217707b6788008217687b6790008217787a6798002817ff9511e07b10187b15107b1608828908140a0000000000000080330b0414050400000000000080acb91f828b7cb27cbc017cb4027cb3039596fc95ba047b8a7b860855162012140518000000000000807b75283b97cc08d42c0c974410973318d44306d46c0c7b1c9599dc95bb247b8b7b89089578083309206476648764a850107e7814017b6582177a6728018210188215108216089511203200951178ff7b1080008089348277849a10520a3e8499205209757c7a531a64c700648796a82998880c968964bf9a0a97aa3998aa383309da0f01c8a9097c9a017c9b3309781a02781b01648a28a1007c77330a810095197f0164ab847a0f98770488a20a86ac3095aa57db2c0a789a9599ff95baff5207e495b7fe56278000bb009aab8100641ac87a0a283e7c77330a810095197f0164ab847a0f98770488a20a86ac3095aa37db2c0a789a9599ff95baff5207e495b7fe562780008b009aab8100641ac87a0a01330902648733082523015020820069ce0082108000951188003200330902551a0a1364870186a830641ac89a0a78a82821648797aa3998aa383308da0f01c8a8087c89017c8878190278180133090101641ac89a0a859b0333080133095020840016ce00821080009511880032003309002d0133088000285ecc003309002d01330880002851cc009511d87b10207b151864858288288279825720828a187b193308720801330906b4a00288007b15087817104611113308e40601330bee00951708330905641a50208a0061d9007c19117c1810d4890751094284880152083c8217087c783484880452081a827828827720828a1833085a0f0133090233008c00320a827828827720828a183308590f01330901b4a0028c008477018210208215189511283200827728ee09019511c07b10387b15307b162864858288288276825720828a183308013309b4a00292007b15087817104901461111017b16189566087b16203305d20064179518183309d20050209400d0dd006417951820645950209600c2dd0082187c19108e87d49707510859849901520953521801367c17118215085107307c5734847704520727825828825720828a1833085f0f01330901b4a00298006478330701510809281c82150801825828825720828a183308dd0e01330901b4a0029a008477018210388215308216289511403200827728013c827728aa04019511d07b10287b152082798297140a00000000000000806485aba78900825828825720828a187b1933087f0501330903b4a002a2007b15107817184911084611193309200195170864185020a400f2dc008218087c19188e87d497075108f9008499015209f200521801b9007c17198215105107b2007c57348477045207a800825828825720828a1833085f0f01330901b4a002a60064783307015108890028b600825828825720828a187b1933087d0501330902b4a002a8007b15107817184911084611193309860095170864185020aa006ddc008218087c19188e87d4970751087484990152096e521801517c171982151051074b7c5734847704520742825828825720828a1833085f0f01330901b4a002ac006478330701510824283782151001825828825720828a183308dd0e013309013300ae00320a82151001825828825720828a183308dd0e01330901b4a002ae0084770182102882152095113032009511c07b10387b15307b1628827764858288288276088277107b1708825720828a183308e10e01330901b4a002b2007b1510821508781718461119510535975504330790007b17019567107b17087b162095171095182082195020b400c9dc009555f08216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002b6006478016487821038821530821628951140320082777c779777033309b80c01c879098299330a482b01828b28c8a707827a82872082bc1864a8320c9511c07b10387b15307b1628827764858288288276088277107b1708825720828a183308e10e01330901b4a002bc007b151082150878171846111951053b975703975505c975053307f6007b17019567187b17087b162095171095182082195020be00f9db009555e88216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002c000647801648782103882153082162895114032009511c07b10387b15307b162864858288288276825720828a183308013309b4a002c4007b15087817104901461111017b16189566087b16203309d60064179518185020c60026da003309d20064179518205020c80016da0082187c19108e87d49707510859849901520953521801367c17118215085107307c5734847704520727825828825720828a1833085f0f01330901b4a002ca006478330701510809281c82150801825828825720828a183308dd0e01330901b4a002cc008477018210388215308216289511403200827728ff0c82777c79648751090f33085d05013309042833cb0033081a11013309052827cb00951178ff7b1080008089348277849a10520a1584992052094d82108000951188002832e500827b3309810095177f01649a98bc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000689a9b8100641ac87a0a283f827b3309810095177f01649a98bc0484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520ce295a7fe56278000379a9b8100641ac87a0a01330902648733082523015020d400fcc700821080009511880032003309002d01330880002844c6003309002d01330880002837c600951178ff7b1080008089348277849a10520a1584992052094d82108000951188002896cf00817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000689a9b8100641ac87a0a283f817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520ce295a7fe56278000379a9b8100641ac87a0a01330902648733082523015020d80027c700821080009511880032003309002d0133088000286fc5003309002d01330880002862c500827728593582798297140a0000000000000080aba716828928828720829c183308690501330904320c9511d07b10287b1520828a2882872082ab187b19330a51050133090464853308510501b4b002de007b15107817184911084611193309f60095170864185020e00065d7008218087c19188e87d49707510859849901520953521801367c17198215105107307c5734847704520727825828825720828a1833085f0f01330901b4a002e2006478330701510809281c82151001825828825720828a183308dd0e01330901b4a002e40084770182102882152095113032009511c07b10387b15307b1628648582882882768277087b1708825720828a183308e10e01330901b4a002e8007b151082150878171846111951053b975703975505c975053307f6007b17019567187b17087b162095171095182082195020ea00d9d7009555e88216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002ec00647801648782103882153082162895114032009511c07b10387b15307b1628827764858288288276088277107b1708825720828a183308e10e01330901b4a002f0007b1510821508781718461119510535975505330704017b17019567207b17087b162095171095182082195020f20037d7009555e08216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002f400647801648782103882153082162895114032009511c07b10387b15307b162882777b1808828928827508827610828720829a183308e10e01330901b4a002f8008218087b1810781718461119510632330780007b17019557017b17087b152095171095182082195020fa009ad6009566ff8215085206e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002fc00647801648782103882153082162895114032009511807b10787b15707b1668827664853307800033081f6469015107137d9a9599019577ffada8f5510af2282a3309800001c876087c8851080b957701ab97f528056479016417646850200001d5cd00821751073f7b164911080864177b17583307aa077b17603307d032017b1728491130029517587b1738825720825828491140014911489519283300020128cec9008217088218107b17187b18209517187b17583307b8077b17603307b032017b1728491130024911489519588257208258287b193849114001951928502002018fc9008210788215708216689511800032009511d87b10207b151864858288288279825720828a187b1933086d0501330904b4a00206017b150878171046111133086a0801330bce00951708330903641a50200801cdce007c19117c1810d4890751094284880152083c8217087c783484880452081a827828827720828a1833085a0f0133090233000a01320a827828827720828a183308590f01330901b4a0020a018477018210208215189511283200827728ba219511b87b10407b15387b16308277648582882882768277087b17825720828a183308350501330904b4a00210017b15107817184911084611198217c967079877031408abaaaaaaaaaaaaaaca87077b16207b17283309e6009517089518205020120124d3008218087c19188e87d49707510859849901520953521801367c17198215105107307c5734847704520727825828825720828a1833085f0f01330901b4a00214016478330701510809281c82151001825828825720828a183308dd0e01330901b4a002160184770182104082153882163095114832009511c07b10387b15307b1628827764858288288276088277107b1708825720828a183308e10e01330901b4a0021a017b15108215087817184611195105359755043307c2007b17019567107b17087b1620951710951820821950201c0198d3009555f08216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a0021e01647801648782103882153082162895114032003309026487330815050128aac3009511d07b10287b15207b161864868288288275826720828a189558087b1833086d0801330905b4a00224017b16087817104611113308610501330b3601951708330904645a5020260199cc003308b70501330b8000951708330908641a5020280181cc007c19117c1810d4890751094284880152083c8217087c783484880452081a827828827720828a1833085a0f0133090233002a01320a827828827720828a183308590f01330901b4a0022a018477018210288215208216189511303200951178ff7b108000808934849a10520a6e8499205209aa007e79989a04532a7102fd00648796a84703988813968a1027bfa909979a3098aa3296aa7b1498ab1198aa1084aafe0796bb64bfb909979931989930330bda0f01c8ba0a7cac7caa01c8b9097c9b017c99781a02781c01781b04781903330b01648928de007e7b3309810095177f01649a97b930989c3484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520cdf95a7fe56278000f5009a9b8100641ac87a0a28437e7b3309810095177f01649a97b930989c3484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520cdf95a7fe56278000c0009a9b8100641ac87a0a013309026487330825230150202e0160bf0082108000951188003200330b05330ada0f015319642e989b0296bb7b1498bc1196c764bf7909979931989930c8a9097c97017c99781704781903330b0364c90155190a1664870195bbff6418c8b80886993078892820648797993198993095bbfec8a9097c98017c99641ac8ba0a78a80178a901641ac8ba0a9abb05330801330950203001d8be00821080009511880032003309002d01330880002820bd003309002d01330880002813bd00951178ff7b108000808934849a10520a1584992052094d82108000951188002874c600817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000689a9b8100641ac87a0a283f817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520ce295a7fe56278000379a9b8100641ac87a0a01330902648733082523015020340105be00821080009511880032003309002d0133088000284dbc003309002d01330880002840bc00951178ff7b108000808934849a10520a1584992052094d82108000951188002868da00827b3309810095177f01649a98bc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000689a9b8100641ac87a0a283f827b3309810095177f01649a98bc0484bb0f88b20a86b93095bb37db290b787b9577ff95a9ff64cb520ce295a7fe56278000379a9b8100641ac87a0a01330902648733082523015020380132bd00821080009511880032003309002d0133088000287abb003309002d0133088000286dbb009511d87b10207b15187b1610647a82771408ffffffffffffff7fc8870988991714080000000000000080d378089398330916ac89fc009788023309600001c898088188c89808320882a70852071a28e00082a70814080000000000000080d48707aa87cd0082a71028b60082a6187b1a82a7107b170851069d00821508955508280c9566ff95551851068b008257f85107f3825750204001c9fe0028e714080000000000000080d48707aa87840082a708286d82a6187b1a82a7107b1708510655821508955508280b9566ff9555185106448257f85107f482575020460183fe0028e882a6187b1a82a7107b1708510625821508955508280b9566ff9555185106148257f85107f4825750204a0153fe0028e882178278088217085108138210208215188216109511282833fe0082102082151882161095112832009511d07b10287b15207b16186485828828825a20828b1864763308e10e0133090164a7b4b00250017b157817084611097b16103305800064179518103309800050205201a3cd009567017b171064179518106459502054018fcd009567027b171064179518106459502056017bcd009567037b1710641795181064595020580167cd009567047b17106417951810645950205a0153cd009567057b17106417951810645950205c013fcd009567067b17106417951810645950205e012bcd009567077b1710641795181064595020600117cd009567087b1710641795181064595020620103cd009567097b17106417951810645950206401efcc0095670a7b17106417951810645950206601dbcc0095670b7b17106417951810645950206801c7cc0095670c7b17106417951810645950206a01b3cc0095670d7b17106417951810645950206c019fcc0095670e7b17106417951810645950206e018bcc0095670f7b1710641795181064595020700177cc009567107b1710641795181064595020720163cc009567117b171064179518106459502074014fcc009567127b171064179518106459502076013bcc009567137b1710641795181064595020780127cc009567147b17106417951810645950207a0113cc009567157b17106417951810645950207c01ffcb009567167b17106417951810645950207e01ebcb009567177b17106417951810645950208001d7cb009567187b17106417951810645950208201c3cb009567197b17106417951810645950208401afcb0095671a7b171064179518106459502086019bcb0095671b7b1710641795181064595020880187cb0095671c7b17106417951810645950208a0173cb0095671d7b17106417951810645950208c015fcb0095671e7b17106417951810645950208e014bcb0095661f7b1610641795181064595020900137cb003307017c180852081b8217827828827720828a183308610f01330901b4a002920182102882152082161895113032009511c07b10387b15307b162864859888205208b1006476645764987b191850209401132e821818510574975703975505c97507956510282d7b18827708c88707821808646950209601c6fd008218188217c876067b86108217109577e895551851073b7b1710825698672052073e8257f87b1708646750209801bb2d8217188278108279c98909ae69b1646950209a0133d800821718827810289f82103882153082162895114032003307170501330ab02301330be82301330816951927283ec8003307170501330ab02301330be823013308169519272825c8009511c87b10307b15287b162082ab0864866478510b3c82a71051073b7b177b181882a77b17107b1908649850209c0145f6005107406475821810821950209e01f4fc008217105020a0011efa00282f51091f280551091a7b1818649864955020a20112f600821818645964752817646528133305821908821818280982190882181801885701db55067b86087b89107b8782103082152882162095113832009511b87b10407b15387b16306475827a95a70151077c97a801e4780898893c330752096e330904e498069769043308f1140bf8ffffffffffff7fac9b55510a1482570897aa047b17187b1a2833070828053307017b17206417330808951a185020a40102ff82175207198217087b57087b5682104082153882163095114832008217088218103309c02f0128edb0003309c02f0128e4b0009511b87b10407b15387b16306475827a95a70151076f97a801e4780898893b3307520961330904e49806976905570955510a1482570897aa057b17187b1a2833070128053307017b17206417330801951a185020a60177fe82175207198217087b57087b5682104082153882163095114832008217088218103309c02f012862b0003309c02f012859b0009511b07b10487b15407b16386476827a95a701648c51077797a801e47807330804e487053308d800d68509330752095f9659d8003308f1140bf8ffffffffffff7fac9b5f7b1c510a1582670896a8d8007b17207b183033070828053307017b1728951708330808951a205020a801d4fd82170852071f8217107b67087b65821048821540821638951150320064c928c7af00821710821818821c64c928b9af0064c928b3af009511b87b10407b15387b16306475827a95a70151077c97a801e4780898893a330752096e330904e498069769063308f1140bf8ffffffffffff7fac9b55510a1482570897aa067b17187b1a2833070828053307017b17206417330808951a185020aa0139fd82175207198217087b57087b5682104082153882163095114832008217088218103309c02f012824af003309c02f01281baf009511b87b10407b15387b16306475827a95a70151078c0097a801e47807330804e48706330718d676083307520874976803976905c989093308f1140bf8ffffffffffff7fac9b5b510a1a82570897a80397aa05c98a0a7b17187b1a2833070828053307017b17206417330808951a185020ac0191fc82175207198217087b57087b5682104082153882163095114832008217088218103309c02f01287cae003309c02f012873ae009511b87b10407b15387b16306475827a95a70151077597a801e47807330804e4870633085001d68609330752095d96695001570956510a1582570896a850017b17187b182833070128053307017b17206417330801951a185020ae0100fc82175207198217087b57087b5682104082153882163095114832008217088218103309c02f0128ebad003309c02f0128e2ad009511b87b10407b15387b1630c88909ac895464758277977801330a08e4a909e498065706415107108258087b18187b1728330701017b17206417330801951a1864695020b00185fb82175207248217087b57087b56821040821538821630951148320033073309302f012874ad008217088218103309302f012865ad00827a08827910648764a828cbb5009511c07b10387b15307b162864858288288276088277107b1708825720828a183308e10e01330901b4a002b6018218087b15107817184611195108389685d80033070c017b17019567d8007b17087b162095171095182082195020b80109c500955528ff8216085205e07c17188477010133080152071f821710827828827720828a183308610f01330901b4a002ba01647801648782103882153082162895114032009511c07b10387b15307b162864858288288276088277107b1708825720828a183308e10e01330901b4a002be017b15108215087817184611195105359755043307c2007b17019567107b17087b162095171095182082195020c00168c4009555f08216085205e27c17188477010133080152071f821710827828827720828a183308610f01330901b4a002c201647801648782103882153082162895114032008279140a0000000000000080aba916828928828720829c183308690501330904320c9511d07b10287b1520828928828a20829c187b17330b51050133090464a764853308510501b4c002c6017b15107817184911084611193309f60095170864185020c80175c2008218087c19188e87d49707510859849901520953521801367c17198215105107307c5734847704520727825828825720828a1833085f0f01330901b4a002ca016478330701510809281c82151001825828825720828a183308dd0e01330901b4a002cc018477018210288215209511303200951110ff7b10e8007b15e0007b16d80064857b1764875020ce013cd5008377510724140700000000000000808218017b878210e8008215e0008216d8009511f0003200838749219000951698004911280849219800084921a000510710044911389788209888201407f8ffffffffffff7f7b17086487140800000000000000807b18107b154028228217387b173828067b1738018217208477015207a103821718c987075107ea03821a90007b17183308aa02e68709821838c98a077b1930ae9742c89805ac852604330718d67508330752083604975803975905c98909821b08ac9b2604510a2097a70397aa05c97a0a8217287b17c0007b1ad0003307082809649828383307017b17c8009517a800330808951ac0005020d00108f88217a8005207d5038217b0007b17287b1798007b159000821540821830018253083302491120018217382838952201821938979703979805c978086497821928c898087b8a8219687b89087b8b109577017b17a000821830d882097b1920aa8215ff7b1738510312ff82577c7a9536ff9579017b597b5608491168017b1248510a6f522aff00820053130985027c78027c79017c7a037c7b04978808d4980897aa1097bb18d4ba0ad4a8087c79067c7a057c7b077c7c08979908d4a90997bb1097cc18d4cb0bd4b909979920d489099533f79577097b17607b577b5308989720951698008218305107c7002880fe646364ab9516980082179000821838aa78a201282bff6ca857080b33054921c000284a84874033050151073a84872033050251073184871033050351072884870833050451071f84870433050551071684870233050651070d8487013305075207a6024921c000ac5683019517c0007b187864987b197064595020d201cbf300821248c95603821770c857078219407b17607b978557079b77ff821878e078078218c0007b9308975503cf57076495c87809989720951698008218305207c1fdac93bdfd5109de006498330a33064911680101330700407b1858e67805c96a077b1370ae5721c85609ac69db015709d701510a188217687b17c0007b1ad000330701280ac8560b28353307017b17c8009517a800330801951ac0007b19785020d401f6f58217a8005207a7018217b0007b1768821b7864ba82137001ac6b7b01ac539d00821768c86707821660646864597b1a507b1b785020d601edf200821b78821a50821370821248c95303c856068217407b16607b76821858c958087b730864b6520850ff82154095169800821710aa7a820082179000821838ab78a2fd2811330a330b82179000821838ab7891fd95179000643664a57b1b785020d80158f8821b78645a646395169800821540821248821798007b17282864fd510a238217685020da0195ef0082172084770182154095169800821830510799fc2832821540951698008217388218307b17382879fc8217388218307b1738286dfc82172084770195169800821830510767fc821798007b1778821538510534821678956608280b9555ff9566185105238267f85107f482675020dc0122ef0028e882680882697b1888007b1980002836821790005107a5fb8217785020de01ffee002897fb8268821790008269087b188000140800000000000000807b198800aa8779fb8218880082198000821a7ba81064a87ba9082870fb3309902f01646764b828c4aa0033073309a82f012860a6008217b0008218b8003309a82f01284fa60033073309a82f012844a6008217b0008218b8003309a82f012833a6003309a82f01282aa6003307782f012807b20083775107121407000000000000008082182893027b151883874921d0009519d800330b087b1b407b1bd8004921e00051073002330a9788209888201407f8ffffffffffff7f7b1708330407821318281384770152076502821810c9080851081102821bd00033070004e67800c9ab077b18107b1020ae0770c80a05aca5640298583c330752086602975904821c08ac9c5c0264a6510b1697bb048217407b1700017b1b100133070828053307017b1708019517e800330808951a00015020e2014af38217e800520708028217f0007b17407b17d8007b15d000821318646a3304078210200197ac049a077b173833020133070128b4009755088217a000d4570782189000978810976618d46808d48707821898009788088219b800d498088219b000979910821ba80097bb18d4b909d49808978820d48707821848978808821968d49808821958979910821b5097bb18d4b909d49808821960979908821b8000d4b909821b7897bb10821c7097cc18d4cb0bd4b909979920d49808821940821c8800c8c9097b977b980895aa017b1ae000d80207952201821838c8280895cc10330901aa98b4fe823808ae84a6fe82397c9b7b1ba0007c95017c9b027b1b90007c96037c9b047b1bb8007c9b057b1b98007c9b067b1bb0007c9b077b1ba800959b087b3b958bf87b3b08aeb466fe7c97087b17687c97097b17487c970a7b17587c970b7b17507c970c7b1780007c970d7b17607c970e7b17787c970f7b17708217d0009599107b399588f07b38087b1c8800ab7ac4fe9517d0007b1a307b12285020e40165f2821228821020330407821a308213188217d8007b1740289afe82980882997b18c8007b19c00028259519d80082988217d0008299087b18c000140800000000000000807b19c800aa8778fd8218c8008219c000821a7ba81064a87ba908017b878210280182152001821618019511300132008217d000510749fd8217d8005020e601a6eb00283afd8217f0008218f8003309a82f012842a30033073309a82f012837a3003309a82f01282ea3007c779777033309b80c01c879098299330a482b01828b28c8a707827a82872082bc1864a8320c9511d07b10287b15207c7964855219078c00825828825a20828b187b1733087d050133090264a7b4b002ec017b15107817184911084611193309200195170864185020ee0189b9008218087c19188e87d497075108fb008499015209f400521801bb007c17198215105107b4007c57348477045207aa00825828825720828a1833085f0f01330901b4a002f001647833070151088b0028b800825828825a20828b187b1733087f050133090364a7b4b002f2017b15107817184911084611193309b80095170864185020f40102b9008218087c19188e87d4970751087484990152096e521801517c171982151051074b7c5734847704520742825828825720828a1833085f0f01330901b4a002f6016478330701510824283782151001825828825720828a183308dd0e013309013300f801320a82151001825828825720828a183308dd0e01330901b4a002f80184770182102882152095113032009511d07b10287b15207c79648551098e00825828825a20828b189577017b1733087f050133090364a7b4b002fc017b15107817184911084611193309b80095170864185020fe012bb8008218087c19188e87d497075108fe008499015209f700521801be007c17198215105107b7007c57348477045207ad00825828825720828a1833085f0f01330901b4a0020002647833070151088e0028bb00825828825a20828b189577087b1733087d050133090264a7b4b00202027b15107817184911084611193309d200951708641850200402a1b7008218087c19188e87d4970751087484990152096e521801517c171982151051074b7c5734847704520742825828825720828a1833085f0f01330901b4a00206026478330701510824283782151001825828825720828a183308dd0e0133090133000802320a82151001825828825720828a183308dd0e01330901b4a002080284770182102882152095113032009511887b10706482957c80009578a0009579c0007b17683307fe007b17609517687b1758491150083307c705017b17483307c6077b17407b1938491130033307a405017b17283307ba077b17207b18184911100c33089805017b18083308820501330a91050133090f330b077b17642750200c0229b9008210709511783200828a08510a559511d87b10207b1518828b7cb995aaff95bb017b8b7b8a08510948330a01aba94e6475641750200e024cc800821814070000000000000080ab784c140701000000000000807b578210208215189511283200140801000000000000807b78320014080000000000000080280d14080100000000000080017b78821020821518951128320082171082180882197b57107b58087b5982102082151895112832009511d07b10287b15207b16188279140a00000000000000806485aba91b8258108257aa877c825708c8870746079588017b5810285d8258108259aa8976825908c88909827610330a01789ac8a8089869207b5810520982008277087b17086467645850201002001a8258108257c98707ac67567b18825708c88707821808646950201202bce9008217c876067b56100182102882152082161895113032003309016457502014024dc4008258102878ff330901647664575020160239c4006467825810287aff645764695020180226c400825810289f3307170501330ab02301330be823013308169519172842b400951148ff7b10b0007b15a8007b16a0008289087b1714070100000000000080510942828a7cab9599ff95aa017b8a330a017b8908aaab22520b2a6487648550201a0212c800837751073014070100000000000080280d140700000000000000800182187b878210b0008215a8008216a0009511b80032007b151083874911509519584911580149116051075202330697882098882049112801821b10281384770152075702821808c9c80851080f02821a5033070002e6780cc96a077b18087b1c20aec761c8c605ac65530298583b33075208550297590557094e02510a1697aa058217287b1780007b1a900033070128053307017b178800951768330801951a800050201c021aeb821768520700028217707b17287b17587b1550821b10821c20019765059ac77b1718330a01330701283c821728c857079518800033092050201e020de8008216389566017b1660821c20821a30d8ca0795aa01821818c8a808955520821b105118013dff82b808330920ac982bff82b79588e09579207bb97bb8087c78017c797b1a307c7a027c7b03978808d4980897aa1097bb18d4ba0ad4a8087c79057c7a047c7b067c7c07979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1880007c78097c79087c7a0a7c7b0b978808d4980897aa1097bb18d4ba0ad4a8087c790d7c7a0c7c7b0e7c7c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1888007c78117c79107c7a127c7b13978808d4980897aa1097bb18d4ba0ad4a8087c79157c7a147c7b167c7c17979908d4a90997bb1097cc18d4cb0bd4b909979920d498087b1890007c78197c79187c7a1a7c7b1b978808d4980897aa1097bb18d4ba0ad4a8087c791d7c7a1c7c7b1e7c771f979908d4a90997bb10977718d4b707821a50d49707977720d487077b1798007b1638aba68ffe95175050202002a9ea8217587b1728287cfe95195882988217508299087b1840140800000000000000807b1948aa8775fd280e82980882997b18487b194001821848821940821a7ba8107ba908286cfd82175051074ffd821728502022027fe3002841fd8217708218783309a82f01281d9b0033073309a82f0128129b003309a82f0128099b009511f0fe7b1008017b1500017b16f8008279140affffffffffffff7fc8a90b88bb17140a0000000000000080d3a90993b9979902330abc0001c8a9098199c8a90964853209957818957938957a40957b48957c509576587b17707b187833070024017b1780007b19880033072024017b1790007b1a98007b17a0007b1ba8007b17b0007b1cb8007b17c0007b16c80033074024017b17d0009517707b17d80082582833076024017b17e000825720828a189516d80033085d060133090db4a00228027b15e8007817f0004621f10033082706013305aa069517e800330909951a78330baa0650202a02ccab003308a705019517e800330908951a8800645b50202c02b3ab0033083006019517e80033090c951a9800645b50202e029aab0033083c06019517e80033090c951aa800645b5020300281ab0033084806019517e800330909951ab800645b5020320268ab0033083105019517e800330904951ac800645b502034024fab0033085106019517e80033090c646a645b5020360238ab007c17f1007c18f000847901d487075109160d84880152080f0d8217e8007c78348488045208cd0b827828827720828a1833085a0f013309023300c602320a825828825720828c1833081e070133090528050b825828825a20828b189577087b17e80033080f070133090564a7b4b0023c027b157878178000462181003308390501330bce00951778330904951ae80050203e02a2aa007c1981007c188000d489075109830c84880152087c0c8217787c78348488045208b10a827828827720828a1833085a0f013309023300c602320a957c209578249579289577087b1778330718017b17609517787b1758491150083307bf05017b1748330732017b17407b19384911300933091721017b19287b17207b18184911100633081121017b18083308ef0601330af40601330905330b07281b02825828825a20828b189576289577087b17e800330899060133090564a7b4b00244027b1578781780004621810033089e0601330b3201951778330906646a50204602c0a9003308270601330b9e00951778330909951ae80050204802a6a9007c1981007c188000d489075109870b8488015208800b8217787c78348488045208cc09827828827720828a1833085a0f013309023300c602320a825828825a20828b189576209577087b17e8003308fb060133090664a7b4b0024c027b1578781780004621810033083d0501330b2c01951778330904646a50204e0227a9003308010701330b9c00951778330905951ae800502050020da9007c1981007c188000d489075109ee0a8488015208e70a8217787c783484880452084a09827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e8003308de060133090664a7b4b00254027b157878178000462181003308e40601330bb000951778330905951ae800502056028fa8007c1981007c188000d489075109700a8488015208690a8217787c78348488045208e308827828827720828a1833085a0f013309023300c602320a957c980095788800957990009577087b177833078e007b17609517787b17584911500433073105017b1748330736017b17407b19384911300933098206017b19287b17207b18184911100633077c06017b1708330732013308e70501330a710601330908330b0b017b17645750205a0288af0028ce09825828825720828c18330814070133090a28ea07825828825a20828b189577087b17e80033088b060133090764a7b4b00260027b157878178000462181003308920601330bd600951778330907951ae8005020620287a7007c1981007c188000d4890751096809848801520861098217787c78348488045208f207827828827720828a1833085a0f013309023300c602320a825828825a20828b189576089577287b17e8003308c6060133090664a7b4b00266027b157878178000462181003308390501330bba07951778330904646a5020680208a7003308c30601330bd200951778330903951ae80050206a02eea6007c1981007c188000d489075109cf088488015208c8088217787c783484880452087007827828827720828a1833085a0f013309023300c602320a825828957930825a20828b187b19e8009576089577287b176833086a060133090764a7b4b0026e027b157878178000462181003308270601330bba07951778330909646a5020700269a6003308300601330b360195177833090c821a685020720250a60033083c0601330bd20095177833090c951ae8005020740236a6007c1981007c188000d4890751091708848801520810088217787c78348488045208e606827828827720828a1833085a0f013309023300c602320a825828825a20828b189576289577087b17e800330823070133090764a7b4b00278027b1578781780004621810033082a0701330b320195177833090a646a50207a02b7a5003308170601330bce00951778330908951ae80050207c029da5007c1981007c188000d4890751097e07848801520877078217787c783484880452086406827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e8003308df050133090864a7b4b00280027b157878178000462181003308340701330bd200951778330905951ae800502082021fa5007c1981007c188000d48907510900078488015208f9068217787c78348488045208fd05827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e8003308a4060133090b64a7b4b00286027b157878178000462181003308af0601330bba0095177833090d951ae80050208802a1a4007c1981007c188000d489075109820684880152087b068217787c783484880452089605827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e800330839070133091364a7b4b0028c027b1580007817880049117846218900330922019517789518e80050208e0241a9008218787c1988008e87d4970751080706849901520900065218013b057c17890082158000510733057c573484770452072905825828825720828a1833085f0f01330901b4a0029002647833070151080a0528c205825828825a20828b189577087b17e800330806070133090964a7b4b00294027b157878178000462181003308410501330bda00951778330904951ae800502096028fa3007c1981007c188000d4890751097005848801520869058217787c78348488045208b704827828827720828a1833085a0f013309023300c602320a825828825a20828b189576089577287b17e8003308bc060133090764a7b4b0029a027b157878178000462181003308390501330bba07951778330904646a50209c0210a3003308c30601330bd200951778330903951ae80050209e02f6a2007c1981007c188000d489075109d7048488015208d0048217787c783484880452083504827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e80033080f060133090864a7b4b002a2027b157878178000462181003308750501330bdc00951778330904951ae8005020a40278a2007c1981007c188000d4890751095904848801520852048217787c78348488045208ce03827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e8003308e9060133090664a7b4b002a8027b157878178000462181003308750501330bba00951778330904951ae8005020aa02faa1007c1981007c188000d489075109db038488015208d4038217787c783484880452086703827828827720828a1833085a0f013309023300c602320a82582895792c825a20828b187b19e80095760895770c7b17683308cc060133090664a7b4b002ae027b157878178000462181003308d20601330b3201951778330907646a5020b00275a1003308390501330bba07951778330904821a685020b2025ca1003308d90601330bd000951778330905951ae8005020b40242a1007c1981007c188000d489075109230384880152081c038217787c78348488045208c602827828827720828a1833085a0f013309023300c602320a825828825a20828b189577087b17e80033084c070133091764a7b4b002b8027b15800078178800491178462189003309a0009517789518e8005020ba02e2a5008218787c1988008e87d497075108a8028499015209a1025218016b027c17890082158000510763027c573484770452075902825828825720828a1833085f0f01330901b4a002bc02647833070151083a02286302825828825a20828b189577087b17e8003308af050133090864a7b4b002c0027b157878178000462181003308750501330bba00951778330904951ae8005020c20230a0007c1981007c188000d489075109110284880152080a028217787c78348488045208e701827828827720828a1833085a0f013309023300c602320a825828825720828c1833086307013309090182100801821500018216f80095111001320c827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a8215800001825828825720828a183308dd0e013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a827828827720828a183308590f013309013300c602320a8215800001825828825720828a183308dd0e013309013300c602320a827828827720828a183308590f01330901b4a002c60284770182100801821500018216f8009511100132009511d87b10207b15187b16106486828828826a20828b18647533086c070133090364a7b4b002ca027b167817084611093308450501330be0026417330904645a5020cc02b89d007c19097c1808d4890751094184880152083b82177c783484880452081a827828827720828a1833085a0f013309023300ce02320a827828827720828a183308590f01330901b4a002ce028477018210208215188216109511283200951170ff7b1088007b1580007b16788279105109eb007b198275087b156833070a027b17107b177033078024017b17389517687b17488287208288284911400149115849112801491150019519387b17207b18185020d2026f970052078e0037075827035317026b6e779558500182169569ff9575ff3307a824017b1708017b193051099d007b18688217107b17708217087b1738491140014911589517687b1748491128014911500195193882172064868218185020d4020e970052072d956850019555ff8219309599ff5205b3821718827a183308610f01330901821720b4a002d6027b1728018217288210880082158000821678951190003200828928828720829c1833086f0701330902821088008215800082167895119000320c33099024018217647828188f00951170ff7b1088007b1580007b16788279105109ed007b198275087b15683307ba077b17107b177033078024017b17389517687b17488287208288284911400149115849112801491150019519387b17207b18185020da02439600520790003a076027035317026d97772098772095582082169569ff9575ff3307a824017b1708017b193051099c007b18688217107b17708217087b1738491140014911589517687b1748491128014911500195193882172064868218185020dc02df950052072c9568209555ff8219309599ff5205b4821718827a183308610f01330901821720b4a002de027b1728018217288210880082158000821678951190003200828928828720829c1833086f0701330902821088008215800082167895119000320c33099024018217647828ea8d009511d87b10207b15186485828828825a20828b187b173308c6070133090664a7b4b002e2027b15087817104611113308cc0701330b0e01951708330902641a5020e402bd9a007c19117c1810d4890751094284880152083c8217087c783484880452081a827828827720828a1833085a0f013309023300e602320a827828827720828a183308590f01330901b4a002e60284770182102082151895112832009511d87b10207b15188179979902330a1c0101c8a9098199c8a9093209828928828720829c183308790501330904821020821518951128320c828928828a20829c189577047b17330bfa070133090d64a764853308fa0701b4c002ee027b15087817104611113308ed0701330bd60095170833090d641a5020f002e699007c19117c1810d4890751090e01848801520807018217087c78348488045208cd00827828827720828a1833085a0f013309023300fc02320a828928828720829c183308d70701330907821020821518951128320c828928828a20829c189577047b17330bde070133090f64a764853308de0701b4c002f6027b15087817104611113308ed0701330bd60095170833090d641a5020f8024d99007c19117c1810d4890751097584880152086f8217087c783484880452084d827828827720828a1833085a0f013309023300fc02320a828928828720829c183308ce0701330909821020821518951128320c827828827720828a183308590f013309013300fc02320a827828827720828a183308590f01330901b4a002fc0284770182102082151895112832009511c87b10307b15287b16208179330a016485ad9a265209bb00825828825720828c18330807080133090a821030821528821620951138320c5219021001825828825a20828b189576049577087b1708330816080133090b64a7b4b00200037b15107817184611193308210801330b320195171033090e646a5020020341980033082f0801330bd600951710330911951a08502004032898007c19197c1818d4890751099a01848801520893018217107c783484880452084201827828827720828a1833085a0f0133090233001203320a825828825a20828b189577047b170833081f060133090864a7b4b00206037b15107817184611193308110801330bd600951710330905951a0850200803b097007c19197c1818d489075109220184880152081b018217107c78348488045208e100827828827720828a1833085a0f0133090233001203320a82582895790c825a20828b187b19089576049577087b17330840080133090b64a7b4b0020a037b151078171846111933082108013305320195171033090e646a330b320150200c032d970033084b080195171033090e821a645b50200e031797003308590801330bd600951710330911951a0850201003fe96007c19197c1818d4890751097084880152086a8217107c7834848804520848827828827720828a1833085a0f0133090233001203320a827828827720828a183308590f0133090133001203320a827828827720828a183308590f0133090133001203320a827828827720828a183308590f01330901b4a002120384770182103082152882162095113832009511c87b10307b15287b162083796482510932977720987320330807330902330a0133070128098b8801ca990901848b01510bf6ca7907aba8f0ae732233042831028228108227aa875403822708c8870746079588017b281028ee02330807330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b33090e330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330401ac83cc0133080e330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b330915330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330402ac838001330815330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b33091c330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330403ac83340133081c330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b330923330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330404ac83e800330823330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32b33092a330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330405ac839c0033082a330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32a330931330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330406ac8350330831330902330b01330a0128098b8801ca990901848c01510cf6caa90aabb8f0aca32c01330938330a02330b0133080128098b9901caaa0a01849c01510cf6ca8a08abb9f0330407ae8304018d4808330a02330b0133090128098b8801caaa0a01848c01510cf6ca9a09abb8f07b1210ae73228228108227c99305aa871201822708c887097895958c017b2c1033080128688a4603330802330a01330701646b28098bbb01ca88080184bc01510cf6ca7807ababf051071601822810822acb7305c99505aa8ae800822708c887097895958c017b2c10330902330a0133080128098b6601ca990901846b01510bf6ca8908aba6f05108fa008229cd8308c9c9097b1818ac492ec8c707951818644964c6644550201403a4cc00c865058217107b7510018210308215288216209511383200642764c8644964266445502016032fa7006454826c1082670828bc461118ff95181833090164276425643650201803b9a6007b1618951818330908645750201a03a7a60028ae3309016427642550201c03eca6006452825810289dfc330901642764367b140850201e03d3a600821408646382121082281028d6fe33090164277b137b140850202003b4a60082140882138212108228102800ff9511d03307e02e017b1749110801491120491110084911183308b82401641728f784003307d0240128dea4009511d07b10287b15207b16186486647533088c0050202203ebc4005107850081680849018c007b17089889187879039889107879028269787898880878780178790498983878780b98983078780a9898287878099898207878089898187878079898107878069899087879054911100c95680c95770c330980005020240345cb00821782180833098c007b59107b577b5808821028821520821618951130320033078c00282c80009517f0069518bc017b18f805330824077b18e8007b1800069518b8017b1808063308b6067b1848017b1810067b171806330720077b1718017b17200633070026017b17d8014921e001034921f8019518f8057b18e8014921f001039517c0019518d801502028033180008216c0018215c801821bd0013308490501330702330904645a0a640151060c645750202a03c0c7004921a8070e9517c0079518a80750202c0341a6008218c007140700000000000000807b183801ab7806284e498218c8078217d0077b1830017b18d8077b17e0079517d80750202e0344a900837752078e0a83874921e807330a083309087b1980017b19f0074921f807330c52070628df45978820988b203302b8001407f8ffffffffffff7f7b174001330320140700000000000000807b1760012811821b50013302b800c99b0b510b520a821ae807330759e67b05c9ca077b1578017b1b5001ae577fc8c505aec506289645d6250833075108062895459659b80082164001ae96062899457b1cb001510a1896a7b800821880017b18d0067b17e00633070828053307017b17d8069517f805330808951ad0065020300342cc8217f805510706284b45821700067b1780017b17f0077b15e807821cb001330320821578010196c7b800957ab0009a577b176801330601330b01281a0182178001c857077b7250ff951800108288f0f87b7858ff951800108288f8f87b7860ff821890017b7868ff821888017b7870ff821870017b7878ff95180010828830f97b789895180010828828f97b789095180010828820f97b788895180010828818f97b7880951800108288e8f87b78b8951800108288e0f87b78b0951800108288d8f87b78a8951800108288d0f87b78a0951800108288c0f87b78d0951800108288b8f87b78c8951800108288b0f87b78c0951800108288c8f87b78d8951800108288a8f87b78f8951800108288a0f87b78f095180010828898f87b78e895180010828890f87b78e0821898017b7895cc017b1cf80782197801d8960b95660182176801c86707955ab800330320330801aa8733fe7b1b88017b16a0017b1aa8017b1cb0018212e007ac320e088218d8079529e07c8a017c8b7c8c027c860397aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b057c8c047c86067c850797bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a40037c8a097c8b087c8c0a7c860b97aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b0d7c8c0c7c860e7c850f97bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a48037c8a117c8b107c8c127c861397aa08d4ba0a97cc10976618d46c0cd4ca0a7c8b157c8c147c86167c851797bb08d4cb0b976610975518d46505d45b0b97bb20d4ba0a7b1a50037c8a197c8b187c8c1a7c861b97aa08d4ab0b97cc10976618d46c0c7c861d7c851c958a20d4cb0b976608d465057c8c1e7c861f7b1ad8077b19e00797cc10976618d46c0cd45c0c97cc20d4cb0b7b1b5803ac39e106952bc07ca9017cac7ca6027ca503979908d4c909976610975518d46505d459097cac057ca6047ca5067ca70797cc08d46c0c975510977718d45707d4c707977720d497077b17d8017ca7097ca9087cac0a7ca60b977708d4970797cc10976618d46c0cd4c7077ca90d7cac0c7ca60e7ca50f979908d4c909976610975518d46505d45909979920d497077b17e0017ca7117ca9107cac127ca613977708d4970797cc10976618d46c0c7ca6157ca514958940d4c707976608d465057cac167ca6177b19d8077b1be00797cc10976618d46c0cd45c0c97cc20d4c7077b17e8017ca7197cac187ca61a7ca51b977708d4c707976610975518d46505d457077cac1d7ca61c7ca51e7caa1f97cc08d46c0c97551097aa18d45a0ad4ca0a97aa20d4a7077b17f001ac3bb805952ba07c97017c9a7c9c027c9603977708d4a70797cc10976618d46c0cd4c7077c9a057c9c047c96067c950797aa08d4ca0a976610975518d46505d45a0a97aa20d4a7077b17f8057c97097c9a087c9c0a7c960b977708d4a70797cc10976618d46c0cd4c7077c9a0d7c9c0c7c960e7c950f97aa08d4ca0a976610975518d46505d45a0a97aa20d4a7077b1700067c97117c9a107c9c127c9613977708d4a70797cc10976618d46c0c7c96157c9514958a60d4c707976608d465057c9c167c96177b1ad8077b1be00797cc10976618d46c0cd45c0c97cc20d4c7077b1708067c97197c9c187c961a7c951b977708d4c707976610975518d46505d457077c9c1d7c961c7c951e7c991f97cc08d46c0c975510979918d45909d4c909979920d497077b171006ac3b8f04952780958880007b18d8077b17e0077ca7017ca87ca9027cab03977708d4870797991097bb18d4b909d497077ca8057ca9047cab067cac07978808d4980897bb1097cc18d4cb0bd4b808978820d487077b17d0067ca7097ca8087ca90a7cab0b977708d4870797991097bb18d4b909d497077ca80d7ca90c7cab0e7cac0f978808d4980897bb1097cc18d4cb0bd4b808978820d487077b17d8067ca7117ca8107ca9127cab13977708d4870797991097bb18d4b909d497077ca8157ca9147cab167cac17978808d4980897bb1097cc18d4cb0bd4b808978820d487077b17e0067ca7197ca8187ca91a7cab1b977708d4870797991097bb18d4b909d497077ca81d7ca91c7cab1e7caa1f978808d4980897bb1097aa18d4ba0ad4a808978820d487077b17e8069517d807502032033ca4003303205207570364858218e00751084d038219d8077c979588ff9599017b19d8077b18e0077b159801510717330807ae872b033306821560017b15900128a0009517ff07957701019518d807502034039aa00095170010827700f9821c6001aac71e037b179001951700107c7708f9951800107c880af9951900107c9909f9951a00107caa0bf9951b00107cbb0cf9978808d4980897aa1097bb18d4ba0a951900107c990df9951b00107cbb0ef9d4a808951a00107caa0ff997992097bb28d4b90997aa30951b001082bb10f97b1b7001d4a909d4980897860864c501d476069517ff07957701019518d80750203603fa9f0095170010827200f9aa523b027b1688019518ff0795880901828708828882194003821a4803951b00107bb788f8951700107b7880f8951700107b7960f8951700107b7a68f882175003821858038219d801821ae001951b00107bb770f8951700107b7878f8951700107b7940f8951700107b7a48f88217e8018218f00182191006821a0806951b00107bb750f8951700107b7858f8951700107b7938f8951700107b7a30f8821700068218f8058219d006821ad806951b00107bb728f8951700107b7820f8951700107b7900f8951700107b7a08f88217e0068218e80695190010829988f8951a001082aa80f8951b00107bb710f8951700107b7818f8951700107b79f8f8951700107b7af0f895170010827778f895180010828870f895190010829968f8951a001082aa60f8951b00107bb730f9951700107b7828f9951700107b7920f9951700107b7a18f995170010827758f895180010828850f895190010829948f8951a001082aa40f8951b00107bb7e8f8951700107b78e0f8951700107b79d8f8951700107b7ad0f895170010827738f895180010828830f895190010829928f8951a001082aa20f8951b00107bb7c8f8951700107b78c0f8951700107b79b8f8951700107b7ab0f895170010827718f895180010828810f895190010829908f8951a001082aa00f8821be807951c00107bc7a8f8951700107b78a0f8951700107b7998f8951700107b7a90f8821cb0018215a8018216a001abbc11f79517e8077b125801502038037ba00082125801821cb0018217f0077b17800128eef61407000000000000008082189001d47808330320aa7810646750203a0335bd003303200182178801847701520729821cb001821b50013302b80082197801c99b0b520bfff52849821788018477013303205107dd8218b00151080628663c8217e80751070e8217800150203c03e5bc0033075a1f01330a083001330b2830013308239519ff079599500128818a008218e80714070000000000000080aa78d48215f0077b180801821738015107138217300164c650203e039abc00646c0196c7b800c857077b177001140700000000000000807b1798011407f8ffffffffffff7f7b1750014921380121384c4921d000abaaaaaa7b150001280d9595b80082179801ab763582177001ab750628a63a645982578256188258207b1868018258287b18b0015107d682970864955020400322bc00645928c57b16f0007b19f80082176801951800107b8760f88217b001951800107b8768f89517ff0795776150204203bf9d0083773303d80051070628e23a83874921d8013309087b19e0014921e801520706280d3a330c97882098852033020828153303d8008212a80182157801c9a50551057c018214d80133074be6750ac9c4077b1578017b1a9001aea73fc8ac06aec60628a73ad63608330751080628a63a9669d800821b5001ae9b0628aa3a7b16b00164c551041a9647d8007b12d0067b17e006330708280b7b12a80128433307017b17d8069517f805330808951ad00650204403d6c08217f80551070628583a821700067b17a8017b17e0018217b0017b17d801645c821a90013303d8000196c9d8009aa77b178801330501330b01283f7b18a801c868079518d0063309d80050204603b6bd00821cb00195cc017b1ce801821a9001d8a50b95550182178801c857079569d800330801aa8707ff64b67b1cb0017b19a0019517f8059518ff0795886150204803358d8217f805821898019588187b188001aa87409517d0069518f8053309d80050204a034fbd008217d8018218a8018216a0018219b001ab7973ff3308c02f019517d80150204c03bdc18218e001285cff846701821578015107062812393303d8008212a801821cb001821a9001c9a50552058cfe8218d80182179801ab78062825397b18d80096c7d800c872087b12a801520c062803381407ffffffffffffff7f7b17600164267b18400128169517f80550204e0394ba82184001ab860628dc3782659568089566d8009517d8013309d000502050039cbc0082178001ab750628bf377b1678017b15f8059518d8013309d000951700065020520377bc009517f8057b174003330724027b17b0017b17480333073026017b17d0064921d806014921f006951840037b18e0064921e806019517a8029518d00650205403b2718216a8028215b002821bb8023308490501330703330904645a330056030a640151060c6457502058033eb900821af80582196001c89a07887817959501d35a079387541717072838199777023308300101c887078177c887077b15580132077b1a2001951c10067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17d8027cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17d0027cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17c8027cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d48707821830067b18b0018216400682184806821538067b17c002951700107b7620f8951700107b7840f89517d006330980009518500650205c0381ba00821700067b17c800821708067b17e0009517c0028218b0016459646a0a090198782051080628a91b977720d4780884880152080628c61b33081150205e0380b300821678015207062845393308050a0133091164755020600325ba00330b11330733083309645a0a6464575020620343b7003307018219200182186001d29808510826262849348218100682190806821700067b17c8009787037b18b001978805c97808c898087b19e0007b1940037b1820017b18480395174003951800107b8718f93307c802951800107b8720f933079029017b17d0064921d806014921f0069518ff07958819017b18e0064921e806019517a0049518d00650206603fd6e8216a0048215a804821bb0043308490501330702330904645a0a640151060c6457502068038db60033080450206a038fb20052070628be3864758218b00178789887187857039887107857029887087857013307f70501330808330a0464590a03013308f7ac870c3308ffaa8706285538645750206c033bb6008216e0008217b00152070628d21833058216e00095661828199566188217900195771895550182182001ab870628ed188268e882179801ab780628a3187b1888019567e88268f07b18b0017b1790018277107b17a001951700107b75d0f89517ff079577d1007b17400333078e067b1748033307a029017b17d0064921d806014921f006951840037b18e0064921e806019517b8049518d00650206e03e46d8217c0048218c8048219b8047b1930017b1728018219b001821aa001330070030a03013308f7ac870c3308ffaa870628de358217300151070e821728015020720358b50082178801510732ff8217b0015020740345b5002823ff8216000682179801aa760628e81833076d0b013308093309330a330078030a0301957801821678015518f8ba3d2806240a0833080d50207c0312b1005207062847373308dd0b0133090d647550207e03bbb7003308490501330702330904330b0d645a0a6464573300380428d0b400801720067a179405951600067c68197c69187c6a1a7c6b1b978808d4980897aa1097bb18d4ba0ad4a8087c691d7c6a1c7c6b1e7c6c1f979908d4a90997bb1097cc18d4cb0bd4b909979920d49808951900107b9830f97c68117c69107c6a127c6b13978808d4980897aa1097bb18d4ba0ad4a8087c69157c6a147c6b167c6c17979908d4a90997bb1097cc18d4cb0bd4b909979920d49808951900107b9828f97c68097c69087c6a0a7c6b0b978808d4980897aa1097bb18d4ba0ad4a8087c690d7c6a0c7c6b0e7c6c0f979908d4a90997bb1097cc18d4cb0bd4b909979920d49808951900107b9820f97c68017c697c6a027c6b03978808d4980897aa1097bb18d4ba0ad4a8087c69057c6a047c6b067c6c07979908d4a90997bb1097cc18d4cb0bd4b909979920d49808951900107b9818f99518ff07958819013309200a1b01520706285c19957809951900107b97d0f85318070728b6379a87062848197f171806951a000682a81082a90882aa951b001079b7b0f8951700107b7828f9951700107b7920f9951700107b7a18f99517ff079577b1007b17400333072c017b1748039517ff07957719017b1750033307d8027b1758033307f028017b17d0064921d806024921f006951740037b17e0064921e80602951730059518d00650208403376b8216300582153805821b40053308490501330702330904645a0a640151060c645750208603c7b20095170010827828f9590806286e35951700107e77b0f87b17900195170010827720f97b17a0017b1788017b18b00197860595650364585020880396ae00520706284e347b15d0067b17d8064921e0068218b00152080628e91b7b17b0013305821aa001018217d006c9570754171f378217b0010195a8207b18a0017b17b001c8570733092064a850208a0305b5009555209566e07b15e006821aa0015206c8281b159517d006330920645864a550208c03938f00645a8215e0068217d80628b681172006951800107a87b0f8951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8730f97cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8728f97cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8720f97cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8718f99517ff079577b1007b1740038217e8007b1748039517ff07957719017b1750033307ba077b17580333075027017b17d0064921d806024921f006951740037b17e0064921e80602951720049518d00650209003c3688216200482152804821b30043308490501330702330904645a0a640151060c64575020920353b000951700108077b0f89518ff07958819010a0c01520706288c16957809951900107b97d0f85318070728e2349a8706287816951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1758037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1750037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1748037cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d487077b174003951740030a100151070628473195174003951800107b8718f933074e01951800107b8720f933072029017b17d0064921d806014921f0069518ff07958819017b18e0064921e80601951778059518d00650209603f9668216780582158005821b88053308490501330702330904645a330098030a640151065915284c15951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1758037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1750037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1748037cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad48a0a821820068219280697aa20d4a7077b174003951740030a0a01510706283f3133080950209c035ca9008216780152070628152f3308160a01330909647550209e0301b0003308490501330702330904330b09645a0a64645733007a042816ad00821700067b1728018218100682160806951700104927d0f87b1630017b18880152080628c513978703978805c978092826951700108277d0f8957701951800107b87d0f88219b001019599e89566185209062843107b19b0017b165004951750047b1740033307f6007b1790017b1748033307a027017b17d0064921d806014921f006951840037b18e0064921e80601951758049518d0065020a203c364821758047b17a00182156004821b68043308490501330702330904645a0a64018217a00151070c64575020a4034bac00821850048287088288103309330a0a03018219b0015317f766ff5117ff07286e2c951750047b174003821790017b1748033307b027017b17d0064921d806014921f006951840037b18e0064921e80601951770049518d0065020a603356482157804821b8004821770047b17a001330733083309645a0a64018217a0015107e6fe64575020a803c1ab0028d9fe9518000682861082878288087b1650037b1740037b18b0017b18480359060628492e966550017b15900195550364585020ac0394a700520706284c2d7b15d0067b17d8064921e006520606286c157b17880133068217b0019577c0007b17b001018217d0067b17a001c9670754171fcb0082178801018218b0019588c06475c867073309205020ae03fead009566208217a001c967077b16e00654171fc0006457018218b0019588e06475c867073309205020b003d2ad009566208217a001c967077b16e00654278f00b7006457016475c86707330990008218b0015020b203a7ad00956690008217a001c967077b16e00654177faf006457018215b001955840ff7b178801c86707330980005020b40376ad00956680007b16e006821790019577b0fe955550017b15b0017b179001520731ff28f00d9517d00633092064685020b603f787008216e0068217d8068218d0067b18a001281fff9517d00633092064685020b803d487008216e0068217d8068218d0067b18a0012828ff9517d0063309900064685020ba03b087008216e0068217d8068218d0067b18a0012831ff9517d0063309800064685020bc038c87008216e0068217d8062840ff81170006951800107a87b0f89517ff079577b1007b1740038217e8007b17480333074027017b17d0064921d806014921f006951840037b18e0064921e80601951708049518d0065020c003fc618216080482151004821b18043308490501330702330904645a0a640151060c64575020c2038ca9009517d0065020c403f3900082171007531751580d811818075218024f0d9578af9517d0060a0f018216780152070628aa17957809951900107b97d0f8531807072800333308265020c6034ba500520706283b33647533086b0a0133092664575020c803f2ab00330b26330733083309645a0a64645733007a04280ca900330528143300cc030a08018355015225e8030828c40f951700107a75d0f89517b8017b174003821648017b1648039517ff079577d1007b1750037b1658033307482a017b17d0064921d806024921f006951840037b18e0064921e806029517b0059518d0065020ce03ec608217b0057b1790018217b8057b17b0018217c0057b17a0019517ff079577d1007b1740037b16480333070009017b17d0064921d806014921f006951840037b18e0064921e806019517c8059518d0065020d00397608216d005821ad8058217c8057b1788018217b0018218a00164690a03013308f7ac870c3308ffaa87062862278217880151070c64675020d20313a80082179001510704ff8217b0015020d40300a80028f5fe811790068218800682198806951a00107aa700f8951700107b7820f8951700107b7940f89517400333098000951800065020d80392aa000a951800107b87d0f83307e026017b17d0069517ff079577d100951800107b8718f982171801951800107b8720f94921d806014921f0069518ff07958819017b18e0064921e806019517c0039518d0065020da03b85f8216c0038215c803821bd0033308490501330702330904645a0a640151060c64575020dc0348a7009517ff079577017b17d006821748017b17d8069517ff079577217b17e006821718017b17e8069518ff079588417b18f0067b17f806951740037b1700073307c0077b1708073307f02601951800107b8718f995170010492720f9049517ff079577390149079517d006951900107b9728f995170010492730f9049517d8039518ff07958819015020de03045f8215e003821be8038216d80333070233083309645a0a640151060c64575020e00398a60095170010807700f895180010828820f895190010829940f8951a40030a0b015107190e957809951900107b97d0f853180707285c2b9a870628040e951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8730f97cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8728f97cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8720f97cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad48a0a8218200697aa20d4a707951900107b9718f9951700107b78b0f89517ff07957719010a0e015107062862289517ff07957719017b1740033307ba077b1748039517ff079577b1007b175003821718017b1758033307c027017b17d0064921d806024921f006951740037b17e0064921e806029517e8049518d0065020e403085d8216e8048215f004821bf8043308490501330702330904645a0a640151060c64575020e60398a4003307390b013308099519ff0795991901330a203300e8030a03015317f7460b5117ff410b28ee27951c00067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8730f97cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8728f97cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d48707951800107b8720f97cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a80882162006978820d48707951800107b8718f99517ff079577190164680a0d01837b987a20551b03cd0d951900107a97d0f8951900107a9ad4f8951900107a98d8f8531b02fb0d8119b801521b02d60d3907682703be8707330b04ae97d70d330728dc0d8217000682169801aa67b1097b179001821708067b17a0018215100633083e0c019517d0063309145020ee033c89008218d006ab68770e4921b0015205120f28851082170006951800107b87d0f89517ff079577d1007b174003821718017b1748033307e029017b17d0064921d806024921f006951740037b17e0064921e806019517d0049518d0065020f203aa5a8216d0048215d804821be0043308490501330702330904645a0a640151060c64575020f4033aa2003308085020f6033c9e0052070628ff236475951700108277d0f878579878387858079878307858069878287858059878207858049878187858039878107858029877087857013307cf0501330808330a0864590a03013308f782167801ae870628c2103308ffab87b22328b810801c1806951700107a7c40f8951a000682a780181c068019200682ab10951200107b2718f982a608951700107a7890f8951700107a79b0f8951700107b7b28f9951700107b7620f9510b260a7b1ba0017b19c8007b18e0007b1c20017b17b00133080c5020fa036f9d00520706282c218065826808787598591878790398591078790298590878790178780498893878790b98893078790a9889287879099889207879089889187879079889107879069888087b17880178780533080c5020fc03159d0052070628d220647a8217a001977704c867077b1730019567107b17900182178801957b0c9576018458ff0078a84921d0060c7b1ad80633080128188219d0067c65956601aa98a400c88a097895958801017b18e0068215b001abb6e27b1a88017b18a0015020fe039ea000821690015106250482173001aa761d0433080c50200004909c00520706284d2081688269087878988a18787a03988a10787a0298880878780178790498983878780b98983078780a9898287878099898207878089898187878079898107878069899087879059566107b169001957b0c64768218a001821a88012856ffc96b0a3309fee69a099599017b1788019517d0067b18a0017b1b280150200204d3a9821b28018218a00182178801821ad806282eff9517d0069518f8053309d80050200604a5a2009517d006951800107b8718f98217b001951800107b8720f93307802a017b1740034921480301492160039518ff07958819017b18500349215803019517e0059518400350200804d8578216e0058215e805821bf0053308490501330702330904645a0a640151060c645750200a04689f009517d00650200c0400a0821678012868e5951c04067cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1758037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1750037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1748037cc7017cc87cc9027cca03977708d4870797991097aa187cc805d4a909d497077cc9049788087cca067ccb07d489098018000697aa1097bb18d4ba0ad4a909979920d497077b1740039517ff07957719019519400350201004af83009517ff0795771901951800107b87d0f83307c401951800107b87d8f833078828017b17d0064921d806014921f0069518ff079588d1007b18e0064921e80601951718059518d0065020120420568216180582152005821b28053308490501330702330904645a0a640151060c645750201404b09d0095170010827618f982179801ab763a0333076d0b013308093309330a330016040a03019578015318f80728862695170010827718f982189801d48707aa873c0495170010827520f9645733003804285b9d0082172001aa673982172001c967078219d000979820c89808d68705985504956608280b9555ff9566185105148267f85107f4826750201804239d0028e88217c8005107e5038217e00033003804280a9d008217d0067b17a001821790018216b00164680a060164758217a0015207800651058906955709951800107b85d0f88216780153170707284f2133081150201a04d69800520706289f1e64753308bf0b01330911645750201c047d9f00330b11330733083309645a0a64645733005a0428979c008216d0068215880164570a0701647952068b06289506951700108277d0f882188801284a038215d0068216d806821c20018218e0008219c800821ba00164c7646a0a050151070a05280c1f82150806821a100633076d0b01330809645933001e040a03013308f7ac87f9013308ffaa87f20128982433082f502020042798008216780152070628442564753308910a0133092f645750202204ca9e00330b2f330733083309645a0a64645733007a0428e49b005517f707286425957809951900107b97d0f8531807072807259788089a8700068678018488015108062840e4987720951800107a8700f89517ff07957701951800107b8718f98217e800951800107b8720f933074026017b1740034921480302492160039517ff07957719017b17500349215803019517e0029518400350202404ac538216e0028215e802821bf0023308490501330702330904645a0a640151060c6457502026043c9b0095170010817700f8951800107a87b0f87b1738330807502028042b970052070628ca2464754615066446150565461504744615036146150265461501724605633308079519ff079599b100330a0464570a03013308f7ac870c3308ffaa8706285225645750202a04d19a009517ff07957719013308ff9519c00250202c0445800095170010827518f982179801aa75620a95170010827620f995170010827928f995170010807700f864680a1b017b15b0017b16a0015107a609957809951900107b97b0f8531807072821279a870628910995170010827520f995170010827a28f933076d0b01330809645933002e040a03013308f7ac870c3308ffaa8706280d235206f90028fe0033070701951800107887d0f8951794057b174003821748017b1748039517ff079577d1007b1750033307ea017b17580333075829017b17d0064921d806024921f006951740037b17e0064921e80602951798059518d006502030041e52821698058215a005821ba8053308490501330702330904645a330032040a6401520675287b33070701951800107887d0f89517ff079577d1007b1740033307ea017b17480333073027017b17d0064921d806014921f006951840037b18e0064921e80601951738049518d00650203404ac518216380482154004821b48043308490501330702330904645a330036040a640151060c64575020380438990082167801282508330701c97807951800107b8718f99517ff07957719017b17400333078e067b1748039518ff079588d1007b1850037b17580333077027017b17d0064921d806034921f006951740037b17e0064921e80602951788049518d00650203a041a5182159004821b98048216880433070233083309645a33003c040a640151060c645750203e04aa9800821678018218880151082e82153001955508280b9588ff95551851081c8257f85107f482576486502040047c980064688216780128e082172801510761078217300133007a04285d98003307070195180010788700f99517ff0795770101951800107b8718f93307ea01951800107b8720f933073027017b17d0064921d806014921f0069518ff07958819017b18e0064921e806019517f0039518d006502042044f508216f0038215f803821b00043308490501330702330904645a0a640151060c645750204404df970095170010817500f895170010827620f895184003330980009517dc0650204604889a007a15d8067b16d0069517ff079577d1009518d00650204804aece951700108275d8f895170010827ae0f833077c0c0133080b645933004a040a03013308f7ac870c3308ffaa8706281f1b951700108277d0f88216780152074e06285306330533060164c7330a010a050151070628091a51050c646750204c044097009517ff079577417b17d006821748017b17d8069518ff07958891007b18e0067b17e8069518ff079588b1007b18f0067b17f8069517ff07957719017b1700073307bc017b1708073307b028017b1740034921480304492160039517d0067b1750034921580304951748059518400350204e04144f82155005821b58058216480533070233083309645a330050040a640151060c645750205204a4960095170010827718f9510764fd95170010827720f9330038042885960064768217900164680a066475646733005404286f960064675020540469960052057ff9330809502056046792008216780152070628201864753308b60b013309096457502058040a99003308490501330702330904330b09645a0a64645750205a0423960095170010827718f951070c0595170010827720f933007a042804960064750a076479016457649550205c04f5950064590151090628691895174003951800107b8718f93307d002951800107b8720f933071029017b17d0064921d806014921f0069518ff07958819017b18e0064921e80601951760059518d00650205e04eb4d8216600582156805821b70053308490501330702330904645a330060040a640151060c6457502062047795008217400351073bfc821748033300380428609500511b030728cc1f988b20951900107a97d0f88119b801951700107a7ad4f8951700107a78d8f8951700107a7bdcf8013907682703be870c330b03330701ac9c0e390a682703c88a0a64b7017b16b001951800107a87b0f8951700107a7ab4f89517ff079577d1007b1740033307fe027b1748039517ff079577b1007b1750033307e8027b17580333072828017b17d0064921d806034921f006951740037b17e0064921e80602951700059518d00650206404f74c8215000582160805821b10053308490501330702330904646a0a640151050c6467502066048794009517ff07957719018218b0010a0f01510706285f173307560b0133080b9519ff0795991901330a20330068040a0301821678015317f745035117ff40032864177b1588018217e00655170807288b1e8217d8067c797c76017c72027c73037c7a047c75057c7b067c7c075108437b1ab0017b16c80064b67b1c30017b1928017b15200164257b13e00050206a04f793008213e00064528215200182192801821c3001646b8216c800821ab00101976608d49607972810973918d49808d48707975808d4a80897b91097ca18d4a909d49808978820d487077b17b0018215880151057b019755058216a00128179566209555e08217b0019577017b17b00151055d017c67197c68187c691a7c6a1b977708d4870797991097aa18d4a909d497077c681d7c691c7c6a1e7c6b1f978808d4980897aa1097bb18d4ba0ad4a808978820d47802951700107b7230f97c68117c69107c6a127c6b13978808d4980897aa1097bb18d4ba0ad4a8087c69157c6a147c6b167c6c17979908d4a90997bb1097cc18d4cb0bd4b909979920d48903951700107b7328f97c69097c6a087c6b0a7c6c0b979908d4a90997bb1097cc18d4cb0bd4b9097c6a0d7c6b0c7c6c0e7c670f97aa08d4ba0a97cc10977718d4c707d4a707977720d47909951700107b7920f97c67017c6a7c6b027c6c03977708d4a70797bb1097cc18d4cb0bd4b7077c6a057c6b047c6c067c680797aa08d4ba0a97cc10978818d4c808d4a808978820d48707951800107b8718f97b1258037b1350037b1948037b1740039517400333082095194003330a200a03013308f7ac87a0fe3308ffaa8799fe2841128217900151070e8217a00150206c041b920033080850206e041d8e008216780152070628dc1364758218b001787898873878570798873078570698872878570598872078570498871878570398871078570298870878570133073e0c01330814330a0864590a03013308f7ac87aa003308ffaa87a30028f719951700108176b0f87b17b00133087450207004aa8d0052070628131e647578769867187857039867107857029866087856019557043309703308502072048996006457330833090a0a0151070628e51d6457502074045b9100330809502076045d8d008216780152070628161364753308620a013309096457502078040094003308490501330702330904330b09645a0a64645733007a0428159100645750207a040f91008217f80582186001c8780888881782195801d3970793875417160728f8d682183801d078078477015107ebd628f1d633070701951800107887b0f89517ff079577b100951800107b87d0f83307ea01951800107b87d8f833076026017b1740034921480301492160039518ff079588d1007b18500349215803019517f8029518400350207c04cf488215f80282160003821b08033308490501330702330904646a0a640151050c646750207e045f90008217b00151070e8217a001502080044d90000a951800107b87d0f833077026017b1740039517ff079577d100951800107b8718f982171801951800107b8720f94921480301492160039518ff07958819017b18500349215803019517100395184003502082043f488216100382151803821b20033308490501330702330904645a0a640151060c645750208404cf8f0095170010807700f895180010828840f895190010829920f8951ad0060a0b0151071a957809951900107b97d0f85318070728a3199a870628063307070195180010788700f99517ff079577417b174003821718017b1748039518ff079588217b1850037b1758039517ff07957701017b1760033307ea017b1768033307802601951800107b8718f995170010492720f9039517ff0795773901490795174003951900107b9728f995170010492730f903951728039518ff07958819015020860455478216280382153003821b38033308490501330702330904645a330088040a640151060c645750208a04e18e008217980182182001aa78d80b33081450208c04d78a0052070628701849214003147b174803492150033307109518400350208e049cc18215500382194003c9590754170f290c7b19b0010182164803c856073308100901330910502090044c91009559109517ff0795771901646850209204e6748217b00151070c6467502094045d8e0095170010827818f982179801ab78113306330a330b08821320012888007b181095170010827820f995170010827728f97b1808951900107b9890f8951800107b8798f89517ff079577910050209604da6f83778213200151073c0482169801821828821920018217107b18287b1920510718821708643550209804df8d0064538219208218280114070000000000000080ab760c3306330a330b082807649b648a018217d8028218d0028219c802951c00107bc730f9951700107b76d0f8951700107b7828f9951700107b7920f98217c00295180010818800f87b189001951800107b8bd8f8951800107b8ae0f8951800107b8718f97b1ba001510ac0007b163001330995b5107b1a880197a6068218e000280f9599019566c0955540510685008257ab78f18257f88218c8007b19b0018219e00050209a04a192008219b0018218e00083775207ce9797068218a001c8870782768278087b1828019578408599ff82158801c8590997990650209c047090009555ff7b158801951700107b75e0f88213200151062a82172801643650209e04c78c0064638216300182158801281d821320018216300182158801280f8216300182158801280533050195170010827730f995180010828828f995190010829920f9951a001082aa18f97b1758037b1850037b1948037b1a4003ab65269517ff079577d100645664355020a004606864536465951700108277d8f87b17a001019758068217a0017b18b001c887077b738218c8007b78088218e0007b7810821850037b7828821848037b7820821840037b7818821858037b7830821890017a7838955501951700107b75e0f859050628ee16951700108277d8f87b17a00197560695660464685020a204e98700520706288a167b1640037b17480398572049215003510706287a169518400364575020a404a7be51052f018217a0019578388217b001957640282f821690017b1ba00195a8e0c85b073309205020a604568e008218b0019555207b1550039566c09588405106ff007b1690018285d89857205207ae0c7b18b0018287d07b1730019518400364575020a80441be8216500382184003c96807ac5782007b18880101821748037b17a001c867078218300164595020aa04f08d00c856087b185003821ab00181a5821c8801c98c075417036a821ba00101c88b079859187879039859107879029859087879017875958504c95c077b15500356171f44ff9517400333092064585020ac045168821ab00182155003821b48032824ff95174003646864595020ae04346882165003821740037b178801286dff951740033309045020b0041868821ab00182185003821b4803821c40032882821748037b17a0018215500301821740037b17b0013308145020b204748600520706280d1449214003147b17480349215003330710951840035020b40439bd821a500382194003c9a90754170fe4077b198801017b1a900182164803c8a60733081009013309105020b604e58c00821890019588107b16300164678219a001645a3300b8040a030164795519f70d7b1990013306286c065119ff5f06959709951800107b89b0f8330807ac87062869179a99fd7b1990013306012846068387951900104929b0f8951a0010492ab8f808951900104929c0f85107d706978820988820282082132001018217b0008477015207c9068218308217b800c9780851084e076489951700108277c0f895180010828ab0f8330800017b1930e68909c97a087b19b800ae988b008215b800c87505ae750628ba1598583a330751080628b915975906821b5001ae9b0628ac156436510a1b951700108277b8f897aa067b1740037b1a500333070828053307017b1748039517ff0795771901330808951a40035020ba049b8e95170010827718f951070628431595170010827720f9951800107b87b8f8951700107b75b0f864630195170010827698f833044921b00001288f00951700108277b8f8976806c88707821890017b78821888017b78088218b0017b7810821840037b7818821848038214c0009544017b7820821850038219a001979908821ab000d4a909821aa80097aa107b782882185803821b280197bb18d4ba0ad4a9097b78307a7938956601951700107b76c0f88217b800d874087b18b0008213200182163001aa749ffe51069bfe95170010827790f87c72956bff957a01951800107b8a90f8951800107b8b98f83308017b1888017b14c00051026a5222ff006c330809ac8661fe7c78027c79017c7a037c7b04978808d4980897aa1097bb18d4ba0ad4a8087c79067c7a057c7b077c7c08979908d4a90997bb1097cc18d4cb0bd4b909979920d49808956bf7957a09951700107b7a90f8951700107b7b98f828c100642c28d3016c2857080f330595170010492718f9285084874033050151073c84872033050251073384871033050351072a84870833050451072184870433050551071884870233050651070f84870133050751070628251595170010492718f9ac5b87039517ff07957719017b18b00164a864597b1aa00164b65020bc04eb8900821aa001c9560bc85a0a951700107b7a90f88557079b77ff8218b001e0780795180010828818f9951900107b9b98f8975503cf5707c878088213200101988720520741fdac8b3dfd330251080701330549218801010133070040e67806c952077b1aa0017b183001ae672ec86509ac59600757095c077b1b280151022182178801951800107b8718f9951700107b7228f9330701280ac865072851330701951800107b8720f99517ff079577d100330801951aff0795aa19017b19b0015020be04f78b951700108277d0f85207f307951700108277d8f87b1788018217b0016472821aa001821b2801017b17b001ac57d206ac6b550282178801c8570764a8646964b57b1290015020c004dd88008218300182129001821aa001c9650bc96808c86a0a821cb00164c552082dff951700107b7b98f814070000000000000080951800107b8a90f882132001ab72092833fc330c01531b0424027ca57ca7017b17a0017ca7027ca60395b0fc95a804951900107b9890f8951900107b9098f833091fae09f7017b17a8007b15b0007b1628017b1cb0017b12900195b9dc95a724951a00107ba790f87b193001951700107b7998f87c87117c89107c8a127c8b13977708d4970797aa1097bb18d4ba0ad4a7077c89157c8a147c8b167c8c17979908d4a90997bb1097cc18d4cb0bd4b909979920d479057c87077b17a0007c87067b1798007c87047b1790007c87057b1788007c87037b1780007c87027b17787c877b17707c87017b17687c870f7b17607c870e7b17587c870c7b17507c870d7b17487c870b7b17407c800a7c86087c84097c831f7c821e7c8c1c7c8b1d7c8a1b7c87197c89187c881a7b155003977708d4970797881097aa18d4a808d4870797bb08d4cb0b972210973318d42308d4b808978820d487077b175803974408d46407970010821840978818d40808d48707821848978808821950d49808821958979910821a6097aa18d4a909d49808978820d487077b174803821768977708821870d4870782187897881082198000979918d49808d487078218880097880882199000d4980882199800979910821aa00097aa18d4a909951a001082a6c0f8951a001082aab0f8d49808978820d487077b174003aba628fb9517ff079577b1005020c204de8b2816fb951700107b7b98f8951700107b7a90f8510225fa821788015020c404b983008217b0008477018213200151071bfa28dc00510209fa8217880164355020c604968300645328f6f933068217187b179001018217880151070e821730015020c8047583008217b00151070e8217a0015020ca0463830051060628320e951700108276e0f8951700108277d8f87b17b0015106268215b001955508280b9566ff9555405106148257f85107f482575020cc0425830028e8951700108277d0f851070e8217b0015020ce040d8300821790017b171882179801018216780182192001d3970788770182186001d298085108ddf15107d9f18217c80033007a0428d582003308330633090828daf4951700108276c0f8951700108277b8f87b17b0015106268215b001955508280b9566ff9555405106148257f85107f482575020d00496820028e8951700108277b0f851070e8217b0015020d2047e8200821320012878f49517400333091064585020d404e75f82155003821740037b17b00128c6f39517400333091064a85020d604c95f821a5003821740037b178801280bf8951700108276b0f814070000000000000080aa7627f4951700108279b8f8951700108278c0f8281ef4642601ab680c283782184001aa683114075f427b09ed25b497c96808d6780598550764673300d80428868264675020d80481829555ff9566d8005205f18217d80051070e8217a8015020da04c381008217f8008218f0009575b8005208062864c5821768015020dc04a681002856c5821708018218000151070c64875020de048f8100821710014607951178078210e8018215e0018216d8019511f001320033050849210801821738015107d1c428bdc433073309a82f01280639281282170006821808063309a82f0128f4383309a82f0128ec388216b0015106228215a80164573300e00428c88164575020e004c3819566ff9555d8005206f18217d80151070e8217a8015020e2040581003307960401330ab02301330b902a0133082b9519ff079599500128a14e33073309a82f01288d38281282170006821808063309a82f01287b383309a82f01287338957809951a00107ba718f95518078b008d87067817d0063307580c01330a782301330b682a0133080b9519d006284d4e82178001957520140600000000000000807b16a801280f9588ff9555b800520806287cc38257f88219a801d49707ab970b8257e052071c28e082577b18b0015020e4044e80008218b0018257e05107ca8257e864865020e604388000646828b933070c2807389517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628e23b957809951a00107ba7b0f85518079d008d87067817d0063307ef0501330a782301330b302a013308089519d00628784d957809951a00107ba718f9551807b8008d87067817d0063307981f01330a883001330bc030013308449519d00628484d957809951a00107ba718f9551807e4008d87067817d0063307ef0501330a782301330bb029013308089519d00628184d3309902f0164578218b00128593b33073309a82f0128f6369517ff079577b100951800107b87d0f882171801951800107b87d8f833071832017b17d0064921d806014921f0069518ff079588d1007b18e0064921e8060133082832019517d00628ef3a9517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628b03a3307170501330ab02301330be823013308169519ff079599500128594c9517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628543a951700108277d8f8951800108288e0f83309a82f0128f83533077d1f01330958300133081b28784c6457280d3633070928073633070828013633071128fb35957809951a00107ba718f955180758038d87067817d0063307ef0501330a782301330b002a013308089519d00628ab4b957809951a00107ba718f955180767038d87067817d0063307ef0501330a782301330bc829013308089519d006287b4b33070428953533070d288f35957809951a00107ba7d0f85518076a038d87067817d0063307981f01330a883001330ba830013308449519d006283f4b959709951a00107ba9d0f855170785038d78067818d0063307fb1f01330a883001330b2031013308339519d006280f4b3307d82f012808353307d82f01280035957809951a00107ba7d0f855180790038d87067817d0063307f30301330a782301330b9823013308489519d00628cf4a957809951a00107ba7b0f85518079f038d87067817d0063307960401330a782301330b70280133082b9519d006289f4a957809951a00107ba7b0f8551807ae038d87067817d0063307ef0501330a782301330b5828013308089519d006286f4a957809951a00107ba7d0f8551807bd038d87067817d0063307960401330a782301330bf8270133082b9519d006283f4a957809951a00107ba7d0f8551807cc038d87067817d0063307ef0501330a782301330be027013308089519d006280f4a957809951a00107ba790f8551807db038d870695180010788718f93307ef0501330a782301330ba82a013308089519ff079599190128d749957809951a00107ba7d0f855180708048d87067817d0063307fb1f01330a883001330bf030013308339519d00628a74933073029017b17d0064921d806014921f0064921e006084921e80633084029019517d00628ba379517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006287b379517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006283c379517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628fd369517ff079577d100951800107b8718f982171801951800107b8720f933071832017b17d0064921d806014921f0069518ff07958819017b18e0064921e8060133082832019517d00628b2369517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d0062873369517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d0062834369517ff079577d100951800107b8718f982171801951800107b8720f933071832017b17d0064921d806014921f0069518ff07958819017b18e0064921e8060133082832019517d00628e9359517ff079577d100951800107b8718f982171801951800107b8720f933071832017b17d0064921d806014921f0069518ff07958819017b18e0064921e8060133082832019517d006289e359517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006285f359517ff079577b1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d0062820359517ff079577b1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628e1349517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628a2349517ff079577d1007b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d0062863349517ff0795779100951800107b87b0f882171801951800107b87b8f83307183201951800107b8718f995180010492820f9019518ff079588390149089518ff079588b100951900107b9828f995180010492830f90133082832019517ff079577190128fe339517ff079577d100951800107b8718f982171801951800107b8720f933071832017b17d0064921d806014921f0069518ff07958819017b18e0064921e8060133082832019517d00628b333957809951a00107ba718f9551807b0028d87067817d0063307ef0501330a782301330b182a013308089519d006284945957809951a00107ba718f9551807bf028d87067817d0063307ef0501330a782301330b7829013308089519d006281945957809951a00107ba718f9551807ce028d87067817d0063307981f01330a883001330bc030013308449519d00628e944957809951a00107ba7b0f8551807dd028d87067817d0063307ef0501330a782301330b9828013308089519d00628b944957809951a00107ba7b0f8551807f8028d87067817d0063307981f01330a883001330bc030013308449519d00628894433072f28a32e9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328723233074032017b174003492148030149216003492150030849215803330868320195174003284b32330714282b2e33070728252e9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328f43133072628d42d9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328a3313307960401330ab02301330bc02a0133082b9519ff0795995001284c433307102801282039957809951a00107ba7d0f855180779023308fdbf780664575020ea046c75781640033307ef0501330a782301330bc8260133080895194003280943646728242d3307170501330ab02301330be823013308169519ff079599500128e74282179001781740033307ef0501330a782301330bb026013308089519400328c6423307d82f0128bf2c9517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d00628b9309517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006287a309517ff07957719017b174003821718017b17480333071832017b17d0064921d806014921f006951840037b18e0064921e8060133082832019517d006283b309517ff079577b100951800107b87d0f882171801951800107b87d8f833071832017b17d0064921d806014921f0069518ff079588d1007b18e0064921e8060133082832019517d00628f02f9517ff079577b100951800107b87d0f882171801951800107b87d8f833071832017b17d0064921d806014921f0069518ff079588d1007b18e0064921e8060133082832019517d00628a52f95170010827720f995180010828828f93309a82f0128492b33073309a82f01283f2b3309a82f0128372b330774285b2b957809951a00107ba7d0f8551807bb008d8706781740033307fb1f01330a883001330b08310133083395194003280b419517ff079577b100951800107b87d0f882171801951800107b87d8f833071832017b1740034921480301492160039518ff079588d1007b185003492158030133082832019517400328fa2e9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328af2e9517ff079577d100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328642e9517ff079577b100951800107b8718f982171801951800107b8720f933071832017b1740034921480301492160039518ff07958819017b185003492158030133082832019517400328192e3307782f0128b335951180fc7b1078037b1570037b166803648564765020ec044a5f9766209866209755209855207b1618017b152001951718015020ee04b453837751070628bb1e951718015020f004a253837751070628a91e64859517f001951818015020f2041d528218f00114070000000000000080ab780628851e7b1808821720018218f8017b1830330820ae8706285f1e821700027b17c8007a15d4009517d4007b171801330724077b1720013307e824017b17f0014921f8010249211002951718017b17000249210802019517d8009518f0015020f40490298216d8008215e000821be8003308490501330702330904645a0a640151060b64575020f6042071140700000000000000807b1790008217307b17f0008218c8007b18f800492100014921080108492110014921c0000851087b091407b9e5e41c6d4758bf7b17401407eb113113bb49d0947b1738821890009587187b1788003307c4017b172833072825017b172033074905017b171833073825017b171407f9ffffffffffff7f7b175049114808104911580006209588127b18102841821708019658d800c88707821890009588168219f802821a0003821b08037b787b79087b7a107b7b189555017b15100128077b151001018217f8005107d9089517f0019518f0005020f804cb42ff8217f00182188800ab870628d21c951718019518f0013309d8005020fa04e8728217180182199000c879089588ff888817d3790993895a190966511908340352190995047b19800082163001330718d6760751070628a11c976703976505c97505821750ac7506288f1c821728017b1778821720017b17705105330664585020fc04c46b646982184852070628721c7b19607b17685206260628b406951c100251190a6e045219152f047c19280182172001492148034921500301492158037b19a00051097402140854f029fde5e6dd783302c8780898891ed39808821340ca380898891bd3980814093f74df7d2c6da6da821538ca5808988a1fd38a0bc8790998981ed39808ca380898891bd3980814092af894fe72f36e3cca5808988a1fd38a0ac8790998981ed39808ca380898891bd398081409157c4a7fb979379eca580898861fd38606c8970798781ed38707ca370798781bd38707ca570798751fd375054921a800019517f001283d9727058218a800c88707951828033309205020fe0483718212c8009522017b125803951c10028217a000821bb800821ab000aa7297019517f00101c85b089e8829c85808976911d35a0ad36b0bd3a606d3b505d39a0a98882078789577019ebb13abc7da7b1ab0007b1bb8007c1709027c1808027c190a027c1a0b02977708d4870797991097aa18d4a909d497077c180d027c190c027c1a0e027c1b0f02978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1740037c1701027c1800027c1902027c1a0302977708d4870797991097aa18d4a909d497077c1805027c1904027c1a06027c1b0702978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1738037c17f9017c18f8017c19fa017c1afb01977708d4870797991097aa18d4a909d497077c18fd017c19fc017c1afe017c1bff01978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1730037c17f1017c18f0017c19f2017c1af301977708d4870797991097aa18d4a909d497077c18f5017c19f4017c1af6017c1bf701978808d4980897aa1097bb18d4ba0a82194803d4a808978820d487077b1728037b12c800ab9254fe9517480350200005b3498212c800821750037b17a800283afe82175803821850038219480382151001821a00017b1708037b1800037b19f802aba55bfc3308a0250195170001502002053074ff2847fc7c1744015107620180152001951c24017cc7197cc8187cc91a7cca1b977708d4870797991097aa18d4a909d497077cc81d7cc91c7cca1e7ccb1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1740037cc7117cc8107cc9127cca13977708d4870797991097aa18d4a909d497077cc8157cc9147cca167ccb17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1738037cc7097cc8087cc90a7cca0b977708d4870797991097aa18d4a909d497077cc80d7cc90c7cca0e7ccb0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1730037cc7017cc87cc9027cca03977708d4870797991097aa18d4a909d497077cc8057cc9047cca067ccb07978808d4980897aa1097bb18d4ba0ad4a808978820d487077b1728039518280364573309330a330b0a110164765516f7150359060628ce1851061b036468502004058b675207062843187b17c8003308646950200605817028010382171001821800019676d800957501ab87133308b825019517000150200805a572ff82170801c86707951818013309d80050200a05fd6d28d9fa7b198000821720017b17a00082183001821728017b17b0007b18a80051080a018218a800978703978805c978088216b000c868087b18b800280d9566188217b800aa76e5008267088268107b1610030a13015517f70c33087b172003281b9578097b174803531807072804178d8706781719033308010178181803951710037b1728033307f6007b173003951718037b1738033307fa017b17400333078025017b17f0014921f8010249211002951728037b170002492108020233082850200c05776652070628881649214803287b175003492158033308802b01951748039519f00150200e05112e510706286816821748037b17c80082155003821b58033308490501330702330904645a330010050a64018217c800510723ff6457502012050b6a2817ff8215100182170001ab7513330868250195170001502014054c71ff821708019658d800c88707821890009588147b788216a8007b76089555017b1510015106258215b000955508280b9566ff9555185106138257f85107f4825750201605a86928e98217a000510708018217b00050201805946928fa003307088218484911607b176851069600976604821978c896067b1698003306957a08649b017b1ac8007b16b00082b77b17b8007b1ba80082b7087b17a000648550201a055765520706284b1564763308645950201c054f6e330a06646733086459821bb800330c33001e050a12018219c8005317f7072825158217a000e657077b95f87b967b97088216b000956601821ba80095bb10959a18645882179800ab7b8682177051070c82177850202005e0688215100182170001ab7513330850250195170001502022052470ff821708019658d800c88707821890009588137b788218607b78088218687b78107b76189555017b15100101821880005618151201821758d08707847701520735f82800015116ff07289215821790007b17480328274921c80001019518280364578215c8006459330a646b0a117b1648037b1550037b16580301951748037b1718038217287b1720038217207b17f0014921f8010149211002951818037b18000249210802019517c8029518f001502024055a208215d002821bd8028216c802330702330904821818645a0a640151060b645750202605ec6782175803821850038219480382151001821a00017b17f0027b18e8027b19e002aba510951700018218502028051b6fff821708019658d8008219e002821ae802c887078218f0027b79087b7a108219107b797b78189555017b1510019517180133002a05282568ff9517180150202a051d68ff2826f7951700017b1748033307b4017b17500333070825017b17f0014921f8010249211002951748037b1700024921080201951728039518f00150202c05821f8216280382153003821b38033308490501330703330904645a0a640151060b645750202e0512678215100159050628ff13821708017b17b8009656d800956604646850203005fd6252070628b5137b16f0017b17f8019857204921000251070628d5139518f001645750203205bc998216b800520506284c129657d800c867077b17a00033079001017b17a800330a07140700000000000000807b17980028649517f001330908502034050c4482180002018217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178759588087b180002018216b8009566d8008217a000330a07ab760628c311826782199000c879089588ff888817d3970793879777028218a800c887078177c887077b16b8003207821800028217f001aa87140f8217f801c8870746078219f001958501c959077b15000254171f0d0f7b19c800018217f8019568187b17c000c857073309205020380596688266389558208217c800c987077b180002541707f90e8217c00001c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178768217f0018216b800826540958808c987077b180002541707c30e8217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f001826548958808c987077b1800025417078e0e8217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f001826550958808c987077b180002541707590e8217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f001958508c957077b15000254177f270e8217f801956858c857073309800050203a054667955780007b1700029518f001646750203c05f67cff2819fe821800028217f001aa87dd0b8217f801c8870733091078799588017b18000228f7fd821800028217f001aa87cf0b8217f801c8870746070e8217f001958501c957077b15000256171f42069517f001330920645850204205884182150002282a06821800028217f001aa87a40b8217f801c8870746070b8217f001816520958801c987077b180002541703990b8217f801c8870798591878790398591078790298590878790178758217f001816524958804c987077b1800025417037c0b8217f801c8870798591878790398591078790298590878790178758217f001816528958804c987077b1800025417035f0b8217f801c8870798591878790398591078790298590878790178758265189588049857207b1800025107062890108266109518f0016457502046052896330a075105e7fc646b82180002975504c8560c7b1cc80028438217f801c88707985938787907985930787906985928787905985920787904985918787903985910787902985908787901787595880895bb107b180002aacb94fc8217f00181b682b508c987075417034f8217f801c8870798691878790398691078790298690878790178768217f001958804c987077b180002ac7a869517f00133090864b6502048051140821cc800646b330a07821800022867ff9517f0013309047b1bc00050204a05f03f821cc800821bc000330a07821800022895821800028217f001aa87520a8217f801c88707330904282904821800028217f001aa874d0a8217f801c8870746070c8217f0017e6520958801c987077b180002541701420a8217f801c8870798590878790178758266189585027b1500025106b5fb8217b800827810976605282b8217f8019589207b19c800c8570733092050205005a6649555209566e07b1500028218c800510680fb8217f001c9570756171fd09517f0013309207b18c8006458502052052a3f8218c8008215000228b1821800028217f001aa87cf098217f801c887074607098265189588019857207b1800025207de0e8266109518f0016457502056055c94330a0751051bfb646b82180002975504c8560c7b1cc80028438217f801c88707985938787907985930787906985928787905985920787904985918787903985910787902985908787901787595880895bb107b180002aacbc8fa8217f00182b682b508c98707ae7a678217f801c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178768217f001958808c987077b180002ac7a6fff9517f00133090864b6502058052d3e821cc800646b330a0782180002284fff9517f0013309087b1bc00050205a050c3e821cc800821bc000330a0782180002287cff821800028217f001aa87bd088217f801c887074607028217f00181659800958801c987077b180002541703b4088217f801c8870798591878790398591078790298590878790178758217f00182658800958804c987077b180002ae7a99088217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f00182659000958808c987077b180002ae7a67088217f801c8870798593878790798593078790698592878790598592078790498591878790398591078790298590878790178758217f001958508c957077b15000254177f36088217f801956808c857073309800050205e054562955780007b1700022825f9821800028217f001aa8721088217f801c8870733090f78799588017b1800022803f9821800028217f001aa8713088217f801c887074607039588018217f0017b180002816508c9870754170308088217f801c8870798591878790398591078790298590878790178759588047b18000228b2f8821800028217f001aa87ea078217f801c88707787a288803821800028217f001aa8786088217f801c887074607018219f001958501c959077b15000254171f7e087b19c800018217f8019568087b17c000c85707330920502068056a618266289558208217c800c987077b1800025417076a088217c00001c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178768217f0018219b800829530958808c987077b180002561707b8f728a1f7821800028217f001aa8724088217f801c887073309110178798217f001816528958801c987077b18000254170346058217f801c8870798591878790398591078790298590878790178758217f001958504c957077b15000254171fc8f98217f801956808c8570733092050206c0593609557207b1700022874f7821800028217f001aa87be078217f801c887074607149588018217f0017b180002826508c98707ac7a0ff728f9f6821800028217f001aa87a7078217f801c8870733090528e003821800028217f001aa87a2078217f801c887074607159588017b1800028217f00164698266087c9510c98707ae7a95078217f801c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178768217f0019588087b180002aa8767078217f801c8870778759588017b18000228a8f6821800028217f001aa875c078217f801c8870733090d78798267189586017b160002510783f68218b8008288109588c0007b18c8009675500128398215c800955840ff8217c000c867073309800050207605695f956680007b1600028217b0009577b0fe955550017b15c8006475510737f67b15b0008217f0016475c9670754171f9e008218c8008217f8017b18c8009588c07b17c000c86707330920502078051a5f956620c965077b16000254171f8a008218c8009588e08217c000c8670733092050207a05f45e956620c965077b16000254278f0086008217c000c86707330990008218c80050207c05cf5e95669000c965077b16000256177f42ff9517f00133098000646850207e056339821600028217f8017b17c0002821ff9517f0013309206468502080054539821600028215f0012850ff9517f0013309206468502082052b39821600028217f8017b17c0008215f001285cff9517f001330990006468502084050839821600028217f8017b17c0008215f0012860ff821800028217f001aa87f4058217f801c88707460706018219f001958501c959077b15000254171f9d027b19c800018217f8019568087b17c000c8570733092050208805fb5d8266289558208217c800c987077b18000254170789028217c00001c8870798693878790798693078790698692878790598692078790498691878790398691078790298690878790178769588087b1800022892f4821800028217f001aa876e058219f801956708c889094609129588017b1800029518f00150208c054173ff2864f4821800028217f001aa8754058217f801c8870733090a28fe00821800028217f001aa874f058217f801c887074607089588018217f0017b180002816508c9870754170344058217f801c8870798591878790398591078790298590878790178758219f001958604c969077b16000254171f2a057b19c800018217f8017b17c0008215b80095580cc8670733092050209205e65c7c592c8215c0009567207b1700028218c800aa781105c8750578599577017b17000228acf3821800028217f001aa8712058217f801c887074607169588017b18000282670882199800ab97778217f001aa872b058217f801c887073309017879c898087b1800022867f3821800028217f001aa87e1048217f801c887073309130178799588017b1800028267108268189519f00150209805415eff2833f3821800028217f001aa87c1048217f801c8870733091778799588017b1800022811f38217f001aa87cb048217f801c88707460764698266189588019867207b1800025207ef068295109518f001646750209c05198c5106dbf2645882150002976605282b8217f8019589207b19c800c8570733092050209e05cd5b9555209566e07b1500028218c8005106a7f28217f001c9570756171fd09517f0013309207b18c80064585020a00551368218c8008215000228b19517f0013309045020a2053a368218000228aefa9517f00133092064585020a4052436821500028217f0017b17c8002852fd9517f0013309085020a6050836821800028217f801286cfd9517f0013309015020a805f035821800022816f49517f0013309015020aa05dc35821800022824f49517f0013309015020ac05c83582180002284ff49517f0013309045020ae05b43582180002285bf49517f0013309045020b005a035821800022878f49517f0013309045020b2058c35821800022895f49517f0013309015020b40578358218000228a1f59517f0013309015020b60564358218000228a6f59517f0013309025020b80550358218000228b2f59517f0013309015020ba053c35821800022824f69517f0013309015020bc052835330a07821800022833f79517f0013309045020be051135330a0782180002283df79517f0013309085020c005fa34330a07821800022857f79517f0013309085020c205e33482180002288cf79517f0013309800064585020c405cc348215000228bbf79517f0013309015020c605b8348218000228d2f79517f0013309015020c805a4348218000228e0f79517f0013309045020ca0590348218000228ecf79517f0013309015020cc057c34330a07821800022806f89517f0013309015020ce0565348218000228dff09517f00133092064585020d0054f34821500028217f0017b17c80028e2f09517f0013309085020d2053334821800028217f80128fcf09517f0013309085020d4051b34821800022831f19517f0013309085020d6050734821800022866f19517f0013309085020d805f33382180002289bf19517f0013309800064585020da05dc338215000228caf19517f0013309015020dc05c83382180002286df79517f00133092064585020de05b233821500028217f0017b17c8002871f79517f0013309085020e0059633821800028217f801288bf79517f0013309015020e2057e338218000228cff79517f0013309015020e4056a33330a07821800022832f89517f0013309015020e605533382180002284cf89517f0013309015020e8053f33330a0782180002284ef89517f0013309085020ea05283382180002285ef89517f0013309015020ec05143382180002288cf89517f0013309015020ee050033821800022897f89517f0013309015020f005ec328218000228fff99517f0013309015020f205d832821800022885fa9517f0013309015020f405c43282180002289ffa9517f0013309015020f605b0328218000228a4fa9517f0013309045020f8059c328218000228b0fa9517f00133092064685020fa058632821600028217f0017b17c80028c5fa9517f00164953309015020fc0568326459821700028215f80128dafa9517f0013309015020fe054e328218000228e1fa9517f001330901502000063a32821800022812fb9517f001330901502002062632821800022832fb9517f0013309015020040612328218000228c8fa9517f00133090150200606fe31821800022828fb8217f8017b17c000821700027b17b80082161001821708017b17c8005106248215c80064573300080628eb54ff645750200806e554ff9566ff9555d8005206f08217000151070d8217c80050200a06265482170851070c82173050200c0617548217c0008218b8008210780382157003821668039511800332003307081028d00b3308ffab876d646750200e06e8533307e8250128761733072828b40b3307120e01330a102c01330b302c0133085695196703287b21951748037b172803330720077b17300333071832017b17f0014921f8010149211002951828037b180002492108020133082832019517f001287a0f33074032017b17f0014921f801014921100249210002084921080233085032019517f00128530f3307960401330ab02301330bd0250133082b95196703280021330760230128f90a645728130b6467280e0b82170851070c8217305020100625533307960401330ab02301330bd0230133082b9519670328c6203307d82f0128bf0a3307170501330ab02301330be823013308169519670328a5209567097b1618035517075e8d78067818f0013307981f01330a883001330b8031013308449519f001287a20330768310128730a3307170501330ab02301330be82301330816951967032859203307170501330ab02301330be8230133081695196703284020951718037b174803330720077b17500333071832017b17f0014921f8010149211002951848037b180002492108020133082832019517f001283f0e3307170501330ab02301330be823013308169519670328ec1f9511c07b10387b15307b16286485647650201206633f9766209866209755209855207b167b1508641750201406d2338377520791006485641750201606c2338377520781006486641750201806b233837752077183588369641733001a06951110fe7b10e8017b15e0017b16d801951188f87b1710017a18b8017a19bc019517d006330026032818397c1751072433082033052050201c06a44d51073f951801330920647650201e06535464672805330501645882103882153082162895114032003307960401330ab02301330bd0230133082b641928131f330720282d09951128fd7b10d0027b15c8027b16c0026485647650202006803e9766209866209755209855207b16b8017b15c0019517b80150202206ea32837752079d089517b80150202406da32837752078d089517b80150202606ca32837752077d084921480210951760029518480250202806772f82186002140700000000000000807b1808aa786e0882186802821770027b187b18a0017b17a8019517a00150202a068032837752074c078387492180023309087b1988024921900233055107d206978820988b20330c98001407f8ffffffffffff7f7b17103302044911300828178407015207fd06821b18c93b0b330c9800510b23038214800233076be67b03c954077b1b187b1328ae3772c8350aac5a0c07d6ca08330752080d0796a99800821610ac96020764a6510417964798008218307b18b8017b17c80133070828053307017b17c00195171801330808951ab80150202c069955ff821718015207ae06821720017b17307b1788027b168002330c98003302048213280196579800957694009a377b17203304013300012841018217e800977708821878d487078218f8009788108219f000979918d49808d487077b17f8008217c8009777088218e000d487078218d8009788108219d000979918d49808d487077b17f0009765088217a800d457078218980097881082169000976618d46808d487078218a0009788088219c000d498088219b800979910821ab00097aa18d4a909d49808978820d487077b17e800821738977708821858d48707821848978810821940979918d49808d48707821850978808821970d49808821968979910821a6097aa18d4a909d49808978820d487077b17e00082153082160001c8650595576cff9518b8013309800050202e0699518217e8007b57ec8217e0007b57f48217f8007a57fc8217f0007a57821588009555017b15900282132882148000d83400954401821820c8480895669800330204330901aa981dfe7b1600018219a801ac290afe821aa0017ca77ca8017b18e8007ca8027b18f8007ca8037b18f0009598fc95ab047b1ba0017b18a801ac28ddfd7ca8047b18e0007ca8057b18c8007ca8067b18d8007ca8077b18d0009598f895ab087b1ba0017b18a801531808affd7ca8087b18a8007ca6097ca80a7b1898007ca80b7b1890007ca80c7b18c0007ca80d7b18a0007ca80e7b18b8007ca80f7b18b000959bf095a8107b18a0017b1ba801330c8000accb64fd959b70ff95ac90007b1ca0017b1ba801330c08accb4dfd7b17787ca790007b17587ca791007b17387ca792007b17487ca793007b17407ca794007b17707ca795007b17507ca796007b17687ca797007b1760959768ff95a998007b19a0017b17a8019517b801330980007b148000502030062c50821780027b158800ab7594fd95178002502032063632821788027b1730287ffd8218800214070000000000000080aa78cf037b18b000821788027b17b80082170851070b821750203406144d51052703965798008216b800c867077b17c0003307870c017b17f800140700000000000000807b17d0003307b6067b17f000330720077b17c8007b15000128168216e800956698008217c00082150001aa76d70295171801330980006468502036067c4f81679000826880007a170c017b18100133080e50203806a3485107e4027b16e80033090e64758218f80050203a064c4f9517b80133090e645850203c06eb32645750203e06694c8218b8018217d000aa783a8215c8018217c001531504227c79017c7a7c7b027c7c03979908d4a90997bb1097cc18d4cb0bd49b0601510809502040062b4c561503073306017a169c019566017a16b0028216000133080e50204206184851075902647533090e8218f80050204406c54e33080e9519b002330a0464570a03013308f7ac870a3308ffab87b902645750204606d54b3308800050204806d74751071e026475951818013309800050204a06834e9517b80133098000645850204c0636138216b8018217c0017b17e00033080350204e069e475107ec016458936880004617023f4617013f46073f51061c3305037b1560027b1768027b157002648750205006634b282494658000821ae000936a80007b1860027b1568027b1a700250205206434b3305030195170c017b17b8018217f0007b17c001951710017b17c8018217c8007b17d001951760027b17d8013307b2017b17e0013307d82a017b1780027b1588024921a0029517b8017b1790027b1598029517a00195188002502054062d038217600251070d8217680250205606d34a8216a0018215a801821bb0013308490501330702330904645a0a640151060b645750205806ac4a95179c017b1780028217f0007b1788023307082b017b17b8014921c001014921d801951880027b18c8014921d00101951748029518b80150205a06b802821748027b17d8008215500281170c0182181001821958027b19e0007a17c0017b18b80195181801330980009517c40150205c06054d951760029518b80150205e06388182166802821a700264578218e00064690a03019578013309f8ae98728217600251070b646750206006004a8217d800510728fd645750206206f049281cfd8217b00051070d8217b80050206406dc49330733088210d0028215c8028216c0029511d80232004921b0004921b8000882170852079efc28a2fc33070e28870133078000288001330703287a019578097b177802551807a7008d8706781780023307ef0501330a782301330b182b0133080895198002282e178217800251070d8217880250206606634933075a1f01330a083001330b2830013308239519b00228041782172001821828013309a82f0128ea0033073309a82f0128e0003309a82f0128d8009578097b1760025518076a3308fdbf78066457502068060f497816b8013307ef0501330a782301330b302b013308089519b80128ac16951778027b17b0028217c8007b17b80233071832017b17800249218802014921a0029518b0027b189002492198020133082832019517800228ab04951760027b1780028217c8007b17880233071832017b17b8014921c001014921d801951880027b18c8014921d0010133082832019517b8012870043307960401330ab02301330bd0230133082b9519b002281d1633077d1f01330970300133081b28931652070764972806648728219511d064783307b02b017b1749110801491120491110084911186417281d049511b87b1764177b173833078e067b17403307c02b017b1708491110024911289517387b1718491120019517089511b88278288279207b18287b1920827818827910827a0882777b18187b19107b1a087b1764177b17303307e02b017b17384711409517303300da0628fa319511a87b10507b15487b1640648a828608828b7b171851065a330595b7086468018279c895059588ff9577105208f582a91851091957054582b708887701885810d28707520736975501015705dc005105327b197b1b087b1a10645850206a0667435107cd00821a10821b088219281782a9183305330701280d33053307012806330701017b15207b17284911305116011352061252090f33063305330801283b51092e3308802b0195172064a950206c06d50a5107383307120e01330a102c01330b302c0133085695193f28a81482b60882b8ac653a330501c85707646950206e06a449c865057b153001821730821828821920821a187ba7107ba8087ba98210508215488216409511583200951720648533086469502070062024645882153082172828b53307f82b012849fe64572863fe828928828720829c183308ce1e01330905320c827851080982770828684632009511e07b10187b15107b16086475827a108277c9a7076496ac972b7b1a825708c8a7076469502078060a498217c876067b56103307821018821510821608951120320064577b1864a8646950207a0698238218825a1028c59511d87b10207b15187b1610330980006475ae98268256108257ab761064577b1850207e066901821801825708c8670778789566012899008b870b48110c52071595170d9889068699c00078190c33090228528b871052072295170e98890c8699e00078190c97893498993a9599800078190d330903282d95170f9889128699f00078190c97892e98993a9599800078190d97893498993a9599800078190e33090401825610825a84883f86888000c96a0a7878ac9a2d7b19825708c8670795180c5020800615488217c87606017b56103307821020821518821610951128320064576468649650208206a422646982561028c59511e07b10187b15107b16086489828a08828818330b01aaba13520a13520810827a103306330501282b5108173308802b0182101882151082160895112028a5088298827a1082798286088285c9a909ac69317b17827808c8a80764586469646564a6502086068047c8560682177b7610330782101882151082160895112032007b17821764a86469502088060e228217827a1028bf9511b87b10407b15387b16306475827795780151084d977901e48908330908e4980657063e5107108258087b18187b1728330701017b17206417951918646850208a06392282175207238217087b57087b56821040821538821630951148320033073309482c0128cdfb8217088218103309482c0128bffb9511e87b177b180847111001641750208c063b2e009511a07b177b180864177b174033078e067b17489518087b18507b17583307482d017b1710491118024911309518407b182049112802951710649828b09511a07b177b18089517087b174033078e067b174864187b18507b17583307902c017b1710491118024911309518407b18204911280295171064982873ff8277019511b87b10407b15387b16307b18987904330814330ada0f01532971028000330814951b2e14094b598638d6c56d347b1910491108102733037b143300ffe0f505016472821710d6720798770b821908ca9709bf9206976930989932ca39099894119899108499fe07964c64bfc606976631986630c8a9097c9c7c9901c8a6067c65017c6678b9ff78bcfe78b50178b69588fc95bbfcac20ac5417633697793098993296997b14989911969b64bfb7079777319877309588fec8a7077c7b017c77951c1cc88c0c78cb0178c764970155170a149588ff95191cc889098677307897281c9777019588fec8a7077c79017c77951a1cc88a0a78a90178a701951a1cc88a0a9a8b1433080182173309502090061382104082153882163095114832009511987b10607b15587b16506473807034649c7b184884080133022b3309000011840704c8b80552070849114828376457510c23330664c4821948c8c905649c017dc98999c085990195cc01c89606ab5cf2280764c4330601c87605644c3309000011018237db82097b1b307b1a2851072e823608ae65288407085207577c3838330201c956077b1c207b1918ad829b005208a9007b1710648728a40082352082362864576468821a4864cb502092066e013308015207b700826c186457821828821930821060821558821650951168320c7b16388137307b17107c38387b1808823720823828481330307b13461338017b17406486821a4864cb502094061f01520767821738c95705955501019555ff5105c900826920330830821740b4900296065107eb2842521802129578019877019888017b18102806491110018238207b18408235288138307b1838957601019566ff510628825920821740821838b4900298065107ec330801016487821060821558821650951168320082164064676458821918821a48821b2050209a068e003308015207d7825a186467821828821930b4a0029c063308015207c18217109a777b17483306ff01821748c867075117ff45825920821740821838b490029e069566015107e52839826a18821740821828821930b4a002a00633080152077eff330882168217107a6730821708786738286aff821610821710d87608285eff821710d876082855ff9511d87b10207b15187b161064a6647c5139000011286485828a2064c764987b1b087b1cb4a002a206821c6458821b08647933070152092051061a828a1864c7646864b9821020821518821610951128320a33070182102082151882161095112832009511a87b10507b15487b1640647c827a827b106486520a0a84b80151081e0384bb01510b910064c082cb186494c869093308510b46330c3303e0003302f0006467646528149556010195cc01c98505c956086465aacb27aa95557d565906e98466ff00ac360bac260d95560428dd95560228d895560328d3646701aa96317d6b64496476640c590b0c84b7ff00330be0000151089d02ae989502c886077d77330bc0afb78d02520a1128900264496476640c01510a860282c3087b19187b161055192027330251091ac89608646a017da78977c085770195aa01c87202ab8af2ac32aa01285502646a9565078455f8c89606c956068468073309aaa5147dab89bbc085bb0195aa01c8b909ab5af27b1c087b13330c510821846bf8c8b50ac85b0bc8b808017dab89bbc085bb0195aa01c8bc0cab8af2986303140a0101010101010101330bff00ff001408ff00ff00ff00ff007b182814000000000001000100140801000100010001007b1820c89c022830821530c89505821638c96303846c03821b28d2b407984808d2b808c88707821820ca8707987730c87202520c97005103820064593307c000e673077b17389778037b18303304531304ba8485e007c8950564980182808507ff987707980b06828c08d4b707d2a707c8470785cbff98bb0782861098cc06d4cb0bd2ab0b856cff98cc07986606d46c0c828618d2ac0cc8cb0bc8b707856bff98bb07986606d46b0bd2ab0b958820c87b04ab58aa2853ff821918821610821c088213ac325d28090133088467fc00977703c8790397cc03821918821610018237953308857bff98bb07987706d4b707d2a70795ccf8c87808520ce6821a28d2a807988808d2a808c88707821820ca8707987730c87202821c088213ae32b3007cca38c923085a1a010c510a1a491120281a521a02129587019888019877017b172028087b182033080182c7207b173882c52881c7307b1730958601019566ff51061a825920821738821830b49002a4065107ec330701288300825a18821738821810821918b4a002a606647833070152086a8217209a777b17283306ff01821728c867073308ffaa8740825920821738821830b49002a8069566015107e32834ab987bfddb6809db6606520a82fd82c82882c720828c186468821050821548821640951158320c821620821720d876072809821720d876070182105082154882164095115832009511c064783307e20e017b17304911382b9517307b17491108014911204911100849111864172838f88279088277829c18320c9511807b10787b15707b166849115820330a03781a608296204911284911387b17487b18505106f70082972851075b0182957b19088298107b18109578ff9788039888039588017b18955908977803977706c98705956618017b1920829951091b821a508217488218208288f882aa18b4a002ac06520738017b15188267107b17587c69188268f8826778196051082152180112977704821810c8870782785108073308280e8277083308012806330801018269e87b18287b1730511902198268f05219011e978704821810c8870782785108073309280e827808330901280633090101826708977704821a10c87a0a82a782aa087b19387b1840951828b4a002ae068215185207a7008219209599109555c8956638520543ff2860829718510768829510977804c858087b18207b190882969577ff9777049877049577017b17956608018269510917821a508217488268f882aa18b4a002b0065207528257825908951828b49002b206520742955510956610821720ab75ce821908821a829708ac7a0d282d330a829708540726829797a804c88709821a50821748829882990882aa18b4a002b40651070833070128053307018210788215708216689511800032009511c07b10387b15307b162880707b18980b0433080a3309da0f01532b7102da0033080a951b261402000000005917b7d17b1210491108102733047b143303ffe0f50533066401970c20821710d67c0c98cc2d821708ca7c05bf500a97a530985532ca45059857119855108455fe07ca6707bf7a0a97aa3198aa30c895057c57017c55c89a0a7ca20178b7ff78b5fe7ca778b201830a9588fc78b795bbfc64c0aca3a7541c635897c730987732967a7b1498aa1196a764bf7c0c97cc3198cc309588fec89c0c7cc7017ccb951c1ec88c0c78c70178cb531a0a2b97aa019588fec8a9097c97017c99951a1ec88a0a78a70178a9281d640c561063b083ca551a0add9588ff95171ec8870786a930787901951a1ec88a0a9a8b0a330801821733095020b806faf68210388215308216289511403200951178ff7b108000808934849a10520a1484992052094a821080009511880028a7f5330a810095197f0164ab987c0484770f88720a867a30957757db2a0778979599ff95baff64c7520ce295b7fe56278000659aab8100641ac87a0a283d330a810095197f0164ab987c0484770f88720a867a30957737db2a0778979599ff95baff64c7520ce295b7fe56278000359aab8100641ac87a0a01330902648733082523015020ba0643f6821080009511880032003309002d0133088000288cf43309002d01330880002880f49511d87b10207b15187b16107b1751098501330a330b0fe4b90b95bbf1958c0784ccf8c98c079584089a93330cee11017b1c08280995aa01ae9a5b01c8a8057d5557054ebfa7058455075205ebaeba26c8a8058255c8a4068266140c8080808080808080d46505d25c0c520c0995aa10acbae0ae9aa2009aa5c88a0a017dac570c93009555ff95aa01ab53f42807018455ff00821c08c85c0c7cc6330c04aac650330c03aac626330201330c02abc6f60095a001ae90eb00c8080c7dcc3305013306bfafc6c90028ea0095a601ae96d200330201c886067c665125e000426c605225ed0052330c9faf0c6528b80095a601ae96ae00c886067c663302015125f0002b6c665225f4005a330c8faf6c602894009a5a01ac9a27ff287a846ce0003305a000aa5c2a287d956c70ff531c30402873955c1fff33060cac6c0e845cfe003305ee00ab5c5f330cc0afc05995a002ae904fc8080c7dcc581cbf34284c955c0fff561c0241330cc0afc63a95a502ae9530c885057d5c3305bfadc52e95a003ae901fc8080c7dcc5a1cbf25950a01ac9aadfe330a82177b78087b7910282333022810330501280b33050228063305030182177b7a08787210787511330a01017b7a8210208215188216109511283200951168ff7b1090007b1588007b168000647c7c77087b1b107b1a08330a0133050152077b016492648382c68067347cc9098478047b1c1852088d00826828330a4f0f01826720828b183308520f01da9a0885990364257b13b4b002bc0682186459821c18330501330a0152073201826a2882672082aa18b4a002be06330a01821c1833050152071701826828826720828a1833084d0f01330902b4a002c006330a01821c183305015207f4008217086468821910b49002c206330a01821c18647528db00520932826828826720828a183308540f013309037b126435b4a002c40664538212330501821c18330a015207ae008167340182682082692833050178153f7b18207b192895183f7b18308168307c6938826a826b08826c108266187a17747a18707819787b1a407b1b487b1c507b16589517207b17603307d02c017b1768951720643864295020c6066e52074733084d0f019517203309025020c8065b520734951840821708821910b49002ca06520723821868821760828a183308570f01330902b4a002cc066475821c18330a012809821c18330a010178c508461c090164c78210900082158800821680009511980032009511807b10787b15707b166833063304491148330b0a0a0a0a14000a0a0a0a0a0a0a0a140a00010101010101017b1a60827a107b1a40827a7b1a388277087b17307b18509588ff7b1833077105017b17089a977b171033050a7b19287b1020283e8217c867077c779577f6887701018218407887821730827a18c94609821850c84808821738b4a002d006645482192882165882102033050a5207610182174884770152075301ae690a280301ac690001c96902821350c8630333070fac2723aa69ea003308821c10c86c0c643a017cabaa5b3a9588ff95aa01ab8cf528cd00953b0784bbf8c93b08510831330a01c8a3077c77aa571a95aa01aba8f57b14487b1658952af0ae8a2228599a8a01c8a608958601ae98992881007b14487b1658952af095bb08280695bb080182bcf882b6d30c04d30606821560c96507d4670714068080808080808080c94505d45c0cd2c707d26707ab670c95881095bb10ae8acdaa288d00330ac88307c9890b821658c96b0b82144833050a01c8a70c7cccaa5c0b95aa01ababf52821c88a0ac8a608958601ae9819ffc8a3077c77ab5710ff330764657b165828126496017b165833070164456496aa943f7b17488217407c77510724821730827a183309048217388218087b15186445b4a002d2066454821518520717ab467bfe33072883fe649682144828b933072806330701018210788215708216689511800032009511d87b10207b15187b16108276107c69827b827508510938825c18330a71050133090464b77b180833087105017b1bb4c002d606821b82180851071433070182102082151882161095112832009587f68877017867825c2064b7821020821518821610951128320c6489828a08828818330b01aaba11520a1152080e33093308012871fd51080b3308d02c01281cf682998298829908285cfd9511907b177b18087b19107b1a1864177b17503307b8077b17589517107b17603307aa067b17683307b02c017b1720491128024911409518507b18304911380295172064b828f5ed9511a07b177b180864177b174033078e067b17489518087b18507b17583307882d017b1710491118024911309518407b182049112802951710649828b7ed9511b07b177b180864177b17403307b8077b174833070009017b1710491118014911309518407b18204911280195171064982882ed00951168ff7b1090007b1588007b1680007c7a10827c330501520a1701648b827608806834848a047b17187b1c10520a4f7b1b7b190888c201330b520f0182692833085c0f01826720829318dbcb08330b02c92b09b43002dc06821c1064788217185208ce0082176468821908b49002de06821c10647582171828b600520c337b1b7b1908826828826720828a1833085d0f01330902b4a002e0066478821718821c1052088d00816834821908821b01826720826a2833050178153f7b17207b1a2895173f7b17308167307c6a388263826c088262108266187a18747a1770781a787b13407b1c487b12507b16589517207b17603307d02c017b176895184064b7b49002e206520723821868821760828a183308570f01330902b4a002e4066475821718821c102809821718821c100178751095c8017b78821090008215880082168000951198003200951170ff7b1088007b1580007b16787c7a08330b01330501520a1301648c82768068347c7a09848b047b1710520b49510a32826828826720828b183308520f017b190833090264c5b4b002e606645c330b0182190864788217103305015208ce0064c76468b49002e806330b01647582171028b900520a36826828826720828a183308600f017b19083309013305017b1cb4a002ea06821c8219086478821710330b015208880081683401826720826a283305017815377b17187b1a209517377b17288167307c6a38826b8263088262108266187a186c7a1768781a707b1b387b13407b12487b16509517187b17583307d02c017b176095183864c7b49002ec06520723821860821758828a183308570f01330902b4a002ee066475821710330b012809821710330b0101787508461709018210880082158000821678951190003200951160ff7b1098007b1590007b16880064758217e8007b17708217f0007b17688257288216f8007b1660821600017b16588216c8007b16508273188217d0007b17488217d8007b17408217e0007b17388217a8007b17308217b0007b17288217b8007b17208217c0007b17188217a0007b17108257207b1c087b1b64a6b43002f0067b1578781780004621810095177864688219821a08821b105020f206c0f7951778821830821928821a20821b185020f406abf7951778821850821948821a40821b385020f60696f7951778821870821968821a60821b585020f80681f77c1981007c188000d4890751094284880152083c8217787c783484880452081a827828827720828a1833085a0f013309023300fa06320a827828827720828a183308590f01330901b4a002fa068477018210980082159000821688009511a00032009511c87b10307b15287b16205618271d978a02330bf00101c8ba0a81aac8ba0a320a33085c3028d201330a5caba80a33085c5c28c501330a0003aca804018499015109fd0088892f1801330a11949a95ac0897cb023309541a01c89b0b81b68a8b0b8a660bd86b06da6c0a95a604976c02c89c0c81cc8acc0bd8cb0cdac60a95a602976c02c89c0c81cc8acc0bd8cb0cdac60a95a601976c02c89c0c81cc8acc0bd8cb0cdac60a95a601976c02c89c0c81cc8acc0bd8cb0cdac60a97ac02c89c0c81cc8acc0bd8bc06d3cb0b88bb01c86a0ac8ba0a330c21acacf40297ab02c89b0b80b99896153302ef02511a210d80bc0498c215510a0c80bafc84aaffff1f01856bffc82b0b510b30330cbfa80b330aef02e4a6039522ff330adc1a0101aa639f02c86a097c99be9c0caccb0c956601ab62ee64260184660152060a01551820db008b8914330a300901c8a9097c99868b0178191797892c98993cc8a9097c9c97893098993cc8a9097c9669b9989902781c18781619978b3498bb3cc8ab0b978c3898cc3cc8ac0c84880fc88a0a9598fe4611167cbb7ccc7caa471114781b1a781c1b781a1c46111d7d951a14c88a0b330c5c78bcc8a9094619ff7546097b811914811a187f1b1c28020133085c6e2828849900015109dc0133085c27281a33085c72281433085c74280e97992f5909c30133085c22017a784717084717064717043308000279780a01821030821528821620951138320053187f99018b89105209be00330629016e823303df1601330a2f1701330c511801330928330b220128c1008b8914330a300901c8a9097c99868b0178190b97892c98993cc8a9097c9c97893098993cc8a9097c9669b9989902781c0c78160d978b3498bb3cc8ab0b978c3898cc3cc8ac0c84880fc88a0a9598fe46110a7cbb7ccc7caa471108781b0e781c0f781a104611117d951a08c88a0b330c5c78bcc8a9094619ff7546097b811908811a0c7f1b10017a797a7a04797b0878780a33080a78780b282fff8b89115209633306e6016e823303d11301330a291401330cf9140133092c330bd000017b16647564276486643833000a079511d07b10287b15207b16187b1c083303821c307b1c987008979901c8980c8476ff007b1a1028ef03646864796457510904fe286f848afeff1f513a1eb802f9fd8489e0ff1f5139e0a602edfd8389c048fd5619f9e1fd83895031fd5619f1d7fd838a1014fd330bf0acabcafd83890008fd56295df6c1fd83890000fd56291dfab6fd8389b0ecfc5619faabfd838900fff1330aaf22f5ac9a9cfd5538f0010e98fd3309800078797a78042850fe3309c82e013308ef026437283ae63309b02e0133082264a7282de600951130ff7b197b1a08532801014f7d7300013302bf330c03ad32197d73ff00330c025a13bf0f7d7cfe0089ccc085cc010195c3fd00c8370c7dcc581cbfcd007b17107b1318330205330cee12017b1c207b1228ae9817285b7b17107b1818330c017b1c20491128ac984aaca847ae9aa50064177b17800033078e067b1788009518087b1890007b1798009517107b17a0003307b8077b17a8009518207b18b0007b17b8003307a82d017b17503307042840d89807db790a7b1a409517407b17800033078e067b1788009517107b1790003307b8077b1798009518207b18a0007b17a8003307382e017b1750330703017b1758491170951880007b18607b176895175064b828d3e43309643a33000c0728f1fe510912ae890fc8970c7dcc89ccc0dbc90a017b1a306489ae8a3a330903e49a029522fd95ac01ae2c0f3309682e01642764c828a1f6c92c09c87a0a3303c00151090e7dac9599ff95aaffad3cf5c8290901c8970a52090bab891364b7280becae891c7da25a12bff328167da88487ff005708177a173c3308012876aa89dc648a33000c07286afe7cac0184781f84cc3f5427df00337ca20297c306842c3fd4c30c5327f0002a7ca70397883d98882b97cc0684773fd4c707d4870751370000119f2811978706d4c707280997870cd4c707017a173c3308015327800017987a0b330802510a0c9877108e7895880301c898087b19407b18489517307b17800033078e067b17880095173c7b17900033070e077b1798009517407b17a00033071a077b17a8009517107b17b0003307b8077b17b8009518207b18c0007b17c8003307e82d017b17503307052893fe9511c87b10307b15287b1620828528828920825a2064763308277b190864977b1ab4a0021007647833070152085d816895171433090101951614502012078df97c1714522780002581181882150864578219b49002140752072d33082764578219b49002160728227c181e7c191f825a18c98909c868088215086457b4a00218075107d93307010182103082152882162095113832009511e87b10107b15087b1664758277648650201c079eed52071c826828826720828a183308de0e01330902b4a0021e075107133307018210108215088216951118320082570864688210108215088216951118285ced7c847c8901958802c89302ab0421ac32850064b4ac2b8b00c8a3030151091a7c359533019599ffab65f52855ac40146423abc8cf280c6423644b821a10abc8c3821c088214c8c404330801017dc995ca0157090d64acbf970759071e2826aa4a86007cca0195cc0284997f979908d4a909bf970757070e858801ab4cd2280533080184870182102882152082161895113032003309982e016437642828fef3642764489511a07b177b180864177b174033078e067b17489518087b18507b17583307682d017b1710491118024911309518407b1820491128023308982e0195171028abe13307802e012845e99511b87b10407b15387b1630827a7b1898ab043308143309da0f01532b71028300330814951b2e14074b598638d6c56d347b1710491108102733007b143303ffe0f5053305640164a4821710d67a0a98aa0b821708ca7a0cbfc406976c3098cc32ca0c0c98c71198cc1084ccfe07ca5707bf7606976631986630c89c0c7cc77ccc01c896067c62017c6678bcff78b7fe78b20178b69588fc95bbfcac43ac541a633697a73098773296777b14987711967b64bfba0a97aa3198aa309588fec89a0a7cab017caa951c1cc88c0c78cb0178ca647a01551a0a149588ff95171cc8870786a9307879281c97aa019588fec8a9097c97017c99951a1cc88a0a78a70178a901951a1cc88a0a9a8b14330801821733095020220741e28210408215388216309511483200951178ff7b108000817b3309810095177f01649a8bbc0484bb0f88b20a86b93095bb57db290b787b9577ff95a9ff64cb520ce295a7fe56278000289a9b8100641ac87a0a3309026487330825230150202607dfe1821080009511880032003309002d01330880002828e09511d064783307f02e017b174911080149112049111008491118641728f4df9511b87b10407b15387b16306475827a95a70151077e97a8018fa9da970898893a330752096e330904e498069769063308f1140bf8ffffffffffff7fac9b55510a1482570897aa067b17187b1a2833070828053307017b17206417330808951a18502028073529ff82175207198217087b57087b5682104082153882163095114832008217088218103309002f01281fdb3309002f012817db9511b87b10407b15387b16306475827a95a70151077197a8018fa9da970898893b3307520961330904e49806976905570955510a1482570897aa057b17187b1a2833070128053307017b17206417330801951a1850202a07a928ff82175207198217087b57087b5682104082153882163095114832008217088218103309182f012893da3309182f01288bda9511e07b10187b15107b16086475827a108277c9a7076496ac97297b1a825708c8a707646950202c077c258217c876067b5610821018821510821608951120320064577b1864a8646950202e070c8218825a1028c89511b87b10407b15387b1630c88909ac895064758277977801330a08e4a909e4980657063d5107108258087b18187b1728330701017b172064179519186468502030073882175207238217087b57087b56821040821538821630951148320033073309302f0128cdd98217088218103309302f0128bfd99511d07b10287b15207b1618829a087b1710510a3282971051072c7b1782957b180850203207f31d51074964766458821950203407a424645750203607d021821508280d648550203807cf1d647601886701330801db66088219107b98087b95107b978210288215208216189511303200330682150828d97c779777033309282301c879098299330af03201828b28c8a707827a82872082bc1864a8320c9511907b10687b15607b1658828a97a902330b900201c8b9098199828608828810c8b9097b1720330b330c3209330c646b2807646b648c017b18107b16183307013306f73308330964a5330042070a12016478ae6746570807017b1808510853648650204407151d51071e017b1733086469502046070f26645a9757023308d80201c887078177c88707330b330c64693207330c821b18283f5218ffcc00140700000000000000808218207b8728a200645a9757023308200301c887078177c88707330b330c3207330c821b182849821b18821c10013305f78217647633080a12015317f7295217ffa500140800000000000000808217207b78646782106882156082165895117028632064652833821b18821c10013305013307013306f7330833090a12015317f71a5217ff8200140800000000000000808217207b7828118217208218087b787b75087b78100182106882156082165895117032003307f02f0128c7d733074032017b17284911300149114849113808491140330850320195172828dfdb646728c0d733074032017b17284911300149114849113808491140330850320195172828b9db33074032017b1728491130014911484911380849114033085032019517282898db951178ff7b1080007b15787b1670648664756487502054075901837752070f826b08978720987a20aeab20140700000000000000807b57018210800082157882167095118800320083885108d50064a77b157b1610826a330c3306491120016478017b182833070040e67805c96c077b1b30ae5722c85609ac69d0005709cc007b1a08510c168217207b17587b1c68330701280ac8560728343307017b1760951740330801951a587b1938502056078f24ff8217405207aa008217487b1720821738647c821b30821a08017b1738ac678400ac5b64821720c8670764a8645964a67b1c18502058078621821c18821b30c95b0bc8560a8217107b7a821828c958087b7b088219386496520859ff140700000000000000808215aa7c12ff280b330c330949112001017b5c8217207b57087b59102805ff8215510cf3fe82172050205a075f1e28e6fe33073309a82f012802d63309902f016467821838284eda8217488218503309a82f0128e7d59511c87b10307b15287b1620827a08330901510a1a01827b7cb895a2ff95b5017b757b720851080101330cff00abc85f330c09accaf9007cb8027cb9017cbc037cb604978808d4980897cc10976618d46c0cd4c8087cb9067cbc057cb6077cb508979908d4c909976610975518d46505d45909979920d4980895aaf795bb097b7b7b7a089887208e7928a3006c8a570a0a3306491118284c84a84033060151083984a82033060251083084a81033060351082784a80833060451081e84a80433060551081584a80233060651080c84a801330607520868491118ae6208330901284c7b170895171864587b1a1064697b1250205c070c208217c96707c865058218087b857b87088567079b77ff821818821910e07907976903cf9707c878089887208e7928089887208e7901649782103082152882162095113832003307782f012889e09511b87b10407b15387b16306475827a95a7015107830097a801e47807330804e487063308b800d68609330752096a9669b8003308f1140bf8ffffffffffff7fac9b56510a1582570896a8b8007b17187b182833070828053307017b17206417330808951a1850205e072322ff82175207198217087b57087b5682104082153882163095114832008217088218103309c02f01280dd43309c02f012805d49511c87b10307b15287b1620827a08330901510a1101827b7cb895a6ff95b5017b757b7608510863330cff00abc861330c09accaf10033097cb8027cbc017cb6037cb504978808d4c808976610975518d46505d485027cbc067cb6057cb5077cb80897cc08d46c0c975510978818d45808d4c808978820d4280895aaf795bb097b7b7b7a08289e0064892899006c8a570a0a3309491118284c84a84033090151083984a82033090251083084a81033090351082784a80833090451081e84a80433090551081584a80233090651080c84a80133090752085e491118ae960833090128427b170895171864587b1a107b16649650206007291e3309c865058218087b858217c967077b87088567079b77ff821818821a10e07a07976603cf6707c8780801649782103082152882162095113832003307782f0128b0de9511b87b10407b15387b16306475827a95a7015107830097a801e47807330804e4870633089800d68609330752096a966998003308f1140bf8ffffffffffff7fac9b56510a1582570896a898007b17187b182833070828053307017b17206417330808951a18502062074a20ff82175207198217087b57087b5682104082153882163095114832008217088218103309c02f012834d23309c02f01282cd29511887b10707b15687b16607b17103305f77b1864877b190864983309330a330b0a010164765517f724570660510632646850206407501651078200647533086469502066074a1f281b5216ff48140700000000000000808218107b87281f3305010182178218086459330a646b0a018217107b75087b767b7610018210708215688216609511783200330738310128a5d19567097b1618551707278d78067818203307981f01330a883001330b503101330844951920287ce764672897d19517187b1750330720077b175833071832017b1720491128014911409518507b18304911380133082832019517202880d59511887b10707b15687b166064753307ff7b18087b1910330a330b330c0a020164763308f7ae86415106507b1557068d006468502068075f155107890064753308646950206a07591e3307ff821808821910645a330b646c0a0282177b75087b767b761028205216ff2c140700000000000000807b57280e49053307017b57084915100182107082156882166095117832009567097b16185517072f8d78067818203307981f01330a883001330bc83101330844951920288ce63307b031012885d06467289fd09517187b1750330720077b175833071832017b1720491128014911409518507b18304911380133082832019517202888d4951158ff7b10a0007b1598007b169000647533084c50206c07851451070702647633094c330850206e077e1d3307ff64680a04015517f7f4017c67197c68187c691a7c6a1b977708d4870797991097aa18d4a909d497077c681d7c691c7c6a1e7c6b1f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17787c67117c68107c69127c6a13977708d4870797991097aa18d4a909d497077c68157c69147c6a167c6b17978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17707c67097c68087c690a7c6a0b977708d4870797991097aa18d4a909d497077c680d7c690c7c6a0e7c6b0f978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17687c67017c687c69027c6a03977708d4870797991097aa18d4a909d497077c68057c69047c6a067c6b07978808d4980897aa1097bb18d4ba0ad4a808978820d487077b17609567207b17504911582c95175050207007e6fa7b18285207cc0095175050207207d6fa7b18205207bc0095175050207407c6fa7b18185207ac0095175050207607b6fa7b181052079c0095175050207807a6fa7b180852078c0095175050207a07b4f883777b1852077b821778821870821968821a607b17487b18407b19387b1a30646750207c07be16821748821840821938821a307b57187b58107b59087b5a8217287b57208217207b57288217187b57308217107b57388217087b574082177a57488210a00082159800821690009511a800320033074c2846ce3308ffab871b646750207e075e163307dc1f013309d8300133081f288ee49578097b1750551807228d87067817603307981f01330a883001330b98310133084495196028e0e39517507b1730330720077b173833071832017b176049116801492180009518307b187049117801330828320195176028e8d151092a827b0001330aff0001acba1e7c8cc87b0b78bc827b000195880195bb019599ff7b7b00015209e5330732009511a0fe7b1058017b1550017b17330a2e2001330701330b15330833090a6433050149210801330843200195170833090f50208207a164177b174001330786077b17480133070009017b1710017b15180149213001951740017b1720017b15280195170895181001502084073401821b0801330701951a08330833090a6400951168ff7b1090007b1588007b1680008285288276828720825a1833080d0f0133090c7b17187b1a10b4a00288073308015207d8007b168267089578109579147b17503307b8077b17587b18603307b6067b17687b19707b17783307602c017b1720491128034911409518507b18304911380395192082161864677b1508645850208a078dd8510708330801287d3308190f013309026467821a10b4a0028c073308015207668217827a82a77b175082a9087b195882a8107b186082ac187b1c6882ab207b1b7082aa28330b017b1a78aab913821808520913520c103309330801281b821808510c10951950646750208e071fd828128278827908016467821a10b4a002900764780164878210900082158800821680009511980032006489828a08828818330b01aaba0b520a0b520808283e51080b3308482f0128d2d7829982980851082c829b827a00013309ff0001aca91e7cbcc87a0a78ac827a000195bb0195aa019588ff7b7a00015208e532009511f8481104552880000e9519057818042880008b890b52091c951906988a0686aac000781a0484883f8688800078180528608b8910520929951907988a0c86aae000781a04978a3498aa3a95aa8000781a0584883f868880007818062834951908988a1286aaf000781a04978a2e98aa3a95aa8000781a05978a3498aa3a95aa8000781a0684883f8688800078180701827a0001951b043308ff0001aca81d7cbcc87a0a78ac827a000195bc0195aa017b7a000164cbab9ce6330795110832006489828a08828818330b01aaba0b520a0b520808283e51080b3308482f0128bdd6829982980851082c829b827a00013309ff0001aca91e7cbcc87a0a78ac827a000195bb0195aa019588ff7b7a00015208e5330732009511e8fe7b1010017b1508017b1600014921c8009517e0009518c8005020960732f18218e00014070000000000000080aa7882028217f00053170861028479f8511908590251191054028479fe5119184c02957be6531b044402957be2531b043c02957ade531a223402957abc531a122c0251195627029579a85319061f02957aa2531a101702957a92531a100f0295778253170807028217e8007c7c187c79197b19607c721a7c701b7c731c7c741d7c796b7c7a6a7c7b1e7b1bb0007c7b6c8a9908d4a9097c7a1f7b1aa8008aba107c7b207b1ba0007c7b217b1b98007c7b427b1b387c7b437b1b287c7b547b1b307c7b557b1b207c7b567b1bc0007c7b577b1bb8007c7b5c7b1b707c7b5d7b1b687c7b6d7c757a7b15587c757b7b15507c757c7b15487c757d7b15407c7582007b1590007c7583007b1588007c7584007b1580007c7585007b15788abb18d4ba0ad49a065108247b121864057b13107b14087b1c502098075611821c8214088213106450821218018467015207480151064e0133070810c3670752074b018215b8009755088218c000d45808510843018217a8008a77088219a0008a9910821a98008aaa18821bb000d4b707d4a909d49707c3870852082201821888008a8808821980008a9910821a788aaa18821b9000d4b808d4a909d49808ae780701821768977708821870d487079678abaa6e8856285555fb00821860978808d4c808978901c898086e88ab87ee008218508a88088219488a9910821a408aaa18821b58d4b808d4a909d4980852288000d60039096c27035629ff00d500970908973a10974b18d42909d4ba0a821b2897bb08821c2097cc08d4a909821a38d4ba0a821b30d4cb0b3d096827033d0a6c27033e0b6027033c0758270382101001821508018216000195111801320033075a1f01330a083001330b2830013308239519ff0028c7dd33077d1f01330940300133081b283dde330704230133081d2857330780320128cfeb3307c8220133083c2845330798320128bdeb330793220133083528333307602201330833282933072e2201330832281f330701220133082d28153307ca2101330837280b3307882101330842017b17e0007b18e8003307522001330ae03101330b00320133081f9519e0002837dd9511907b10687b15607b1658828a2882797b19408276088285207b1a2082a92033082264577b1910b490029c073308015207670251061e027b153302330a3303a149110801000133002233045c82154064687b1618280fc95808c89a0a8216185108c0013307c885097b193801c875097c9b95bc81ac3c12aa0b0faa4b0c957701ab78ec2898017d988486ff007b123057080b9599017b1928285d7c9b0184681f84bb3f330cdf00ae6c337c9c0297bb0684cc3fd4cb0b330cf000acc62e959c047b1c287c990397883d98882b97bb0684993fd4b909d48906281d9599027b1928978806d4b806280f9599037b192897880cd48b0601c8a70595174c646882190850209e07bce07c174c522780001664593303a182123033002233045c28d9007c17577c1856bf87078477ff0082123033045c5217010f64593303a133002228b700ac257d01821840c82808510216821718ae720c7d875a17bf0b286401ab72610151051c821718ae7512821740c857077d775a17bf0b284801ab754501821720827a18c9250982177b1a30b4a002a007520716017c174c52278000188118508217821910b49002a20751072328fa007c18567c1957c9890995174cc878088217821a30b4a002a4075207de00330701532680001798680b33070251080c9867108e77957703016459c857023303a133002233045c01821838821528330a0133078000ac7654fe98670b330a0251074afe9866108e6a95aa03283efec88a0a01ac2a8c008215510230ae6236821740c827077d7764285817bf76510a21ae6a29821740c8a7077d7764825817bf6264a6281b330828173308520ae53306280e6468aa62d928486482ab6a44821720827a18c98609821740c878086457b4a002a6073308015207133308226457821910b49002a8076478016487821068821560821658951170320033080128ed330b302d018217406468642933000c0728dbe2330b182d018217408218186429645a33000c0728c5e2951130ff7b10c8007b15c0007b16b800827508827955250304a0005105da018287207b17308287287b17283307b4077b1720140720000000080000007b171833070009017b1710017b19389517387b17708217207b177849218000024921900049219800024921a0008217187b17a8004621b000038217107b174049114801951880007b1860491168019518707b18504911580164969519408217308218285020ac07cfcf520754019555ff9569015205992843017b15088287207b17308287287b1728330600023307b4077b1720140720000000080000007b171033070009017b17187b196495017b15389517387b17708217207b177849218000024921900049219800024921a0008217107b17a8004621b000038217187b174049114801951880007b1860491168019518707b1850491158019519408217308218285020ae0730cf5207b5009566ff95550152069b821728827a183308212301330903821730b4a002b0076478330701520891008217088218c88707957500fe33060002140720000000080000007b1710017b15389517387b17708217207b177849218000024921900049219800024921a0008217107b17a8004621b000038217187b174049114801951880007b1860491168019518707b1850491158019519408217308218285020b2078bce5207109566ff95550152069c33072806330701018210c8008215c0008216b8009511d0003200951178ff7b10800082777c77330a810095197f0164ab847a0f98770488a20a86ac3095aa57db2c0a789a9599ff95baff5207e495b7fe56278000289aab8100641ac87a0a330902648733082523015020b607efc7821080009511880032003309002d01330880002838c6827a827908648764a82847ca9511b8fe7b1040017b1538017b1630017d79330b1fad9b075209a7037d7a015a1a1f08520a9c037d7b0233061fadb607520b8f037d7c035a1c1f08520c84037d720433061fad2607520277037d74055a141f0852046c037d730633051fad350752035f037d76075a161f08520654037d75087b15c0005a151f0c8215c000520541037d75097b15b8005a151f0c8215b80052052e037d750a7b15b0005a151f0c8215b00052051b037d750b7b15a8005a151f0c8215a800520508037d750c7b15a0005a151f0c8215a0005205f5027d750d7b1598005a151f0c821598005205e2027d750e7b1590005a151f0c821590005205cf027d750f7b1588005a151f0c821588005205bc027d75107b1580005a151f0c821580005205a9027d75117b15785a151f0b821578520598027d75127b15705a151f0b821570520587027d75137b15685a151f0b821568520576027d75147b15605a151f0b821560520565027d75157b15585a151f0b821558520554027d75167b15505a151f0b821550520543027d75177b15485a151f0b821548520532027d75187b15405a151f0b821540520521027d75197b15385a151f0b821538520510027d751a7b15305a151f0b8215305205ff017d751b7b15285a151f0b8215285205ee017d751c7b15205a151f0b8215205205dd017d751d7b15185a151f0b8215185205cc017d751e7b15105a151f0b8215105205bb017d751f7b15085a151f0b8215085205aa017b1888a801889a01d4a80888ca0188bb018e99d4ba0a85bb03db890bd4a808884901882a01d4a90985aa05db8b0ad49808886901883b01d4b90985bb07db8a0bd498088219c000889901848a01859c09dbab0c821ab80088aa01d4a909d498088219b000889901848a01859b0bdbac0b821aa80088aa01d4a909d498088219a000889901848a01859c0ddbab0c821a980088aa01d4a909d4980882199000889901848a01859b0fdbac0b821a880088aa01d4a909d4980882198000889901848a01859c11dbab0c821a7888aa01d4a909d49808821970889901848a01859b13dbac0b821a6888aa01d4a909d49808821960889901848a01859c15dbab0c821a5888aa01d4a909d49808821940889901821a4888aa01821b5088bb01d4ba0ad48a0a84880185bb17db8c0b84a801859c19db8b0c821838888801d49808d4a808821920889a01821928889901821b3088bb01d4b909d4980684880185b91bdb8c0984680185ab1ddb890b8218088e8895881f33091e821c10dbc809821818888801d4a808d46808848801db8b0964789517c80064855020bc07ffcd8217c80051076282186457017b17c8004921d000089517c8007b1720013307aa077b1728013307d032017b17f0004921f80002951720017b1700018287208288284921080101492110019519f0005020be07e8c98210400182153801821630019511480132008217d0008218d8007b17e0007b18e8009517e0007b1720013307b8077b1728013307b032017b17f0004921f80002492110019519200182188287208288287b19000149210801019519f0003300be072881c99511807b10787b15707b1668648564763307800033081f6469015107137d9a9599019577ffada8f5510af228293309800001c876087c8851080b957701ab97f52805647901641764685020c207f8cc821751073e7b164911080864177b17583307aa077b17603307d032017b1728491130029517587b1738825720825828491140014911489519283300c40728f2c88217088218107b17187b18209517187b17583307b8077b17603307b032017b1728491130024911489519588257208258287b1938491140019519285020c407b4c88210788215708216689511800032009511807b10787b15707b1668648564763307900033081f6469015107137d9a9599019577ffada8f5510af228293309900001c876087c8851080b957701ab97f52805647901641764685020c80719cc821751073e7b164911080864177b17583307aa077b17603307d032017b1728491130029517587b1738825720825828491140014911489519283300ca072813c88217088218107b17187b18209517187b17583307b8077b17603307b032017b1728491130024911489519588257208258287b1938491140019519285020ca07d5c78210788215708216689511800032003307011409e0ffffff1f000000ae8907330732009511d87b10207b15187b161095861f3305080003955948278499fc013e04000003977420994420819ad44a047a943a0400000384aaff00520ae59557002082770006986605510734011407ffffffff00000000aa760d015648e0ffff3f090183620164675328c10f2a69278d7918c6920a84aa7f9099ffe092098e99977707c97a0ac8a9099597ff079577800401330b0020c8b509829a38078b7906d09a0c84cc01510c1d979c03955b0020c8cb0b82bb0806330cffcf7c07d2b7075207219599019b97ffd2a7075107d4006a7997970395580020c8870782770806016a748a9706d44707977702c8750b80bc80ca04836798aa01aea76b7b12089557002082730006bf3c008b0805c868028568ffbea808817744077b188e88c82808838a01aea72433076577c9370797a825988220ae270bc97207657751072b955700207a7a44070180ca0881cc8407e0c837077aba510aa300481a0c821408821028d000330701955800204628480782102082151882161095112832007b18085020cc078b011408ffffffff0000000064793307aa86d38218085209c1fe28c964675428c00f1f69278d7818c6820884887f977707c978089587ff0795778004018b7906d09a0884880151081e979803955b0020c8b80882880806330bffcf7b07d287075207fdfe9599019b97ffd2a7075207dffe2872ff979a0333020020c8250bc8ab0b82b808063303fedc430ad2a8087bb8080682140882105208169552002082283807dc9309d298087b283807019748019588019779209899207a9c7a9804957320976a05c8a30951008b00640754107f2a330700000002e67007697a8dab18c6b70784777f97aa07c9a7079577ff079577800495770101837bff8bb70684b23f97bb02c8b50c80cb7ac9970c01979a2098aa207aa47aac047aab08481a0c510b077ab90c01977a03955c0020c8ca0a82ac08069b2801d4c8087ba80806955b002082b838079b7701d487077bb738076404019557002081784007817a0006970705be9707978805bea808ae87149777209877207a749737209877202868fe973720987720285ffe33087127033307657795881f8488e0ae8714c9780765793307510962fe520809285bfe51084333076577c9870756171f0d9a79206597510745fe3303080003953b002081ba4007482b4407013307000000027bb80006aca71c510a12552a80001383a4ff282833072813fe3304281f64a70169798d9a18c6a70784777f979907c997079577ff0795748004018b4b0697b70395390020c87909829c0806330701cf470ad4ca0a7b9a08069532002082293807812a4007cfb70bd4b9097b29380797aa0148087a8a0448180848180c974402c843037a3828a5fd3303080003953948278499fc330a01013e0400000397a420994420819bd44b047a943a0400000384bbff00520be5510794029511d87b10207b15187b16109570e09709209537002082750006989920809a04819bbf500c98a901541c1fd60097ba05bfa00c97ca2098aa2080a4048446015206bf009848013306000000027b185644000000041651042a6486552400010a958bff282069688d8518c6560684667f978807c986069568ff07958b80042805330b0181a87b180897b52098551ec83505815280a608abc20d7a5651061648160c284780aa0c7aa60851063f7a6a0c28398bb606976a0395380020c8a808828a080684bc3f3305fedcc50cd2ca0a7b8a0806520a169532002082283807dc650ad2a8087b2838070195380020828500068218be8909974404bf4000821b0801817a400797aa05be5a02979c05be0c0cae2cd10097ca2098aa2080a6048467015207c1007b1b08986b013305000000025646000000041651062a64b5552600010a95b4ff282069578d7818c6850884887f977707c978089587ff0795748004280533040197472098771ec87305815780a608abc7137a5651062248160cbeb909821b08285e80aa0c7aa60851064f7a6a0cbeb909821b08284a8b4a0697a703330c0020c8c308c887078278080684453f3306fedc5605d258087b78080652081d953c002082c73807dca608d287077bc73807beb909821b082809beb909821b080151098900649a54197f2a330700000002e6790769788d8a18c6a70784777f978807c987079577ff07957a800495aa010183a8ff8b8a06848c3f978802c8380880867a809798019707209877207a7b7a78047a760848170c5106077a600c0197a70395380020c88707827808069bcc01d4c8087b78080695360020826738079ba801d487077b67380701979705be0707ae270c9777209877207a790182102082151882161095112895370020462748073200953700204627480732009511e87b10107b15087b16531910698d7a84a207c82706ae671a642c647a648b017cb578a595aa0195ccff95bb01520cf3c82804c929028423f8844807c8360a51083eaea64e9748038480388445f8825b8d88848c38955808018285d00b0bcfc509d4b9097b69956608958808645baca6eb281f647ac8970bacb7242831aea61364480182897b69956608958808aca6f6c83408842907c89a0baeba137c8b78ab95aa019599ff9588015209f3821010821508821695111832009511d87b10207b15187b1610c9870aae9a8500c8970c330a10c89804aca91b0184c20784c0f89a23aec01ac898059555ff017c5695cbff78c6ff9555ff64bcacb0f3c83404c929097b19088499f8844b079a9a7b1ac9900c510bb100ae0ccc00974903849338844bf882b58d9984993895bbf864060182bacf9505d03a02d452059568f87b65f895bbf8648664a5ac8ce82897005319106b8d7a84a207c82705ae571a642c647a648b017cb678a695aa0195ccff95bb01520cf3c82804c929028423f8844807c8350a51088a00aea599009748038480388446f8826b8d88848c38956808018286d00b0bcfc609d4b9097b59955508958808646baca5eb2869647ac8970bacb76e287bae0c1f821908c898089588f8640901828a959bf87b9af89588f864b9acbcf38218c8800cc8840482190884990701c99c08aec848954bff017cb995caff78c9ff95bbff64acaca8f32831aea51364480182897b59955508958808aca5f6c83408842907c89a0baeba137c8b78ab95aa019599ff9588015209f38210208215188216109511283200531910558d7a84a207c8270baeb713642c647a0178a895ccff95aa01520cf8c92909849af8c8ba0aaeab22978238140c0001010101000000d6c202972c20d42c0c017bbc95bb08acabfb849907c89a0bacba0d2815647ac8970baeb70e78a89599ff95aa015209f832005109167c7a7c8babba139599ff9588019577015209f033073200c9ba07320021449248924a9224292529499224294992a42493a49288881455921409295145442480520009204a22499294449224495242924892a4942488228a484494049024521111a98888448988a822228088882401948a884800250144a988885444442a22228924499224499224499212404914228088882401942844440450921021222a218a484492882422454422225223122122222212a3488a440489882412914244a2a4445222224a444489880812058982880822228888880891444424228088484544240a1140444492004a1422808888240194444992944452224989101285082022224900250a1140444492004a00252925414a9224499294004a0154194a0025494450442208a002684444894422224924912289482252231221222222899822492291481249249244128924494a2422892442125191442229914422914422112989884842124944121111112426495212045022491149a228120144120194244992244992244992882492004a92440489085188108588081122491240024825494a045049929494a40a2a93a48888240920a5004a929442842892a44c22124992489148225223122122229229498914944492244948282929499224499224491249922449922449924492244992244992249124499224499224924422422489888244005186244902484992441011514488428448124045449494942449922449922491244992244992244924499224499224491249922449922449922422892492242521128944228944222989449248241289444a22499224499224499104d088884a924494880aa21001248054248944929224499244241289249124124992482291481289442249124992244992244992884844442448228288288988489248249224490248005152529224499224499224499224499224499224499224499224499224499224499224499204504992244992244992949424154050992425491240a2a4a424499224499224499224499224499224499224499224499224499224499224499224499224494a49244d92922449929424499224499224499224494952414a9224490009204a4a4a92244992244992244992244992244992244992244992244992244992244992244992002a49922449922449929292a402082a93a48888049000a29224298924499224499254122529499224495242008d88288a9424499224499224499224494a9224499224499224aaa04422912449000920952429114025495252922aa84c92225292144a9224249594224b92a42409a254644992942441942904224a34a54a42529225291051880811494a4921214988a4409224499224244a121292a422494a490a4991244a044a8152924492244942424999242424492a45920aa048524848924881a4442224914492905022490a0949122990942491244992905052260989324948489292a4922409094992449324059224932409092d49aa841492924a924a922424244992449324059224932409092d494a925292425224891288122825492449922424949449424292a4522524422222254944145992242525214a459624494949883285404421224444a4248988224b92a4a42444a9c8922429290951a6108828448488520124494849921452084912299094249124499290505226090949529294922424244992449324059224932409092d494a924a9224242449124d921448924c922424b42429494a9224242449a249920249924992848496242549459a92a49892d40a94a24442224922894892241212499248204994a49414129284480a244992244942a2242121492a495229252121494a024812492029492249922421a1a44c1212922425492549121292248926490a2449264912125a9294422449294921214988a44088a4409224499224244a1212922445244994a0482489249224a42425494a912549929292204a459624494a4a82285308449410892449529246535225492a69490a4414224244449224228a2c4992929210a5224b92a4a42444994220a210112222922411516449929494842815599224252521ca1402118588109124154012404a24a494240124929444521249810248a49424294952a09424294952a09224492449495292420a25891281a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a414484a81a4144852929090242549892a41922495a4404a922425494152522029490a2124114248224952494a498194020994920aa4aa929249529294249524294912409224a929094a52929484089124952429499224494d49509292a424448824a99424899448002989243549822429492a928a22492a4952922480244952531294a42429091122492a91244949920092244952531294a42429091122492a499248892491a4a624284949521222449224952449924c4941494a920a9184482a92a4244942429224a29124052249264912125a9294242549121292248926490a2449264912125a92540049125292248554084912299094249124499290505226090949524444152801b488888a88109124801440525226914424922489a44892489244a46a44228888442453a42449494912492251498924084992244992244992242989442a229152942449922449922411112905922429492249492422aa1849924424112935229120229232224925812449929214894424aa484425484512491248128912499224492422495292a44029a2880452240288882825a1a21011210a1111224488124049114944940490a44412114992448a244a12911a91082222112999922491489248241249922449922489244992245212499244a22289241289444a22499224912449a4242292204912498a288a44001111a52c22a2880824224421429490425252492a2429a4902491024989444822892421a14492145248924881a42489244992848492324948944942429294a422490a292449a440522211924822494828912429a490249102494912499224090925659290289384842429292212492224920889244242482a909424a9a424a9400124809214400550004b5252529292a4004a4a52524a929424920a525292022593a414485281920aa41042121111298054529254a00450002b22a29444a22452228944922491229124223592081249924c494a228124499224218992929224499224499224912449922449922449244992244992244912499224499224499224120992a42401244ac9a4248904928428448888880248024809299524491222424444444488444224111222224424110891281022512044a240884481108902215281882412914492902849489224420a8908914420229148124942a2244924899048222489901092244922a49088104981104904221289249124244a92444821112192022192084424124922494894241152484488240211894492481212454412492224920849222442485620494292241152484488240211894492481212254922a49088104981104904221289249124244a92481252484488a440882481104904221289249124244a924448211121920221920844241249224948942411524844882402118944924812122549841412112289404422912492844449122185884424028912891011492409092592241152484488240211894492481212254922a49088104981104904221289249124244a9208292422441281884422492409899224420a8908914420229148124942a224892421854484480a84481208914420229148124942a22489904244221281448944888824928484124992082924224412818844224924098992848c882849489424244a12122509899284444942a224215192902849489424244a1212c524215192902849489424244a1212250989629290284948484444495292420a25214a8124494a9224244a1212922445441225918424492422492042142521134992481291944022499290844c44a2242111894245442451124948924422920422449292908924492489484a204992242421139128494844a25091a42485149284480a244992244942a2242121494a52424a4942929424855408494224059244224924098992842429490aa9109284480a244992244942a22421494a12122509094952929412494292a490244948214988a440882481249148124942a22449482149882481249148124942a224495248214988a40884a40221920492244992242151929028494894242424494a924a92242593242545529224299924499292499248929249922429992489242999244992924992489292499224299924892429992449929249924892924992242999244992944c9248929249924892924992a444529292a49249122989a42449c924899224a90249264955054a8aa40a2429502a50924a202549259092241489842244925404229124a92449922449924412811229495244444444444444448408111122021111929a02212210014412111111288a24224a9208201240149188241149929288241211911a910822898888c48822492422222222222222129188442422118948442212918844242211894844a244249148222222229292244992244992249224499224499224892449922449922449224992244992241149924424254992244992244924499224499224491249922449922489489224922449922449922489484a922449922449924892244992244992249224499224491291244924499224499224491291884849922449922449124992244992244992449224499224499224912449922449922449442051449444441245242202111111111111119124898848449244449226220211111129222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222202111109209214c84822221245924822110821242222804444a24023114004108180282291541289a440454444444402455224222949119188241149929288244a44a4a81189209288882862144991884024228944a2888804118944442022224204914412119188482212490051144124252202914444442022224284880811414484a4680a44244991849428224992244992244992489224499224499224922449922449922489942449922449922411111111111111888888a826499224104948815455201189444424924444444444840811112122888890d4144820294a922424354952202229522289249144129188888888102122424410111111d124492402111148441252d144480924210542922a222292244992244992248948922449922449922422499224499224499288942449922449922411116992884812911411111111111111111122444488082222243505229288888888a4401211914431123111498124228954a0882222922449922449922489489224499224499224224992244992244992889424499224499224111111111111214244848820224252532022a2492222492449922449922449124992244992244992449224499224499224919224499224499224a249444444881011112282880849d148244992244992244912499224499224499244922449922449922491922449922449928824a249029184140849aa888888888824491189884912111111214244848820222224355220926a8448444488101121228888a86a4481444a444412910249442451448c488891924022111a29092412414b442022111a118940444444442205222212291011918802111189281091888888881021224244101121a92910810811128d2411910492420aa4aa222a9a908888888888102122424410111111111122444488082222226a922452202202898888888888088c081111111121222244041111929a0291888844442422222244448828222244441011a99a02111111d1484424892449922449922449229224499224499224894892244992244992242225499224499224914444443489888888880811222244041111929a0221118986904892244992244992242249922449922449928824499224499224492252922449922449124944442d21222222440849948888880891404444222222224284880811414484a4a640024911254992244912929a481289882822118988888888888848202925499224499224492481a4482211491422458994118908444424909492244992244992248914912489880822221211818888881021222244041111929a0211442249922449922449924892244992244992249224499224499224899424499224492449121111888888881021222244041111929a02111121154d242212114544229124294952a088888888a8454422229240524881545544442d1211898888a84622428a2612492092420aa4aa2222898824922411918888081122224244101121a92910119104922222229288d424498188442022222222a24644222249444448d124492423222222222244888810114444488aa68c8888881021224244101121299a029126222222222242848808114144a48aa640442225492a50141111191111111121424444880822222435052222222210111141444448d1244944444a69920291888888888888888810212242441011a9a2291011111145554481482092420a84245520222222aa15681211111122444484882022425234052222229288888888189224a411111111111111112244448808222224350522a2494844a2114222225192244944115104a2882862922449922411912889442449922449922449129124499224499224494492244992244992241129499224499224492222222291261289442081488a922449922409494d241211092495244952a06a522081480a29109254110522924891482219111111112142444488082222243505221118112222222242444488082222243505222222a24944246544242222111111112222441411112222888890144d8188482009118920222146420289288814888854228988484444445044249324054a06904a352222222222222222222222a244a2245112118188924889249240222212058a28228aa811111111111189a8a02262225224128944229144241191144822124914442452449240249288242211051189189128908822119324494984484144242a88884482888822624402498848041109312221814814458b282111491249111122222946229128222292444492942492129188d48848041149444444230a8948a248241291442412494492241191888888288988484442224992244992244992242222a51451922449922449922411111115052289482211114962228ac248229288484444a4d48888444410111111458c884815818848124544001191349448928888444444241111912449922449922449229148249292244992244992244922499224499248922449922449124992449248241249444492888820111111814844220a94122311884420898888444a921428221188c48824121111954444442225490a149108442205119148414424228088884853248024450a12894420229208242205a28888281191428a08098944918244221108212422851411129288900821241209204a248924a908449202954444448810112144121112218444121112218444121112218444429142222222224488880821121111214244841009212422111111224444089188889010929224492242228490482242228490489224222442088924222442088984842422244208892422244208892422244208892422244208892422244208892422242284908824222442088984081122442222224488881022111111224444089188880811222284484444448408111121442222224488881022111111224444089188888808112222428844444444881011114224222242848808211211112142448410898888102122428844444488101121442222224488881022111111112222441411112144242222224284888810228908891042228908891042228908891042228908891042228908891042224944444484081111214442840811224912111111214244440891242222224284888810222184442424112129881042222584908844841012098988881021224288444444881011214422222244888810221111111122444484108988888810212222844844444821214922422284904844444484081111214422222222448888082112111111214244440891888888081122224288844444549024111194445052441001248948928888880811222244041111929a82002211112142049000128948484828808420292291882849a218111188482222884892228488244992241289829224910821224244045092240920499224092049922401244992244988128904918888889824499294242222224992888824499288884892242222499224222292248988489224898888244922229224912422228888444444444488402222922449922449922489244992244992244922499224499224499248499224499224491251d542120549549088484204221141222222222249244a44128da2442491111111111122444448908488101124111121291a5190888408442452249148499282222248924492288d282251905441aa281a91484a249222498288840844245292243142924824a2308aaa8818914884880811414424a92988888888481488884824298988884022224284880811414484a4a62092884841129148a22092488448005124889124499224494a8c882445929448892222522411622492208944884992244992a4882411224992244992144922449224499224299244882449922449522411228920220a242222299188882492089182484444124922449224299244882449522489104992a42491240a124512459224499224298988928824495224914841944824828844111149222292481221929444445224922089884844a220a28888244944a2205124512449922449929288284924499224499222898814448944228848242222894822449224292289882449922449524412114992244992a448224412414422229212898848129184489224251211911211914412214622099248849824499224498a4812211111c9224984489224451221920489444424894822111149222292449424224992244992148988944844445212118988222211444444141125444422099208914882248248441089105110118914442452101191888288884444242389102391044924424c92244992242512119124118144442489884812918448922445122146449220918848894444248944222229918888648924028988484a2422524922120551a448244112119188444144912012298888448288448248248844824824884482482488448248248844824824884482442241229120914810892888448248248844824824482412442205119148109148108904914810892888448248a4202212092212092291209148108904894482482488448248248844824824884482482488440a2222518222120922912012092291201209221111119128a24022910892041111114592829024849048444488101121444284081122218444424a491284101209092124928844082191901042222184442222428488082112424824492a4892525054505450aa88888888884849822405d592a410424a1211514192444450445044508488200248441411415124a2482280244a24119124492245244a24223522118888444432a2248924914812914824924422914812894422491249922449922449924422111144229128128924892422224a2412494424128944121112492412894422898888884824128944229148242222228288882092082022222988884488002222228a888888828888041129824841414422222549924c108d4424881241223549144410450411051191208a88241151504444449011111111111121222222228844101121a929888808112222440411111111111141441051d424918288824422a888882224924492444884101289441042482422a49090442405114248242222448888102211112142448410092124124292aa148a444a2a919048124992242949252489244929292d499224493252521049a554ca2491544a4841420849529a826492242549a98292424a92904a529294524a0a4a4a92aa8252924449a9a04949922492922492445212492249245322a524419992a42a2825494992a494902429a5a4a42a482949aa0a4a49524992244932a5a0242549852421295130a5449284449248a5249192904812294d92921024012444b02449922491242949494a124924499294a44692549294a4495221494a9252929024954a5a92a49ac29224499224112949a52091922429499244915290241349428a24299324992409c992a45492204949485232892421294942a29224499224492a289542a524a992a42649495229894429a5882a93949448524a4d552246a444528d2451d29224919224a524492a49b22449020802480001242549922449125124491169499224499224499224499224292992244b9224499224495224911449529a24c92449489224a12425932449488a2492942425299924a59048914849294922499244a224495292644a92244492a42492944aca249148522a2993a42409892489922449495296240929094992945452929484a4268992a492102401244490499224491249929424a594124924499294a44892925451244993a482242922491291c89224292909518a2c4992929210650a414421122249529424490a299192242905902489b24892484a9224122989249242214922251492348a48114512912449514492a4244991a8a44a294b92254911112549a24a9248484252a1924492902492848424911292922421152a4964922449529224499284a442421292242449482829992822a224a904010490a42449212549a5644a9224942491441249922869499224694a9224354992a44c499200922449a22449a624258914959a5492922449a1244aa932499424a9242125144a922425a524a924494a1252494a522249242492444a4a892409892491925222219124524644a4444992948484244948894a48494a9290501249932449929224499224248524090925259322228a8824894a922449480a2525512125491292144a4964922449529224499284a4902421a1a4642222514444914822914822914824128944920a8948254192244892044992202249922449489424242422a2248984942249242211521252922429499292242949929224254992484a449284494a2461a424692224a42449492a49922449522549242991248922a2284ac492442649211185101249484992925492244992a44a924852224996244912851012ab8a2449294924551402211042082111821084104204a224440929a38448128948460a49429292244914118948442422242549442422121192492429154952322549122a49a62499a4a4a848528a240949129224495052320949522649442422121189884484244992a4a442498904094985a44249921492495292aa2049424292945252529244912949295592922525459224496a92a450a99412491212492224214952490892001222c8922449924492a4242529492491244952921a4952495292264905495244912549525212a2a4104414222914899424a924252549024892243525819294242521214952494a4a9224495253122849495212129224a59492829224a92a949224a9244992644aa1242549852421499224a904a9828254d024494a924a25a490945492949092a4aa6a9254d11249415441092955a510404a0929552999aa11024825492a99a41a2180545232490a098944425242229190844422218948aaa02449008d242a2a55349124894892a4d424492091a46422499520495292140114552c498a0429249424214992442525228924499224499224499292524a49922449922449922425a5829294244992545a92149224954892488904902492d494044a5292948484244912959444122549922449922449929494524a92244992244992242929159454922449b2242924492a9124911209204924a9290994a4242909094992a452d52229882a28024865a94aca24292489248490a4249190481221499292542d4951105590a4aa9422802ac924298924849024a4249190481221898828419482d49024499224499224499224499224499224499224499224499224494992244992244992241224122412241224122412549224490a92244949922449898828490a4248924842084992484844122149c4942249a422a290548990842222444444248248aa111129094942a224892492844492484a4152480949952449922449524a525059428b88a824494a484911538a249192902449228994249124924849248924914446624a914429952449092929624a912452111111114400112121242424242484848484844892244922492289449224492291244992248988888824494a0a4aca44442212894824124912914812492249a22049522449922441089224499224092184888842088984908414125248482185145248212342088924494a92a48444540992949244c99424498a24294924492249922429494a924909124a922489244a1249922291242912494a8884949024494a888424294b92492292484946495291241555a54a2549524852424b925248454252918888144492440248c824498810899044249228491049922489480009a94992102112218948249124882449484289948800324912224422241189249204495219115144459624494912a2a410441422a512115112894812894812894812894844442222121191888844442422221111898848444412892412492492482491482291442289441289241249249248249148229144a22449499224499224912491249224924492449248924812499224499224499224499224499224499224499224494a9224498a82488d10111122442421228888282222222244882889109124a94853921453925a41294a242492249248922491904892448224519254a429498a2949ada014251212491249244992484824492241922801a49224213224a51089480410012994242949c448244922912449a2446249524a129124894a52499212312545521a91242508a092a850922462922412492249249144924889486292942451042949129149924a52926422914444128d882449529224a192a422492552848810441252a996248998442249444412294952422443520a91489224222909499244820229a4922411b524292929494a2291249188244694242349224922090229a49224113549494952922425914492442292484a4611a424494426492a49499289441211494c923449228a284951926a254992244952c99294944a4a2b4992549224a524492489a4244b52922495882495b424492a8992542b49922411492a599292524949d292949264922529a59524492a4992a428492d49922480244b925452925249924a", + "003f00b000000000322492fe2a551a1b92091558671642b4cf3c7bb91ab8ea": "0100000000", + "0b000000000000000000000000000000000000000000000000000000000000": "00000000", + "08000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "07000000000000000000000000000000000000000000000000000000000000": "ff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499ace00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001409c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d93300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001419c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e34100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001429c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001439c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001449c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c0600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffff7f000001459c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" }, - "genesis_header": "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25bb30a42c1e62f0afda5f0a4e8a562f7a13a24cea00ee81917b86b89e801314aaff71c6c03ff88adb5ed52c9681de1629a54e702fc14729f6b50d2f0a76f185b34418fb8c85bb3985394a8c2756d3643457ce614546202a2f50b093d762499acedee6d555b82024f1ccf8a1e37e60fa60fd40b1958c4bb3006af78647950e1b91ad93247bd01307550ec7acd757ce6fb805fcf73db364063265b30a949e90d9339326edb21e5541717fde24ec085000b28709847b8aab1ac51f84e94b37ca1b66cab2b9ff25c2410fbe9b8a717abb298c716a03983c98ceb4def2087500b8e3410746846d17469fb2f95ef365efcab9f4e22fa1feb53111c995376be8019981ccf30aa5444688b3cab47697b37d5cac5707bb3289e986b19b17db437206931a8d151e5c8fe2b9d8a606966a79edd2f9e5db47e83947ce368ccba53bf6ba20a40b8b8c5d436f92ecf605421e873a99ec528761eb52a88a2f9a057b3b3003e6f32a2105650944fcd101621fd5bb3124c9fd191d114b7ad936c1d79d734f9f21392eab0084d01534b31c1dd87c81645fd762482a90027754041ca1b56133d0466c060000ffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "id": "dev", + "protocol_parameters": "0a00000000000000010000000000000064000000000000000200004b00000c000000809698000000000080f0fa020000000000ca9a3b00000000002d3101000000000800100008000300403800000300080006005000040080000500060000fa0000017cd20000093d0004000000000c00000204000000c0000080000000000c00000a000000", "bootnodes": [], - "protocol_parameters": "0a00000000000000010000000000000064000000000000000200004b00000c000000809698000000000080f0fa020000000000ca9a3b00000000002d3101000000000800100008000300403800000300080006005000040080000500060000fa0000017cd20000093d0004000000000c00000204000000c0000080000000000c00000a000000" -} + "id": "dev" +} \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 9e11eed1..99f165fe 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -68,9 +68,9 @@ def send(self, stream_id: int, data: bytes, end_stream=False): def connection_lost(self, exc): - logger.info("QUIC UDP transport closed:", exc) + logger.info("QUIC UDP transport closed") self.protocol.disconnect(self) - super().connection_lost(exc) # keeps aioquic tidy + super().connection_lost(exc) async def _keepalive(self): @@ -103,7 +103,7 @@ def quic_event_received(self, event: QuicEvent) -> None: # Initiating side will send a JAM handshake message and set the stream id stream_up = self.open_jam_stream(StreamUP, direction=self.direction) self.stream_up = stream_up - self.protocol.up0_send_handshake(self) + #self.protocol.up0_send_handshake(self) else: # Note: it seems only now we have this info available from the accepting side self.host, self.port = self._quic._network_paths[0].addr @@ -139,7 +139,7 @@ def quic_event_received(self, event: QuicEvent) -> None: # If we're on the acceptor side of this stream, send a handshake message back if self.stream_up is None or self.stream_up.stream_id < stream_id: self.stream_up = stream_obj - # Note: handshake will be sent after processing the incoming message + #self.protocol.up0_send_handshake(self) # Only the first time an acceptor receives a message, we expect a stream id byte data = data[1:] diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 0ffd14c4..b547cb46 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -150,11 +150,14 @@ async def connect(self, host, port): def disconnect(self, connection: JAMConnection): if connection.direction == StreamDirection.initiator: - self.conn_initiated.remove(connection.jam_connection_ulid) + if connection.jam_connection_ulid in self.conn_initiated: + self.conn_initiated.remove(connection.jam_connection_ulid) else: - self.conn_accepted.remove(connection.jam_connection_ulid) + if connection.jam_connection_ulid in self.conn_accepted: + self.conn_accepted.remove(connection.jam_connection_ulid) - del self.connections[connection.jam_connection_ulid] + if connection.jam_connection_ulid in self.connections: + del self.connections[connection.jam_connection_ulid] def up0_send_handshake(self, conn: JAMConnection): From b324b797b633c578b0fa59c4fda7b49dd30e397f Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 17 Jul 2025 20:56:29 +0200 Subject: [PATCH 30/51] JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 61 +++++++++++++------------ 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 99f165fe..f393b9e9 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -103,7 +103,7 @@ def quic_event_received(self, event: QuicEvent) -> None: # Initiating side will send a JAM handshake message and set the stream id stream_up = self.open_jam_stream(StreamUP, direction=self.direction) self.stream_up = stream_up - #self.protocol.up0_send_handshake(self) + self.protocol.up0_send_handshake(self) else: # Note: it seems only now we have this info available from the accepting side self.host, self.port = self._quic._network_paths[0].addr @@ -125,34 +125,37 @@ def quic_event_received(self, event: QuicEvent) -> None: stream_id = event.stream_id data = bytes(event.data) - - if stream_id not in self.streams: - if self.direction == StreamDirection.acceptor: - stream_type = int(data[0]) - stream_cls = StreamLookup.get(stream_type) - if stream_cls is None: - raise Exception(f"QUIC stream {stream_id} is not mapped (type {stream_type})") - - stream_obj = self.open_jam_stream(stream_cls, direction=self.direction, stream_id=stream_id) - - if stream_cls == StreamUP: - # If we're on the acceptor side of this stream, send a handshake message back - if self.stream_up is None or self.stream_up.stream_id < stream_id: - self.stream_up = stream_obj - #self.protocol.up0_send_handshake(self) - - # Only the first time an acceptor receives a message, we expect a stream id byte - data = data[1:] - else: - #TODO: of idem? kan een accepting connection bv ook een block request terug sturen? - raise Exception(f"Received data from unknown stream id: {stream_id}") - - self.streams[stream_id].receive_data(data, event.end_stream) - # try: - # self.streams[stream_id].receive_data(data) - # except Exception as e: - # #TODO: reset stream? return error? - # logger.error(f"JAMStream received invalid message for stream {stream_id} ({self.streams[stream_id]}): {e}") + + logger.debug(f"StreamDataReceived: stream_id={stream_id}, data_len={len(data)}, end_stream={event.end_stream}") + + try: + if stream_id not in self.streams: + if self.direction == StreamDirection.acceptor: + stream_type = int(data[0]) + stream_cls = StreamLookup.get(stream_type) + if stream_cls is None: + raise Exception(f"QUIC stream {stream_id} is not mapped (type {stream_type})") + + stream_obj = self.open_jam_stream(stream_cls, direction=self.direction, stream_id=stream_id) + + if stream_cls == StreamUP: + # If we're on the acceptor side of this stream, send a handshake message back + if self.stream_up is None or self.stream_up.stream_id < stream_id: + self.stream_up = stream_obj + #self.protocol.up0_send_handshake(self) + + # Only the first time an acceptor receives a message, we expect a stream id byte + data = data[1:] + else: + #TODO: of idem? kan een accepting connection bv ook een block request terug sturen? + raise Exception(f"Received data from unknown stream id: {stream_id}") + + self.streams[stream_id].receive_data(data, event.end_stream) + except Exception as e: + logger.error(f"Error handling stream data: {e}", exc_info=True) + # Reset the stream with an error code + self._quic.reset_stream(stream_id, error_code=2) + raise elif isinstance(event, ConnectionTerminated): logger.info(f"QUIC connection terminated with code {event.error_code}") From 4ac8ea8d219fb7186d7c561b2a3f745bdcb3c02f Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 17 Jul 2025 21:07:41 +0200 Subject: [PATCH 31/51] JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 4 +++- pyjamaz/transport/jamnp_s/protocol.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index f393b9e9..f09b9bb2 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -51,7 +51,9 @@ def close_jam_stream(self, stream: Stream, reason:int=0, clean_close: bool = Fal def is_connected(self) -> bool: """Check if the connection is active and ready""" - return self._quic is not None and not self._quic._close_pending + return (self._quic is not None and + not self._quic._close_pending and + self.stream_up is not None) def send(self, stream_id: int, data: bytes, end_stream=False): diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index b547cb46..65513165 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -244,6 +244,11 @@ async def up0_broadcast_block(self, block: Block): ).to_jam_bytes().to_bytes() for client_id, client in self.connections.items(): + # Check if UP0 stream is established + if not client.is_connected(): + logger.debug(f"Skipping broadcast to {client.host}:{client.port} - connection is not fully established yet") + continue + logger.debug(f"up0_broadcast_block send block header to client {client.host}:{client.port} with hash {block.header.hash}") client.send( client.stream_up.stream_id, From 31e7113e42ed5285ee0e3071d46d2af034b6d78c Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 17 Jul 2025 21:16:49 +0200 Subject: [PATCH 32/51] JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 9 ++++--- pyjamaz/transport/jamnp_s/streams/stream_0.py | 24 ++++++++++++------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index f09b9bb2..97253201 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -36,6 +36,7 @@ def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_i logger.debug(f"QUIC accepting stream {stream_id} for {direction}") self.streams[stream_id] = StreamCls(stream_id, connection=self, direction=direction) + logger.debug(f"Added stream {stream_id} to streams dict, total streams: {len(self.streams)}") return self.streams[stream_id] @@ -99,7 +100,8 @@ def quic_event_received(self, event: QuicEvent) -> None: # Both nodes are validators, and are neighbours in the grid structure. # At least one of the nodes is not a validator. if self.stream_up is not None: - raise Exception("There can be only one UP connection active at a time") + logger.debug("UP0 stream already exists, skipping creation") + return if self.direction == StreamDirection.initiator: # Initiating side will send a JAM handshake message and set the stream id @@ -129,6 +131,7 @@ def quic_event_received(self, event: QuicEvent) -> None: data = bytes(event.data) logger.debug(f"StreamDataReceived: stream_id={stream_id}, data_len={len(data)}, end_stream={event.end_stream}") + logger.debug(f"Current streams in dict: {list(self.streams.keys())}") try: if stream_id not in self.streams: @@ -149,8 +152,8 @@ def quic_event_received(self, event: QuicEvent) -> None: # Only the first time an acceptor receives a message, we expect a stream id byte data = data[1:] else: - #TODO: of idem? kan een accepting connection bv ook een block request terug sturen? - raise Exception(f"Received data from unknown stream id: {stream_id}") + # Initiator receiving data on unknown stream - this is an error + raise Exception(f"Initiator received data from unknown stream id: {stream_id}") self.streams[stream_id].receive_data(data, event.end_stream) except Exception as e: diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py index ec308d77..73e328e8 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -30,10 +30,14 @@ def initiator_message(self, data: bytes): match self.state: case UPState.IN_PROGRESS: - self.state = UPState.CONNECTED + try: + self.state = UPState.CONNECTED - msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) - self.protocol.up0_received_handshake(self.conn, msg) + msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) + self.protocol.up0_received_handshake(self.conn, msg) + except Exception as e: + logger.error(f"Error processing handshake in initiator: {e}", exc_info=True) + raise case UPState.CONNECTED: # After handshake is completed, we receive Announcement messages @@ -60,11 +64,15 @@ def acceptor_message(self, data: bytes): match self.state: case UPState.IN_PROGRESS: - self.state = UPState.CONNECTED - - msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) - self.protocol.up0_received_handshake(self.conn, msg) - self.protocol.up0_send_handshake(self.conn) + try: + self.state = UPState.CONNECTED + + msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) + self.protocol.up0_received_handshake(self.conn, msg) + self.protocol.up0_send_handshake(self.conn) + except Exception as e: + logger.error(f"Error processing handshake in acceptor: {e}", exc_info=True) + raise case UPState.CONNECTED: # After handshake is completed, we receive Announcement messages From 4b951eccbaca0983403644776be403e7d74d3ada Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 17 Jul 2025 22:04:33 +0200 Subject: [PATCH 33/51] - JAMNP WIP --- pyjamaz/cli.py | 2 + pyjamaz/transport/jamnp_s/connection.py | 44 ++++++++++--------- pyjamaz/transport/jamnp_s/protocol.py | 13 +++--- pyjamaz/transport/jamnp_s/streams/stream_0.py | 31 +++---------- 4 files changed, 38 insertions(+), 52 deletions(-) diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 3db56226..fd749073 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -186,7 +186,9 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, # Setup logging log_level = logging.DEBUG if verbose else logging.INFO # Note: Add packages that need a different logging level here + log_level = logging.ERROR log_package_overrides = { + "pyjamaz.transport.jamnp_s": logging.DEBUG, "pyjamaz.transport": log_level, "quic": logging.WARNING, } diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 97253201..21234737 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -25,7 +25,7 @@ def __init__(self, *args, **kwargs): self.stream_up = None self.streams = {} - #self._keepalive_task = asyncio.create_task(self._keepalive()) + self._keepalive_task = asyncio.create_task(self._keepalive()) def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_id: int=None): @@ -109,6 +109,10 @@ def quic_event_received(self, event: QuicEvent) -> None: self.stream_up = stream_up self.protocol.up0_send_handshake(self) else: + if self.host and self.port: + logger.debug(f"Connection already exists for direction {self.direction}") + return + # Note: it seems only now we have this info available from the accepting side self.host, self.port = self._quic._network_paths[0].addr @@ -135,25 +139,25 @@ def quic_event_received(self, event: QuicEvent) -> None: try: if stream_id not in self.streams: - if self.direction == StreamDirection.acceptor: - stream_type = int(data[0]) - stream_cls = StreamLookup.get(stream_type) - if stream_cls is None: - raise Exception(f"QUIC stream {stream_id} is not mapped (type {stream_type})") - - stream_obj = self.open_jam_stream(stream_cls, direction=self.direction, stream_id=stream_id) - - if stream_cls == StreamUP: - # If we're on the acceptor side of this stream, send a handshake message back - if self.stream_up is None or self.stream_up.stream_id < stream_id: - self.stream_up = stream_obj - #self.protocol.up0_send_handshake(self) - - # Only the first time an acceptor receives a message, we expect a stream id byte - data = data[1:] - else: - # Initiator receiving data on unknown stream - this is an error - raise Exception(f"Initiator received data from unknown stream id: {stream_id}") + #if self.direction == StreamDirection.acceptor: + stream_type = int(data[0]) + stream_cls = StreamLookup.get(stream_type) + if stream_cls is None: + raise Exception(f"QUIC stream {stream_id} is not mapped (type {stream_type})") + + stream_obj = self.open_jam_stream(stream_cls, direction=self.direction, stream_id=stream_id) + + # if stream_cls == StreamUP: + # # If we're on the acceptor side of this stream, send a handshake message back + # if self.stream_up is None or self.stream_up.stream_id < stream_id: + # self.stream_up = stream_obj + # self.protocol.up0_send_handshake(self) + + # Only the first time an acceptor receives a message, we expect a stream id byte + data = data[1:] + # else: + # # Initiator receiving data on unknown stream - this is an error + # raise Exception(f"Initiator received data from unknown stream id: {stream_id}") self.streams[stream_id].receive_data(data, event.end_stream) except Exception as e: diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 65513165..fa2f5bb3 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -259,6 +259,7 @@ async def up0_broadcast_block(self, block: Block): def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRequest): #TODO: missch een batch param meegeven, zodat we een grotere reeks kunnen binnenhalen maar batchen... + return stream = conn.open_jam_stream(StreamBlockRequest, direction=StreamDirection.initiator) logger.debug(f"ce128_initiate_block_request on stream id: {stream.stream_id} direction: {req.direction}, max_block: {req.max_blocks} header hash: {req.header_hash}") conn.send( @@ -282,6 +283,7 @@ def ce128_received_block_request(self, stream: StreamBlockRequest, req: MsgCE128 def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE128BlockRequest): + return block:Block = None blocks:List[Block] = [] first_block_hash = bytes(32) @@ -324,13 +326,9 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 stream.create_message(MsgCE128BlockRequestResponse(blocks=blocks).to_jam_bytes().to_bytes()), end_stream=True ) - - #TODO: check ook direction!!! - if not block or block_req.header_hash == last_block_hash or block.header.timeslot == 0: - #TODO: PolkaJAM lijkt in dit geval een reset te sturen???? + else: #TODO: ook op max_blocks checken -> bijhouden op de stream? logger.debug(f"ce128_send_block_request finished") - # stream.acceptor_reset(1) # Replaced with FIN stream.conn.send(stream.stream_id, b'', end_stream=True) @@ -351,8 +349,8 @@ async def ce128_processed_block_request(self, *args, **kwargs): self.state_requesting_blocks = False - def up0_failure(self, reset_code: int): - logger.error(f"up0_failure with code {reset_code}") + def up0_failure(self, reset_code: int, direction:StreamDirection): + logger.error(f"up0_failure with code {reset_code} ({direction}") # TODO: handle UP0 reset, e.g., reconnect @@ -362,6 +360,7 @@ def ce128_block_request_failure(self, reset_code: int): async def ce131_initiate_distribute_own_ticket(self, data): + return #TODO: wrap in a dataclass and pass that in the signal epoch_index: int = data[0] attempt: int = data[1] diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py index 73e328e8..37af5acc 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -35,6 +35,8 @@ def initiator_message(self, data: bytes): msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) self.protocol.up0_received_handshake(self.conn, msg) + if self.direction == StreamDirection.acceptor: + self.protocol.up0_send_handshake(self.conn) except Exception as e: logger.error(f"Error processing handshake in initiator: {e}", exc_info=True) raise @@ -49,38 +51,17 @@ def initiator_message(self, data: bytes): def initiator_reset(self, reset_code: int): - logger.debug(f"UP0 received reset code: {reset_code}") - self.protocol.up0_failure(reset_code) - super().initiator_reset(reset_code) + self.protocol.up0_failure(reset_code, self.direction) def acceptor_reset(self, reset_code: int): - self.protocol.up0_failure(reset_code) - super().reset(reset_code) + self.protocol.up0_failure(reset_code, self.direction) def acceptor_message(self, data: bytes): # Note: in case of UP0, initiator and acceptor have symmetrical message flow - - match self.state: - case UPState.IN_PROGRESS: - try: - self.state = UPState.CONNECTED - - msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) - self.protocol.up0_received_handshake(self.conn, msg) - self.protocol.up0_send_handshake(self.conn) - except Exception as e: - logger.error(f"Error processing handshake in acceptor: {e}", exc_info=True) - raise - - case UPState.CONNECTED: - # After handshake is completed, we receive Announcement messages - msg = MsgUP0Announcement.from_jam_bytes(JamBytes(data)) - self.protocol.up0_received_announcement(self.conn, msg) - - case _: - raise RuntimeError(f"Unexpected state {self.state}") + # (except for sending a handshake response, which is checked using the connection direction) + self.initiator_message(data) def handle_fin(self): From 2e400723bc3c850cc2821af096f0af823444f800 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 17 Jul 2025 22:09:08 +0200 Subject: [PATCH 34/51] JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 21234737..38fc14b1 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -147,11 +147,10 @@ def quic_event_received(self, event: QuicEvent) -> None: stream_obj = self.open_jam_stream(stream_cls, direction=self.direction, stream_id=stream_id) - # if stream_cls == StreamUP: - # # If we're on the acceptor side of this stream, send a handshake message back - # if self.stream_up is None or self.stream_up.stream_id < stream_id: - # self.stream_up = stream_obj - # self.protocol.up0_send_handshake(self) + if stream_cls == StreamUP: + # Register UP0 stream reference for both acceptor and initiator directions + if self.stream_up is None or self.stream_up.stream_id != stream_id: + self.stream_up = stream_obj # Only the first time an acceptor receives a message, we expect a stream id byte data = data[1:] From a468d5c112f2371c152f00893dbe20e68b16e75d Mon Sep 17 00:00:00 2001 From: matthijsb Date: Thu, 17 Jul 2025 22:14:03 +0200 Subject: [PATCH 35/51] JAMNP WIP --- pyjamaz/transport/jamnp_s/connection.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 38fc14b1..79301e0b 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -139,24 +139,19 @@ def quic_event_received(self, event: QuicEvent) -> None: try: if stream_id not in self.streams: - #if self.direction == StreamDirection.acceptor: + # Remote side initiated a new stream – first byte is stream type stream_type = int(data[0]) stream_cls = StreamLookup.get(stream_type) if stream_cls is None: raise Exception(f"QUIC stream {stream_id} is not mapped (type {stream_type})") - stream_obj = self.open_jam_stream(stream_cls, direction=self.direction, stream_id=stream_id) + stream_obj = self.open_jam_stream(stream_cls, direction=StreamDirection.acceptor, stream_id=stream_id) - if stream_cls == StreamUP: - # Register UP0 stream reference for both acceptor and initiator directions - if self.stream_up is None or self.stream_up.stream_id != stream_id: - self.stream_up = stream_obj + if stream_cls == StreamUP and (self.stream_up is None or self.stream_up.stream_id != stream_id): + self.stream_up = stream_obj - # Only the first time an acceptor receives a message, we expect a stream id byte + # Strip stream type byte for further processing data = data[1:] - # else: - # # Initiator receiving data on unknown stream - this is an error - # raise Exception(f"Initiator received data from unknown stream id: {stream_id}") self.streams[stream_id].receive_data(data, event.end_stream) except Exception as e: From e515a7d2a4675d71f1ab58f1722aac57b706fee8 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Fri, 18 Jul 2025 14:58:50 +0200 Subject: [PATCH 36/51] - JAMNP WIP --- pyjamaz/cli.py | 5 +- pyjamaz/logger.py | 23 +- pyjamaz/transport/jamnp_s/connection.py | 62 ++- pyjamaz/transport/jamnp_s/protocol.py | 420 +++++++++--------- pyjamaz/transport/jamnp_s/stream_base.py | 8 +- pyjamaz/transport/jamnp_s/streams/stream_0.py | 3 + .../transport/jamnp_s/streams/stream_133.py | 4 +- .../transport/jamnp_s/streams/stream_134.py | 2 +- pyjamaz/transport/protocol_fs.py | 81 ---- 9 files changed, 294 insertions(+), 314 deletions(-) delete mode 100644 pyjamaz/transport/protocol_fs.py diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index fd749073..9de58b7e 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -95,7 +95,7 @@ async def cli_import_block(self, block: Block, dry_run=False): def wrap_produced_block_jamnp(app: PyjamazApp, traces_dir, np_protocol: JAMNPS): async def produced_block_jamnp(block: Block): - await np_protocol.up0_broadcast_block(block) + await np_protocol.pubsub_up0_broadcast_block(block) return produced_block_jamnp @@ -189,7 +189,6 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, log_level = logging.ERROR log_package_overrides = { "pyjamaz.transport.jamnp_s": logging.DEBUG, - "pyjamaz.transport": log_level, "quic": logging.WARNING, } setup_logging(log_level, log_package_overrides) @@ -258,7 +257,7 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app) app.protocol = nps_protocol app.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, wrap_produced_block_jamnp(app, record_traces, nps_protocol)) #TODO: is this function wrapping still necesary? - app.pubsub.subscribe(MESSAGE_TYPES.TICKET_ADD, nps_protocol.ce131_initiate_distribute_own_ticket) + app.pubsub.subscribe(MESSAGE_TYPES.TICKET_ADD, nps_protocol.pubsub_ce131_initiate_distribute_own_ticket) tg.start_soon(nps_protocol.listen) if bootnode: diff --git a/pyjamaz/logger.py b/pyjamaz/logger.py index 52d1f141..5763e6d1 100644 --- a/pyjamaz/logger.py +++ b/pyjamaz/logger.py @@ -15,6 +15,10 @@ class LogFormatter(logging.Formatter): logging.CRITICAL: "🚨" } + def __init__(self, width=30, *args, **kwargs): + super().__init__(*args, **kwargs) + self.width = width + def format(self, record): # Prepend the appropriate icon based on log level icon = self.icons.get(record.levelno, "") @@ -25,12 +29,27 @@ def format(self, record): if record.levelno > logging.INFO: record.msg = click.style(f'{record.msg}', fg='red') + + combo = f"{record.filename}:{record.funcName}" + record.funcName = (combo[: self.width]).ljust(self.width) + return super().format(record) def setup_logging(log_level=logging.INFO, package_loggers=None): - log_format = click.style("%(asctime)s.%(msecs)03d", fg=(80, 80, 80)) + " %(message)s" - formatter = LogFormatter(log_format, datefmt="%Y-%m-%d %H:%M:%S") + #log_format = click.style("%(asctime)s.%(msecs)03d", fg=(80, 80, 80)) + " %(message)s" + #formatter = LogFormatter(log_format, datefmt="%Y-%m-%d %H:%M:%S") + + time_style = click.style("%(asctime)s.%(msecs)03d", fg=(80, 80, 80)) + + log_format = ( + f"{time_style} " + "%(funcName)s " + "%(message)s" + ) + + # only want HH:MM:SS, so change datefmt + formatter = LogFormatter(width=50, fmt=log_format, datefmt="%H:%M:%S") # Configure logging handler = logging.StreamHandler() diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 79301e0b..9a40f7f5 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -22,6 +22,7 @@ def __init__(self, *args, **kwargs): self.jam_connection_ulid = None self.host = None self.port = None + self.addr = None self.stream_up = None self.streams = {} @@ -31,17 +32,16 @@ def __init__(self, *args, **kwargs): def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_id: int=None): if stream_id is None: stream_id = self._quic.get_next_available_stream_id() - logger.debug(f"QUIC initiating stream {stream_id} for {direction}") + logger.debug(f"Connection {self.jam_connection_ulid} QUIC initiating stream {stream_id} for {direction}") else: - logger.debug(f"QUIC accepting stream {stream_id} for {direction}") + logger.debug(f"Connection {self.jam_connection_ulid} QUIC accepting stream {stream_id} for {direction}") self.streams[stream_id] = StreamCls(stream_id, connection=self, direction=direction) - logger.debug(f"Added stream {stream_id} to streams dict, total streams: {len(self.streams)}") return self.streams[stream_id] def close_jam_stream(self, stream: Stream, reason:int=0, clean_close: bool = False): - logger.info(f"QUIC closing JAM stream {stream.stream_type} {stream.stream_id} for {stream.direction} (clean: {clean_close})") + logger.debug(f"Connection {self.jam_connection_ulid} QUIC closing JAM stream {stream.stream_type} {stream.stream_id} for {stream.direction} (clean: {clean_close})") if clean_close: self.send(stream.stream_id, b'', end_stream=True) else: @@ -67,11 +67,20 @@ def send(self, stream_id: int, data: bytes, end_stream=False): self.transmit() except Exception as e: #TODO!!!!!!!!!!!!!!!!!! + logger.error(f"Connection {self.jam_connection_ulid} error") logger.error(e) + raise + + + # def connection_made(self, transport): + # Note: initiator does not seem to have its connection info available here + # super().connection_made(transport) + # self.host, self.port = transport._extra["sockname"][0:2] + # self.addr = f"{self.host}:{self.port}" def connection_lost(self, exc): - logger.info("QUIC UDP transport closed") + logger.info(f"Connection {self.jam_connection_ulid} QUIC UDP transport closed") self.protocol.disconnect(self) super().connection_lost(exc) @@ -87,44 +96,56 @@ async def _keepalive(self): pass - def quic_event_received(self, event: QuicEvent) -> None: + def add_connection(self, addr, direction) -> bool: + if addr in self.protocol.conn_addr: + logger.warning(f"Connection {self.jam_connection_ulid} direction: {direction} is a duplicate {addr}") + self.close(error_code=2, reason_phrase="duplicate") + self.protocol.disconnect(self) + return False + else: + self.addr = addr + self.protocol.conn_addr.add(self.addr) + logger.debug(f'Connection {self.jam_connection_ulid} direction: {direction} added {self.addr}') + return True + + def quic_event_received(self, event: QuicEvent) -> None: if event and hasattr(event, "stream_id"): if event.stream_id != 0: - logger.debug(f'QUIC stream {event.stream_id} received data {event}') + logger.debug(f'Connection {self.jam_connection_ulid} stream {event.stream_id} received data {event}') else: - logger.debug(f'QUIC received non stream data {event}') + logger.debug(f'Connection {self.jam_connection_ulid} QUIC received non stream data {event}') if isinstance(event, HandshakeCompleted): #TODO: # Both nodes are validators, and are neighbours in the grid structure. # At least one of the nodes is not a validator. - if self.stream_up is not None: - logger.debug("UP0 stream already exists, skipping creation") - return if self.direction == StreamDirection.initiator: + addr = f"{self.host}:{self.port}" + if not self.add_connection(addr, self.direction): + return + # Initiating side will send a JAM handshake message and set the stream id stream_up = self.open_jam_stream(StreamUP, direction=self.direction) self.stream_up = stream_up self.protocol.up0_send_handshake(self) else: - if self.host and self.port: - logger.debug(f"Connection already exists for direction {self.direction}") - return - # Note: it seems only now we have this info available from the accepting side self.host, self.port = self._quic._network_paths[0].addr + addr = f"{self.host}:{self.port}" + if not self.add_connection(addr, self.direction): + return elif isinstance(event, StreamReset): stream_id = event.stream_id reset_code = event.error_code - logger.info(f'QUIC StreamReset {stream_id} {self.direction} code {reset_code}') + logger.info(f'Connection {self.jam_connection_ulid} QUIC StreamReset {stream_id} {self.direction} code {reset_code}') if stream_id not in self.streams: #raise Exception(f"QUIC stream {stream_id} not available") - logger.debug(f"QUIC stream {stream_id} already closed") + logger.debug(f"Connection {self.jam_connection_ulid} QUIC stream {stream_id} already closed") return self.streams[stream_id].reset(reset_code) @@ -134,8 +155,7 @@ def quic_event_received(self, event: QuicEvent) -> None: stream_id = event.stream_id data = bytes(event.data) - logger.debug(f"StreamDataReceived: stream_id={stream_id}, data_len={len(data)}, end_stream={event.end_stream}") - logger.debug(f"Current streams in dict: {list(self.streams.keys())}") + logger.debug(f"Connection {self.jam_connection_ulid} StreamDataReceived: stream_id={stream_id}, data_len={len(data)}, end_stream={event.end_stream}") try: if stream_id not in self.streams: @@ -155,11 +175,11 @@ def quic_event_received(self, event: QuicEvent) -> None: self.streams[stream_id].receive_data(data, event.end_stream) except Exception as e: - logger.error(f"Error handling stream data: {e}", exc_info=True) + logger.error(f"Connection {self.jam_connection_ulid} Error handling stream data: {e}", exc_info=True) # Reset the stream with an error code self._quic.reset_stream(stream_id, error_code=2) raise elif isinstance(event, ConnectionTerminated): - logger.info(f"QUIC connection terminated with code {event.error_code}") + logger.info(f"Connection {self.jam_connection_ulid} QUIC connection terminated with code {event.error_code}") self.protocol.disconnect(self) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index fa2f5bb3..7af8c75d 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -99,6 +99,7 @@ def __init__(self, host, port, certificate, private_key, app): self.connections = {} # All connections self.conn_accepted = set() # All incomming connections self.conn_initiated = set() # All outgoing connections (who we connect to) + self.conn_addr = set() # Reference with host:port pairs async def listen(self): @@ -122,6 +123,20 @@ async def listen(self): ) + def is_preferred_initiator(a: bytes, b: bytes) -> bool: + """ + TODO: see https://docs.jamcha.in/knowledge/advanced/simple-networking/spec#required-connectivity + """ + if len(a) != 32 or len(b) != 32: + raise ValueError("pubkey must be 32 bytes") + + a_flag = a[31] > 127 + b_flag = b[31] > 127 + lex_lt = a < b + + return a_flag ^ b_flag ^ lex_lt + + async def connect(self, host, port): configuration = QuicConfiguration( alpn_protocols=[self.protocol_name], @@ -132,6 +147,11 @@ async def connect(self, host, port): ) configuration.load_cert_chain(certfile=self.cert, keyfile=self.pk) + addr = f"{host}:{port}" + if addr in self.conn_addr: + logger.warning(f"Ignoring duplicate connection to {addr}") + return + logger.debug(f"Connecting to {host}:{port}") try: async with connect( @@ -145,7 +165,7 @@ async def connect(self, host, port): await client.wait_closed() self.disconnect(client) except ConnectionError as exc: - logger.warning(f"💩 ClientProtocol Cannot connect to {host}:{port} {exc}") + logger.warning(f"💩 Cannot connect to {host}:{port} {exc}") def disconnect(self, connection: JAMConnection): @@ -159,6 +179,9 @@ def disconnect(self, connection: JAMConnection): if connection.jam_connection_ulid in self.connections: del self.connections[connection.jam_connection_ulid] + if connection.addr and connection.addr in self.conn_addr: + self.conn_addr.remove(connection.addr) + def up0_send_handshake(self, conn: JAMConnection): # Create a presistent UP0 stream for this connection @@ -170,7 +193,7 @@ def up0_send_handshake(self, conn: JAMConnection): timeslot=slot, leafs=leafs ) - logger.debug(f"up0_send_handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") + logger.debug(f"Send handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") add_stream_type = conn.direction == StreamDirection.initiator @@ -183,13 +206,13 @@ def up0_send_handshake(self, conn: JAMConnection): def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): # Note: For now we employ a very simple strategy, where we sync blocks from the first node that announced a finalized block greater than we have if self.state_requesting_blocks: - logger.debug(f"up0_received_handshake Skipping handshake block header check, already importing blocks") + logger.debug(f"Skipping handshake block header check, already importing blocks") return block = self.app.retrieve_block_by_hash(msg.header_hash) #TODO: missch een efficientere exists check toevoegen #TODO: ook nog iets doen met slot en leafs ook mee requesten? if not block: - logger.debug(f"up0_received_handshake Received newer block from handshake: {msg.header_hash} -> initiate CE128RequestBlocks") + logger.debug(f"Received newer block from handshake: {msg.header_hash} -> initiate CE128RequestBlocks") self.state_requesting_blocks = True curr_hash = self.app.retrieve_block_hash(self.app.state.timeslot.number) # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? @@ -206,7 +229,7 @@ def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement): # Note: For now we employ a very simple strategy, where we sync blocks from the first node that announced a finalized block greater than we have if self.state_requesting_blocks: - logger.debug(f"up0_received_announcement Skipping block header announcement check, already importing blocks") + logger.debug(f"Skipping block header announcement check, already importing blocks") return """ @@ -220,7 +243,7 @@ def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement block = self.app.retrieve_block_by_hash(msg.header.hash) #TODO: missch een efficientere exists check toevoegen #TODO: ook nog iets doen met bijhorende finalized header_hash & slot? if not block: - logger.debug(f"up0_received_announcement Received new block announcement from up0: {msg.header.hash}") + logger.debug(f"Received new block announcement from up0: {msg.header.hash}") self.state_requesting_blocks = True curr_hash = self.app.retrieve_block_hash(self.app.state.timeslot.number) # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? @@ -234,34 +257,10 @@ def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement ) - async def up0_broadcast_block(self, block: Block): - logger.debug(f'up0_broadcast_block to {len(self.connections)} connections') - - msg = MsgUP0Announcement( - header=block.header, - header_hash=block.header.hash, - timeslot=block.header.timeslot - ).to_jam_bytes().to_bytes() - - for client_id, client in self.connections.items(): - # Check if UP0 stream is established - if not client.is_connected(): - logger.debug(f"Skipping broadcast to {client.host}:{client.port} - connection is not fully established yet") - continue - - logger.debug(f"up0_broadcast_block send block header to client {client.host}:{client.port} with hash {block.header.hash}") - client.send( - client.stream_up.stream_id, - client.stream_up.create_message(msg), - end_stream=False - ) - - def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRequest): #TODO: missch een batch param meegeven, zodat we een grotere reeks kunnen binnenhalen maar batchen... - return stream = conn.open_jam_stream(StreamBlockRequest, direction=StreamDirection.initiator) - logger.debug(f"ce128_initiate_block_request on stream id: {stream.stream_id} direction: {req.direction}, max_block: {req.max_blocks} header hash: {req.header_hash}") + logger.debug(f"Initiate block request on stream id: {stream.stream_id} direction: {req.direction}, max_block: {req.max_blocks} header hash: {req.header_hash}") conn.send( stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), @@ -271,7 +270,7 @@ def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRe def ce128_received_block_request(self, stream: StreamBlockRequest, req: MsgCE128BlockRequestResponse): blocks = req.blocks - logger.debug(f"ce128_received_block_request request (parsing {len(blocks)} blocks)") + logger.debug(f"Parsed {len(blocks)} blocks") asyncio.create_task( self.app.import_queue_add_blocks( blocks, @@ -283,7 +282,6 @@ def ce128_received_block_request(self, stream: StreamBlockRequest, req: MsgCE128 def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE128BlockRequest): - return block:Block = None blocks:List[Block] = [] first_block_hash = bytes(32) @@ -318,9 +316,8 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 blocks.append(block) - logger.debug(f"ce128_send_block_request {len(blocks)} blocks (direction={block_req.direction} max blocks={block_req.max_blocks})") - if blocks: + logger.debug(f"Send {len(blocks)} blocks (direction={block_req.direction} max blocks={block_req.max_blocks})") stream.conn.send( stream.stream_id, stream.create_message(MsgCE128BlockRequestResponse(blocks=blocks).to_jam_bytes().to_bytes()), @@ -328,12 +325,12 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 ) else: #TODO: ook op max_blocks checken -> bijhouden op de stream? - logger.debug(f"ce128_send_block_request finished") + logger.debug(f"No blocks to send requested: {block_req.header_hash.hex()} current: {last_block_hash.hex()}") stream.conn.send(stream.stream_id, b'', end_stream=True) def ce128_abort_block_request(self): - logger.debug(f"ce128_abort_block_request Finished, start parsing import queue {len(self.app._import_queue)}") + logger.debug(f"Finished, start parsing import queue {len(self.app._import_queue)}") # Note: could happen during a block_request "session" #TODO: or do we want to clear the queue? asyncio.create_task( @@ -350,62 +347,25 @@ async def ce128_processed_block_request(self, *args, **kwargs): def up0_failure(self, reset_code: int, direction:StreamDirection): - logger.error(f"up0_failure with code {reset_code} ({direction}") + logger.error(f"Failed with code {reset_code} ({direction}") # TODO: handle UP0 reset, e.g., reconnect def ce128_block_request_failure(self, reset_code: int): - logger.error(f"ce128_block_request_failure with code {reset_code}") + logger.error(f"Failed with code {reset_code}") self.state_requesting_blocks = False # Reset state on failure - async def ce131_initiate_distribute_own_ticket(self, data): - return - #TODO: wrap in a dataclass and pass that in the signal - epoch_index: int = data[0] - attempt: int = data[1] - proof: bytes = data[2] - - msg = MsgCE131SafroleTicketDistribution( - epoch_index=epoch_index, - ticket=MsgCE131SafroleTicket( - attempt=attempt, - proof=proof - ) - ) - - distributed_count = 0 - for conn in self.connections.values(): - if conn.is_connected(): - try: - stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep1, direction=StreamDirection.initiator) - logger.debug(f"ce131_initiate_distribute_own_ticket on stream id: {stream.stream_id}") - conn.send( - stream.stream_id, - stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), - end_stream=True - ) - - distributed_count += 1 - except Exception as e: - logger.error(f"Failed to distribute ticket to peer: {e}") - - logger.info(f"ce131_initiate_distribute_own_ticket Distributed own ticket to {distributed_count} peers") - return distributed_count - - def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, msg: MsgCE131SafroleTicketDistribution): - logger.debug(f"ce131_received_ticket for epoch {msg.epoch_index}") + logger.debug(f"Received ticket for epoch {msg.epoch_index}") - # Validate epoch current_epoch = self.app.state.timeslot.number // EPOCH_TIMESLOTS if msg.epoch_index != current_epoch and msg.epoch_index != current_epoch + 1: - logger.warning(f"ce131_received_ticket Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") + logger.warning(f"Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") stream.reset(1) return - # Verify ticket - #TODO: create helper function from this + #TODO: create helper function from this (in app? verify_ticket) try: # Get ring public keys from current validators ring_public_keys = [v.bandersnatch for v in self.app.state.safrole.validators] @@ -423,7 +383,7 @@ def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, ms # Check if we already have this ticket if ticket_body in self.app.state.safrole.ticket_accumulator: - logger.debug(f"ce131_received_ticket Ticket already in accumulator") + logger.debug(f"Ticket already in accumulator") stream.reset(2) return @@ -435,7 +395,7 @@ def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, ms entropy ) - logger.info(f"ce131_received_ticket added for epoch {msg.epoch_index}") + logger.info(f"Ticket added for epoch {msg.epoch_index}") #TODO: check if we should forward to (proxy) nodes via CE132 # Forward to other connections via CE132 @@ -451,11 +411,11 @@ def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, ms # ) # ) # ) - # logger.debug(f"ce131_received_ticket Forwarded ticket to {len(self.connections) - 1} peers via CE132") + # logger.debug(f"Forwarded ticket to {len(self.connections) - 1} peers via CE132") except ValueError as e: #TODO: register this, detect bad nodes - logger.error(f"ce131_received_ticket Invalid ticket: {e}") + logger.error(f"Invalid ticket: {e}") stream.reset(3) return @@ -463,18 +423,18 @@ def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, ms def ce131_ticket_distribution_success(self, reset_code: int): - logger.debug(f"ce131_ticket_distribution_success with code {reset_code}") + logger.debug(f"Finished with code {reset_code}") # TODO: handle success, perhaps update state def ce131_ticket_distribution_failure(self, reset_code: int): - logger.error(f"ce131_ticket_distribution_failure with code {reset_code}") + logger.error(f"Failed with code {reset_code}") # TODO: handle failure, e.g., retry or log def ce132_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE132SafroleTicketDistribution): stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep2, direction=StreamDirection.initiator) - logger.debug(f"ce132_initiate_ticket_distribution on stream id: {stream.stream_id}") + logger.debug(f"Distribute ticket on stream id: {stream.stream_id} to {conn.host}:{conn.port}") conn.send( stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), @@ -483,12 +443,12 @@ def ce132_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE132S def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, msg: MsgCE132SafroleTicketDistribution): - logger.debug(f"ce132_received_ticket for epoch {msg.epoch_index}") + logger.debug(f"Received ticket for epoch {msg.epoch_index}") # Verify epoch index current_epoch = self.app.state.timeslot.number // EPOCH_TIMESLOTS if msg.epoch_index != current_epoch and msg.epoch_index != current_epoch + 1: - logger.warning(f"ce132_received_ticket Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") + logger.warning(f"Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") stream.reset(1) return @@ -510,7 +470,7 @@ def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, ms # Check if we already have this ticket if ticket_body in self.app.state.safrole.ticket_accumulator: - logger.debug(f"ce132_received_ticket Ticket already in accumulator (via CE132), ignoring") + logger.debug(f"Ticket already in accumulator (via CE132), ignoring") # Don't reset, just close normally as this is expected in CE132 stream.conn.send(stream.stream_id, b'', end_stream=True) return @@ -522,10 +482,10 @@ def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, ms entropy ) - logger.info(f"ce132_received_ticket Valid ticket received via CE132 (forwarded) for epoch {msg.epoch_index}") + logger.info(f"Valid ticket received via CE132 (forwarded) for epoch {msg.epoch_index}") except ValueError as e: - logger.error(f"ce132_received_ticket Invalid ticket: {e}") + logger.error(f"Invalid ticket: {e}") stream.reset(3) return @@ -533,146 +493,76 @@ def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, ms def ce132_ticket_distribution_success(self, reset_code: int): - logger.debug(f"ce132_ticket_distribution_success with code {reset_code}") + logger.debug(f"Success with code {reset_code}") # TODO: handle success, perhaps update state def ce132_ticket_distribution_failure(self, reset_code: int): - logger.error(f"ce132_ticket_distribution_failure with code {reset_code}") + logger.error(f"Failed with code {reset_code}") # TODO: handle failure - def ce134_initiate_sharing(self, conn: JAMConnection, sharing: MsgCE134WorkPackageSharing, bundle: MsgCE134WorkPackageBundle): - stream = conn.open_jam_stream(StreamWorkPackageSharing, direction=StreamDirection.initiator) - logger.debug(f"ce134_initiate_sharing on stream id: {stream.stream_id}") - conn.send(stream.stream_id, stream.create_message(sharing.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) - conn.send(stream.stream_id, stream.create_message(bundle.to_jam_bytes().to_bytes()), end_stream=True) - - - def ce134_received_sharing(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageSharing): - logger.debug(f"ce134_received_sharing for core {msg.core_index}") - # TODO: process mappings - - - def ce134_received_bundle(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageBundle): - logger.debug(f"CE134 received bundle") - # TODO: verify, refine - if not self.verify_bundle(msg): - raise ValueError("Invalid bundle") - response = MsgCE134RefineResponse(report_hash=bytes(32), signature=bytes(64)) - stream.conn.send(stream.stream_id, stream.create_message(response.to_jam_bytes().to_bytes()), end_stream=True) - - - def ce134_received_refine_response(self, stream: StreamWorkPackageSharing, msg: MsgCE134RefineResponse): - logger.debug(f"CE134 received refine response") - # TODO: process response - # stream.initiator_reset(0) - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce134_sharing_success(self, reset_code: int): - logger.debug(f"CE134 sharing successful with code {reset_code}") - - - def ce134_sharing_failure(self, reset_code: int): - logger.error(f"CE134 sharing failed with code {reset_code}") + def ce133_initiate_workpackage_submission(self, conn: JAMConnection, wp: MsgCE133WorkPackageSubmission, extrinsic: MsgCE133Extrinsic): + stream = conn.open_jam_stream(StreamWorkPackageSubmission, direction=StreamDirection.initiator) + logger.debug(f"Initiating workpackage submission on stream id: {stream.stream_id}") + conn.send(stream.stream_id, stream.create_message(wp.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) + conn.send(stream.stream_id, stream.create_message(extrinsic.to_jam_bytes().to_bytes()), end_stream=True) - def ce141_initiate_distribution(self, conn: JAMConnection, msg: MsgCE141Assurance): - stream = conn.open_jam_stream(StreamAssuranceDistribution, direction=StreamDirection.initiator) - logger.debug(f"CE141 initiating distribution on stream id: {stream.stream_id}") - conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + def ce133_received_workpackage_submission(self, stream: StreamWorkPackageSubmission, msg: MsgCE133WorkPackageSubmission): + logger.debug(f"Received workpackage submission for core {msg.core_index}") + # TODO: process work package - def ce141_received_assurance(self, stream: StreamAssuranceDistribution, msg: MsgCE141Assurance): - logger.debug(f"CE141 received assurance") - # TODO: process assurance + def ce133_received_extrinsic_submission(self, stream: StreamWorkPackageSubmission, msg: MsgCE133Extrinsic): + logger.debug(f"CE133 received extrinsic data of length {len(msg.bytes_)}") + # TODO: process extrinsic, close stream if successful # stream.acceptor_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce141_distribution_success(self, reset_code: int): - logger.debug(f"CE141 distribution successful with code {reset_code}") - - - def ce141_distribution_failure(self, reset_code: int): - logger.error(f"CE141 distribution failed with code {reset_code}") - - - def ce142_initiate_announcement(self, conn: JAMConnection, msg: MsgCE142PreimageAnnouncement): - stream = conn.open_jam_stream(StreamPreimageAnnouncement, direction=StreamDirection.initiator) - logger.debug(f"CE142 initiating announcement on stream id: {stream.stream_id}") - conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) - - - def ce142_received_announcement(self, stream: StreamPreimageAnnouncement, msg: MsgCE142PreimageAnnouncement): - logger.debug(f"CE142 received announcement for hash {msg.hash.hex()}") - # TODO: check if needed, request via CE143 if so - # stream.acceptor_reset(0) - stream.conn.send(stream.stream_id, b'', end_stream=True) + def ce133_submission_success(self, reset_code: int): + logger.debug(f"CE133 submission successful with code {reset_code}") - def ce142_announcement_success(self, reset_code: int): - logger.debug(f"CE142 announcement successful with code {reset_code}") + def ce133_submission_failure(self, reset_code: int): + logger.error(f"CE133 submission failed with code {reset_code}") - def ce142_announcement_failure(self, reset_code: int): - logger.error(f"CE142 announcement failed with code {reset_code}") + def ce134_initiate_workpackage_sharing(self, conn: JAMConnection, sharing: MsgCE134WorkPackageSharing, bundle: MsgCE134WorkPackageBundle): + stream = conn.open_jam_stream(StreamWorkPackageSharing, direction=StreamDirection.initiator) + logger.debug(f"Initiate sharing workpage on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + conn.send(stream.stream_id, stream.create_message(sharing.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) + conn.send(stream.stream_id, stream.create_message(bundle.to_jam_bytes().to_bytes()), end_stream=True) - def ce143_initiate_request(self, conn: JAMConnection, msg: MsgCE143HashRequest): - stream = conn.open_jam_stream(StreamPreimageRequest, direction=StreamDirection.initiator) - logger.debug(f"CE143 initiating request on stream id: {stream.stream_id}") - conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + def ce134_received_workpackage_sharing(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageSharing): + logger.debug(f"Received workpage for core {msg.core_index}") + # TODO: process!!! - def ce143_received_request(self, stream: StreamPreimageRequest, msg: MsgCE143HashRequest): - logger.debug(f"CE143 received request for hash {msg.hash.hex()}") - # TODO: lookup preimage, send if available - preimage = MsgCE143Preimage(bytes_=b'') # placeholder - stream.conn.send(stream.stream_id, stream.create_message(preimage.to_jam_bytes().to_bytes()), end_stream=True) + def ce134_received_bundle(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageBundle): + logger.debug(f"Received bundle") + # TODO: verify, refine + if not self.verify_bundle(msg): + raise ValueError("Invalid bundle") + response = MsgCE134RefineResponse(report_hash=bytes(32), signature=bytes(64)) + stream.conn.send(stream.stream_id, stream.create_message(response.to_jam_bytes().to_bytes()), end_stream=True) - def ce143_received_preimage(self, stream: StreamPreimageRequest, msg: MsgCE143Preimage): - logger.debug(f"CE143 received preimage of length {len(msg.bytes_)}") - # TODO: process preimage + def ce134_received_refine_response(self, stream: StreamWorkPackageSharing, msg: MsgCE134RefineResponse): + logger.debug(f"Received refine response") + # TODO: process.... # stream.initiator_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce143_request_success(self, reset_code: int): - logger.debug(f"CE143 request successful with code {reset_code}") - - - def ce143_request_failure(self, reset_code: int): - logger.error(f"CE143 request failed with code {reset_code}") - - - def ce133_initiate_submission(self, conn: JAMConnection, submission: MsgCE133WorkPackageSubmission, extrinsic: MsgCE133Extrinsic): - stream = conn.open_jam_stream(StreamWorkPackageSubmission, direction=StreamDirection.initiator) - logger.debug(f"CE133 initiating submission on stream id: {stream.stream_id}") - conn.send(stream.stream_id, stream.create_message(submission.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) - conn.send(stream.stream_id, stream.create_message(extrinsic.to_jam_bytes().to_bytes()), end_stream=True) - - - def ce133_received_submission(self, stream: StreamWorkPackageSubmission, msg: MsgCE133WorkPackageSubmission): - logger.debug(f"CE133 received submission for core {msg.core_index}") - # TODO: process work package - - - def ce133_received_extrinsic(self, stream: StreamWorkPackageSubmission, msg: MsgCE133Extrinsic): - logger.debug(f"CE133 received extrinsic data of length {len(msg.bytes_)}") - # TODO: process extrinsic, close stream if successful - # stream.acceptor_reset(0) - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce133_submission_success(self, reset_code: int): - logger.debug(f"CE133 submission successful with code {reset_code}") + def ce134_sharing_success(self, reset_code: int): + logger.debug(f"Success with code {reset_code}") - def ce133_submission_failure(self, reset_code: int): - logger.error(f"CE133 submission failed with code {reset_code}") + def ce134_sharing_failure(self, reset_code: int): + logger.error(f"Failed with code {reset_code}") def ce135_initiate_distribution(self, conn: JAMConnection, msg: MsgCE135GuaranteedWorkReport): @@ -832,6 +722,76 @@ def ce140_request_failure(self, code: int): logger.error(f"CE140 failure {code}") + def ce141_initiate_distribution(self, conn: JAMConnection, msg: MsgCE141Assurance): + stream = conn.open_jam_stream(StreamAssuranceDistribution, direction=StreamDirection.initiator) + logger.debug(f"Initiating assurance distribution on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + + def ce141_received_assurance(self, stream: StreamAssuranceDistribution, msg: MsgCE141Assurance): + logger.debug(f"Received assurance") + # TODO: process!... + # stream.acceptor_reset(0) + stream.conn.send(stream.stream_id, b'', end_stream=True) + + + def ce141_distribution_success(self, reset_code: int): + logger.debug(f"Success with code {reset_code}") + + + def ce141_distribution_failure(self, reset_code: int): + logger.error(f"Failed with code {reset_code}") + + + def ce142_initiate_announcement(self, conn: JAMConnection, msg: MsgCE142PreimageAnnouncement): + stream = conn.open_jam_stream(StreamPreimageAnnouncement, direction=StreamDirection.initiator) + logger.debug(f"Initiating preimage announcement on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + + def ce142_received_announcement(self, stream: StreamPreimageAnnouncement, msg: MsgCE142PreimageAnnouncement): + logger.debug(f"Received preimage announcement for hash {msg.hash.hex()}") + # TODO: check if needed, request via CE143 if so + # stream.acceptor_reset(0) + stream.conn.send(stream.stream_id, b'', end_stream=True) + + + def ce142_announcement_success(self, reset_code: int): + logger.debug(f"Success with code {reset_code}") + + + def ce142_announcement_failure(self, reset_code: int): + logger.error(f"Failed with code {reset_code}") + + + def ce143_initiate_request(self, conn: JAMConnection, msg: MsgCE143HashRequest): + stream = conn.open_jam_stream(StreamPreimageRequest, direction=StreamDirection.initiator) + logger.debug(f"CE143 initiating request on stream id: {stream.stream_id}") + conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) + + + def ce143_received_request(self, stream: StreamPreimageRequest, msg: MsgCE143HashRequest): + logger.debug(f"CE143 received request for hash {msg.hash.hex()}") + # TODO: lookup preimage, send if available + preimage = MsgCE143Preimage(bytes_=b'') # placeholder + stream.conn.send(stream.stream_id, stream.create_message(preimage.to_jam_bytes().to_bytes()), end_stream=True) + + + def ce143_received_preimage(self, stream: StreamPreimageRequest, msg: MsgCE143Preimage): + logger.debug(f"CE143 received preimage of length {len(msg.bytes_)}") + # TODO: process preimage + # stream.initiator_reset(0) + stream.conn.send(stream.stream_id, b'', end_stream=True) + + + def ce143_request_success(self, reset_code: int): + logger.debug(f"CE143 request successful with code {reset_code}") + + + def ce143_request_failure(self, reset_code: int): + logger.error(f"CE143 request failed with code {reset_code}") + + def ce144_initiate_announcement(self, conn: JAMConnection, ann: MsgCE144Announcement, evidence: MsgCE144Evidence): stream = conn.open_jam_stream(StreamAuditAnnouncement, direction=StreamDirection.initiator) conn.send(stream.stream_id, stream.create_message(ann.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) @@ -874,10 +834,70 @@ def ce145_publication_failure(self, code: int): logger.error(f"CE145 failure {code}") + # Note: pubsub_ prefixed methods are driven by application level events + async def pubsub_up0_broadcast_block(self, block: Block): + logger.debug(f'Broadcast {block.header.hash.hex()} to {len(self.connections)} connections') + + msg = MsgUP0Announcement( + header=block.header, + header_hash=block.header.hash, + timeslot=block.header.timeslot + ).to_jam_bytes().to_bytes() + + for client_id, client in self.connections.items(): + # Check if UP0 stream is established + if not client.is_connected(): + logger.debug( + f"Skipping broadcast to {client.host}:{client.port} - connection is not fully established yet") + continue + + logger.debug(f"Send block header to client {client.host}:{client.port} with hash {block.header.hash.hex()}") + client.send( + client.stream_up.stream_id, + client.stream_up.create_message(msg), + end_stream=False + ) + + + async def pubsub_ce131_initiate_distribute_own_ticket(self, data): + #TODO: wrap in a dataclass and pass that in the signal + epoch_index: int = data[0] + attempt: int = data[1] + proof: bytes = data[2] + + msg = MsgCE131SafroleTicketDistribution( + epoch_index=epoch_index, + ticket=MsgCE131SafroleTicket( + attempt=attempt, + proof=proof + ) + ) + + distributed_count = 0 + for conn in self.connections.values(): + if conn.is_connected(): + try: + stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep1, direction=StreamDirection.initiator) + logger.debug(f"Send ticker announcement on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + conn.send( + stream.stream_id, + stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True + ) + + distributed_count += 1 + except Exception as e: + logger.error(f"Failed to distribute ticket to peer {conn.host}:{conn.port}: {e}") + + logger.info(f"Distributed ticket to {distributed_count} peers") + return distributed_count + + # Temp placeholders def verify_ticket_proof(self, msg): return True + def verify_bundle(self, msg): return True diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py index 4409dae7..67891b3b 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -141,7 +141,8 @@ def handle_error(self, exc: Exception): def handle_fin(self): - self.conn.close_jam_stream(self, clean_close=True) + #self.conn.close_jam_stream(self, clean_close=True) + pass def reset(self, reset_code: int): @@ -149,12 +150,11 @@ def reset(self, reset_code: int): self.initiator_reset(reset_code) else: self.acceptor_reset(reset_code) - self.conn.close_jam_stream(self, reason=reset_code, clean_close=False) def initiator_reset(self, reset_code: int): - self.conn.close_jam_stream(self) + self.conn.close_jam_stream(self, reset_code, False) def acceptor_reset(self, reset_code: int): - self.conn.close_jam_stream(self) + self.conn.close_jam_stream(self, reset_code, False) diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py index 37af5acc..741937e0 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -35,8 +35,11 @@ def initiator_message(self, data: bytes): msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) self.protocol.up0_received_handshake(self.conn, msg) + + # Note: exception when received as an acceptor stream if self.direction == StreamDirection.acceptor: self.protocol.up0_send_handshake(self.conn) + except Exception as e: logger.error(f"Error processing handshake in initiator: {e}", exc_info=True) raise diff --git a/pyjamaz/transport/jamnp_s/streams/stream_133.py b/pyjamaz/transport/jamnp_s/streams/stream_133.py index cfc549bf..e82ea97b 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_133.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_133.py @@ -37,12 +37,12 @@ def acceptor_message(self, data: bytes): if not self.received_wp: logger.debug(f"CE133 acceptor received work package") msg = MsgCE133WorkPackageSubmission.from_jam_bytes(JamBytes(data)) - self.protocol.ce133_received_submission(self, msg) + self.protocol.ce133_received_workpackage_submission(self, msg) self.received_wp = True else: logger.debug(f"CE133 acceptor received extrinsic data") msg = MsgCE133Extrinsic.from_jam_bytes(JamBytes(data)) - self.protocol.ce133_received_extrinsic(self, msg) + self.protocol.ce133_received_extrinsic_submission(self, msg) def handle_fin(self): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_134.py b/pyjamaz/transport/jamnp_s/streams/stream_134.py index 95899e97..f96359e4 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_134.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_134.py @@ -43,7 +43,7 @@ def acceptor_message(self, data: bytes): if not self.received_mappings: logger.debug(f"CE134 acceptor received mappings") msg = MsgCE134WorkPackageSharing.from_jam_bytes(JamBytes(data)) - self.protocol.ce134_received_sharing(self, msg) + self.protocol.ce134_received_workpackage_sharing(self, msg) self.received_mappings = True else: logger.debug(f"CE134 acceptor received bundle") diff --git a/pyjamaz/transport/protocol_fs.py b/pyjamaz/transport/protocol_fs.py deleted file mode 100644 index f5d68829..00000000 --- a/pyjamaz/transport/protocol_fs.py +++ /dev/null @@ -1,81 +0,0 @@ -# import json -# import logging -# import os -# import anyio -# from jamcodec.base import JamBytes -# -# from pyjamaz.constants import MESSAGE_TYPES -# from pyjamaz.models.block import Block -# from pyjamaz.transport.pubsub import PubSubSignal -# from pyjamaz.transport.types import ProtocolType -# -# -# logger = logging.getLogger("pyjamaz.transport") -# -# -# class FSProtocol(ProtocolType): -# -# def __init__(self, block_dir, app): -# self.block_dir = block_dir -# self.lock = anyio.Lock() -# self.pubsub = app.pubsub -# self.app = app -# -# async def listen(self): -# -# seen_files = set() -# -# while True: -# # Run the directory check in a separate thread (non-blocking) -# new_files = await anyio.to_thread.run_sync( -# lambda: {f for f in os.listdir(self.block_dir) if f.startswith('block-')} - seen_files -# ) -# -# if new_files: -# for filename in sorted(new_files): -# filepath = os.path.join(self.block_dir, filename) -# -# try: -# async with self.lock: -# -# with open(filepath, 'r') as file: -# if filename.startswith("block-req-"): -# await self.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.REQUESTED_BLOCKS, data=json.load(file))) -# -# else: -# await self.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.RECEIVED_BLOCK,data=json.load(file))) -# -# except Exception as e: -# logging.error(f"Failed to process {filepath}: {e}") -# -# # Update the seen_files set to include the newly processed files -# seen_files.update(new_files) -# -# await anyio.sleep(.5) -# -# -# async def request_blocks(self, direction, max_blocks, block_bytes): -# block = Block.from_jam_bytes(JamBytes(block_bytes)) -# blocks = [] -# # TODO: take direction and max_blocks into account -# # TODO: we decode and serialize blocks unnecessary here, improve! -# while block.header.parent != bytes(32): -# block = self.app.retrieve_block_by_hash(block.header.parent) -# if not block: -# break -# blocks.append(block) -# -# serialized_blocks = [b.to_json() for b in blocks] -# -# filepath = os.path.join(self.block_dir, f'block-req-{block.header.timeslot:06}.json') -# with open(filepath, 'w') as file: -# json.dump(serialized_blocks, file, indent=2) -# -# -# async def broadcast_block(self, block): -# # write block to dir -# async with self.lock: -# filepath = os.path.join(self.block_dir, f'block-{block.header.timeslot:06}.json') -# with open(filepath, 'w') as file: -# logger.debug(f"FSProtocol dump block to disk: {filepath}") -# json.dump(block.to_json(), file, indent=2) From 259923835f06ef52e180976dc22a4533be494093 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Fri, 18 Jul 2025 15:13:09 +0200 Subject: [PATCH 37/51] JAMNP WIP --- bootstrap_builder.py | 6 ++--- pyjamaz/transport/jamnp_s/protocol.py | 34 +++++++++++++++++++-------- pyjamaz/transport/rpc/ws_client.py | 6 +---- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/bootstrap_builder.py b/bootstrap_builder.py index 42b914bf..da7c4951 100644 --- a/bootstrap_builder.py +++ b/bootstrap_builder.py @@ -21,8 +21,8 @@ async def get_service_registry(client) -> ServiceRegistry: async def create_empty_workpackage(client: WebsocketClient) -> WorkPackage: best_block = await client.bestBlock() - block_hash = best_block[0] - block_timeslot = best_block[1] + block_hash = best_block["header_hash"] + block_timeslot = best_block["slot"] state_root = await client.stateRoot(block_hash) beefy_root = await client.beefyRoot(block_hash) @@ -72,7 +72,7 @@ async def create_bootservice_workpackage(client: WebsocketClient, instruction: I async def main(): - async with WebsocketClient("ws://127.0.0.1:19800") as client: + async with WebsocketClient("ws://127.0.0.1:19801") as client: # Init vars bootstrap_service_id = 0 registration = "test123" diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 7af8c75d..29fe1128 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -50,6 +50,7 @@ from pyjamaz.transport.jamnp_s.connection import JAMConnection +from pyjamaz.pyjamaz.models.common import WorkPackage logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -537,23 +538,36 @@ def ce134_initiate_workpackage_sharing(self, conn: JAMConnection, sharing: MsgCE def ce134_received_workpackage_sharing(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageSharing): - logger.debug(f"Received workpage for core {msg.core_index}") - # TODO: process!!! + logger.debug(f"ce134_received_workpackage_sharing for core {msg.core_index}") + # Store sharing msg in stream state for later use with bundle + stream.sharing_msg = msg def ce134_received_bundle(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageBundle): - logger.debug(f"Received bundle") - # TODO: verify, refine - if not self.verify_bundle(msg): - raise ValueError("Invalid bundle") - response = MsgCE134RefineResponse(report_hash=bytes(32), signature=bytes(64)) + logger.debug(f"ce134_received_bundle") + + work_package = WorkPackage( + authorization=msg.authorization, + auth_code_host=msg.auth_code_host, + authorizer=msg.authorizer, + context=msg.context, + items=msg.items + ) + + work_report = self.app.process_work_package(work_package) + + report_hash = work_report.hash() + signature = self.app.config.keys.ed25519.sign(report_hash) + + response = MsgCE134RefineResponse(report_hash=report_hash, signature=signature) stream.conn.send(stream.stream_id, stream.create_message(response.to_jam_bytes().to_bytes()), end_stream=True) def ce134_received_refine_response(self, stream: StreamWorkPackageSharing, msg: MsgCE134RefineResponse): - logger.debug(f"Received refine response") - # TODO: process.... - # stream.initiator_reset(0) + logger.debug(f"ce134_received_refine_response") + work_report = None #TODO!!! msg... + # Guarantee the work report + self.app.guarantee_work_report(work_report, self.app.current_timeslot()) stream.conn.send(stream.stream_id, b'', end_stream=True) diff --git a/pyjamaz/transport/rpc/ws_client.py b/pyjamaz/transport/rpc/ws_client.py index 8b1e978d..12129d46 100644 --- a/pyjamaz/transport/rpc/ws_client.py +++ b/pyjamaz/transport/rpc/ws_client.py @@ -122,11 +122,7 @@ async def parameters(self) -> Dict: async def bestBlock(self) -> Optional[dict]: - res = await self._send_and_wait("bestBlock", None) - if not res: - return None - res[0] = bytes(res[0]) - return res + return await self._send_and_wait("bestBlock", None) async def listServices(self) -> List[int]: return await self._send_and_wait("listServices", None) From 4e5ab8d6afd2ade55c978e474e7723ecea83abf8 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Mon, 21 Jul 2025 12:19:20 +0200 Subject: [PATCH 38/51] - JAMNP WIP --- pyjamaz/cli.py | 3 ++- pyjamaz/graypaper_constants.py | 5 ++++- pyjamaz/transport/jamnp_s/connection.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 9de58b7e..8d957c11 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -186,7 +186,8 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, # Setup logging log_level = logging.DEBUG if verbose else logging.INFO # Note: Add packages that need a different logging level here - log_level = logging.ERROR + #log_level = logging.ERROR + log_level = logging.INFO log_package_overrides = { "pyjamaz.transport.jamnp_s": logging.DEBUG, "quic": logging.WARNING, diff --git a/pyjamaz/graypaper_constants.py b/pyjamaz/graypaper_constants.py index abb7adb9..421c2f3a 100644 --- a/pyjamaz/graypaper_constants.py +++ b/pyjamaz/graypaper_constants.py @@ -44,7 +44,10 @@ PVM_SEGMENT_SIZE = 2**16 #ZQ PVM_INIT_ZONE_SIZE = 2**16 #ZZ -COMMON_ERA = 1704110400 +#TODO +import datetime +dt = datetime.datetime(2025,1,1,12,0,0, tzinfo=datetime.timezone.utc) +COMMON_ERA = int(dt.timestamp()) from pyjamaz.settings import TEST_SUITE if TEST_SUITE == 'tiny': diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 9a40f7f5..b54e3fc1 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -26,7 +26,7 @@ def __init__(self, *args, **kwargs): self.stream_up = None self.streams = {} - self._keepalive_task = asyncio.create_task(self._keepalive()) + #self._keepalive_task = asyncio.create_task(self._keepalive()) def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_id: int=None): From d6ca65bbb14640462646ee511861b19038610b02 Mon Sep 17 00:00:00 2001 From: Arjan Zijderveld <5286904+arjanz@users.noreply.github.com> Date: Tue, 22 Jul 2025 13:17:53 +0200 Subject: [PATCH 39/51] * reverted RPC bestBlock --- bootstrap_builder.py | 4 ++-- pyjamaz/transport/rpc/ws_client.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bootstrap_builder.py b/bootstrap_builder.py index da7c4951..443fe183 100644 --- a/bootstrap_builder.py +++ b/bootstrap_builder.py @@ -21,8 +21,8 @@ async def get_service_registry(client) -> ServiceRegistry: async def create_empty_workpackage(client: WebsocketClient) -> WorkPackage: best_block = await client.bestBlock() - block_hash = best_block["header_hash"] - block_timeslot = best_block["slot"] + block_hash = best_block[0] + block_timeslot = best_block[1] state_root = await client.stateRoot(block_hash) beefy_root = await client.beefyRoot(block_hash) diff --git a/pyjamaz/transport/rpc/ws_client.py b/pyjamaz/transport/rpc/ws_client.py index 12129d46..8b1e978d 100644 --- a/pyjamaz/transport/rpc/ws_client.py +++ b/pyjamaz/transport/rpc/ws_client.py @@ -122,7 +122,11 @@ async def parameters(self) -> Dict: async def bestBlock(self) -> Optional[dict]: - return await self._send_and_wait("bestBlock", None) + res = await self._send_and_wait("bestBlock", None) + if not res: + return None + res[0] = bytes(res[0]) + return res async def listServices(self) -> List[int]: return await self._send_and_wait("listServices", None) From 748d95d2eea59edf52889753c4d1b2ba829545f9 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Tue, 22 Jul 2025 17:24:35 +0200 Subject: [PATCH 40/51] - JAMNP WIP --- pyjamaz/app.py | 30 ++-- pyjamaz/cli.py | 68 ++++---- pyjamaz/extrinsic.py | 72 +++++--- pyjamaz/transport/jamnp_s/connection.py | 6 +- pyjamaz/transport/jamnp_s/message_types.py | 13 +- pyjamaz/transport/jamnp_s/protocol.py | 161 +++++++++--------- pyjamaz/transport/jamnp_s/stream_base.py | 22 ++- .../transport/jamnp_s/streams/stream_128.py | 3 +- .../transport/jamnp_s/streams/stream_131.py | 6 +- .../transport/jamnp_s/streams/stream_132.py | 6 +- .../transport/jamnp_s/streams/stream_133.py | 35 ++-- .../transport/jamnp_s/streams/stream_134.py | 3 +- .../transport/jamnp_s/streams/stream_135.py | 3 +- .../transport/jamnp_s/streams/stream_136.py | 6 +- .../transport/jamnp_s/streams/stream_137.py | 6 +- .../transport/jamnp_s/streams/stream_138.py | 6 +- .../transport/jamnp_s/streams/stream_139.py | 6 +- .../transport/jamnp_s/streams/stream_140.py | 6 +- .../transport/jamnp_s/streams/stream_141.py | 11 +- .../transport/jamnp_s/streams/stream_142.py | 3 +- .../transport/jamnp_s/streams/stream_143.py | 6 +- .../transport/jamnp_s/streams/stream_144.py | 6 +- .../transport/jamnp_s/streams/stream_145.py | 6 +- pyjamaz/transport/rpc/rpc.py | 10 +- 24 files changed, 299 insertions(+), 201 deletions(-) diff --git a/pyjamaz/app.py b/pyjamaz/app.py index cb339ee6..f6fc3e70 100644 --- a/pyjamaz/app.py +++ b/pyjamaz/app.py @@ -17,7 +17,7 @@ from pyjamaz.exceptions import PyjamazAppError, StateKeyNoResult, ProcessWorkpackageError from pyjamaz.extrinsic import BlockExtrinsicAccumulator, WorkpackageExtrinsicAccumulator from pyjamaz.graypaper_constants import MAXIMUM_AUTHORIZATION_QUEUE_ITEMS, CORE_COUNT, EPOCH_TIMESLOTS, \ - SLOT_PERIOD, MAXIMUM_AGE_LOOKUP_ANCHOR + SLOT_PERIOD, MAXIMUM_AGE_LOOKUP_ANCHOR, TICKET_SUBMISSION_END_SLOT from pyjamaz.hashing import blake2b_256_hash from pyjamaz.merkle import PatriciaMerkleTrie from pyjamaz.models.app import StateDump, Trace @@ -233,22 +233,6 @@ async def update_state_trie(self): state_trie = PatriciaMerkleTrie(list(self.state_db)) self.state_trie_root = state_trie.root() - def is_epoch_change(self, slotnumber: int = None) -> bool: - """ - GP-0.5.0-general: `e!=e' ? T, F` | Helper function that determines if the epoch has changed. - - Returns - ------- - bool - `True` when epoch has changed, `False` otherwise. - - TODO duplicate code, move to dedicated package? - """ - if slotnumber is None: - slotnumber = self.current_timeslot() - - return self.state.timeslot.number // EPOCH_TIMESLOTS != slotnumber // EPOCH_TIMESLOTS - async def state_transition(self, block: 'Block', transaction: Transaction, produce=False) -> 'STFOutput': """ GP-0.6.4-eq:4.1 (Υ, σ') | Block Level State Transition Function for the JAM state. @@ -615,6 +599,8 @@ async def process_block(self, block: Block): await self.store_block(block) + await self.block_extrinsic.process_block(block) + await self.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.BEST_BLOCK, data=block)) await self.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.FINALIZED_BLOCK, data=block)) # TODO: placeholder for now, move when implemented await self.pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.STATISTICS, data=list(self.state.statistics.to_jam_bytes().to_bytes()))) @@ -869,12 +855,18 @@ async def produce_block(self, timeslot: int, safrole_state: SafroleState, entrop epoch_index=epoch_index, pubsub=self.pubsub ) + ticketz = [] + if timeslot % EPOCH_TIMESLOTS < TICKET_SUBMISSION_END_SLOT: + ticketz = await self.block_extrinsic.collect_tickets() + + guaranteez = await self.block_extrinsic.collect_guarantees() + extrinsic = Extrinsic( - tickets=self.block_extrinsic.collect_tickets(), + tickets=ticketz, disputes=ExtrinsicDisputes(verdicts=[], culprits=[], faults=[]), preimages=self.block_extrinsic.collect_preimages(self.state.services), assurances=self.block_extrinsic.collect_assurances(), - guarantees=self.block_extrinsic.collect_guarantees(), + guarantees=guaranteez, ) header = Header( diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 8d957c11..1cd061c6 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -188,8 +188,9 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, # Note: Add packages that need a different logging level here #log_level = logging.ERROR log_level = logging.INFO + log_level = logging.DEBUG log_package_overrides = { - "pyjamaz.transport.jamnp_s": logging.DEBUG, + "pyjamaz.transport.jamnp_s": logging.INFO, "quic": logging.WARNING, } setup_logging(log_level, log_package_overrides) @@ -315,6 +316,8 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, async def timeslot_ticker(app: PyjamazApp): + prev_timeslot = app.state.timeslot.number + while True: timeslot = app.current_timeslot() # TODO centralize @@ -325,43 +328,48 @@ async def timeslot_ticker(app: PyjamazApp): logging.debug(f"⏳️ Timeslot ticker: {timeslot}") - if app.state.timeslot.number >= timeslot: - logging.debug('⚠️ Timeslot did not advance; yield for 0.1 seconds') - await anyio.sleep(0.1) + if timeslot <= prev_timeslot: + logging.debug(f'⚠️ Timeslot did not advance; yield for 0.1 seconds {app.state.timeslot.number} >= {timeslot}') + await anyio.sleep(0.01) continue - if app.is_epoch_change(timeslot): + if prev_timeslot // EPOCH_TIMESLOTS != timeslot // EPOCH_TIMESLOTS: logging.info("🗓️ Process Epoch change") - # TODO !! temporary to determine if first block in new epoch should be produced. Cannot be determined without - # triggering state changes in STFs caused be epoch change. + if timeslot != app.state.timeslot.number: - header = Header.default() - header.timeslot = timeslot + # TODO !! temporary to determine if first block in new epoch should be produced. Cannot be determined without + # triggering state changes in STFs caused be epoch change. - entropy_output = app.components.entropy.state_transition( - header=header, - pre_state_timeslot=app.state.timeslot, - pre_state_entropy=app.state.entropy - ) + header = Header.default() + header.timeslot = timeslot - safrole_output = app.components.safrole.state_transition( - header=header, - pre_state_timeslot=app.state.timeslot, - pre_state_safrole=app.state.safrole, - pre_state_validator_queue=app.state.validator_queue, - post_state_entropy=entropy_output.post_state, - post_state_disputes=app.state.disputes, - post_state_validator_pool=app.state.validator_pool, - extrinsic_tickets=[] - ) + entropy_output = app.components.entropy.state_transition( + header=header, + pre_state_timeslot=app.state.timeslot, + pre_state_entropy=app.state.entropy + ) + + safrole_output = app.components.safrole.state_transition( + header=header, + pre_state_timeslot=app.state.timeslot, + pre_state_safrole=app.state.safrole, + pre_state_validator_queue=app.state.validator_queue, + post_state_entropy=entropy_output.post_state, + post_state_disputes=app.state.disputes, + post_state_validator_pool=app.state.validator_pool, + extrinsic_tickets=[] + ) + + safrole_state = safrole_output.post_state + entropy_state = entropy_output.post_state + else: + safrole_state = app.state.safrole + entropy_state = app.state.entropy # Process tickets - app.block_extrinsic.process_epoch_change() + await app.block_extrinsic.process_epoch_change() logging.debug(f"Current tickets {[i.hex() for i in app.block_extrinsic.own_tickets_current]}") - - safrole_state = safrole_output.post_state - entropy_state = entropy_output.post_state else: safrole_state = app.state.safrole entropy_state = app.state.entropy @@ -382,7 +390,7 @@ async def timeslot_ticker(app: PyjamazApp): # Rollback state from DB app.state = app.retrieve_jam_state() # TODO Make transactional - app.block_extrinsic.clear_tickets() + #await app.block_extrinsic.clear_tickets() else: logging.info(f'💤 Waiting for block #{timeslot} | epoch #{epoch} | phase #{phase}') @@ -395,7 +403,7 @@ async def timeslot_ticker(app: PyjamazApp): if work_report: logging.info(f'👨‍💻 Refine complete | slot={timeslot} | core={app.get_core_assigment()} | work_report={format_hash(work_report.package_spec.hash)}') - + prev_timeslot = timeslot await anyio.sleep(app.get_next_slot_timestamp() - time.time() + 0.01) #TODO: create constant to give meaning to this number diff --git a/pyjamaz/extrinsic.py b/pyjamaz/extrinsic.py index 7973c231..3f2d9fca 100644 --- a/pyjamaz/extrinsic.py +++ b/pyjamaz/extrinsic.py @@ -1,3 +1,4 @@ +import asyncio import logging from typing import Dict, List, Optional @@ -7,7 +8,7 @@ from pyjamaz.graypaper_constants import TICKET_ENTRIES, MAXIMUM_EXTRINSIC_TICKETS, TICKET_SUBMISSION_END_SLOT, \ EPOCH_TIMESLOTS from pyjamaz.hashing import blake2b_256_hash -from pyjamaz.models.block import TicketEnvelope, Guarantee, Assurance, Preimage +from pyjamaz.models.block import TicketEnvelope, Guarantee, Assurance, Preimage, Block from pyjamaz.models.common import TicketBody, WorkPackage from pyjamaz.models.state import ServicesState from pyjamaz.models.stf_output import SafroleErrorCode @@ -27,6 +28,9 @@ def __init__(self, ring_data: bytes): self.preimage_queue: List[Preimage] = [] self.ring_data = ring_data + self._ticket_queue_lock = asyncio.Lock() + + def create_ticket_body(self, ticket_data: TicketEnvelope, ring_public_keys: List[bytes], entropy: bytes) -> TicketBody: if ticket_data.attempt >= TICKET_ENTRIES: raise ValueError(SafroleErrorCode.bad_ticket_attempt) @@ -45,9 +49,10 @@ def create_ticket_body(self, ticket_data: TicketEnvelope, ring_public_keys: List return TicketBody(id=ring_vrf_output, attempt=ticket_data.attempt) - def add_ticket(self, ticket_data: TicketEnvelope, ring_public_keys: List[bytes], entropy: bytes): + async def add_ticket(self, ticket_data: TicketEnvelope, ring_public_keys: List[bytes], entropy: bytes): ticket_body = self.create_ticket_body(ticket_data, ring_public_keys, entropy) - self.tickets_queue[ticket_body.id] = ticket_data + async with self._ticket_queue_lock: + self.tickets_queue[ticket_body.id] = ticket_data def can_add_own_ticket(self, timeslot: int) -> bool: return len(self.own_tickets_next) < TICKET_ENTRIES and timeslot % EPOCH_TIMESLOTS < TICKET_SUBMISSION_END_SLOT @@ -57,11 +62,11 @@ async def add_own_ticket( epoch_index: int = None, pubsub: PubSub = None ): - if len(self.tickets_queue) > TICKET_ENTRIES: - raise ValueError("Too many tickets") - attempt = len(self.own_tickets_next) + if attempt >= TICKET_ENTRIES: + raise ValueError("Too many tickets") + # GP-0.3.8-eq:75 vrf_input_data = vrf_input_ticket_seal(entropy, attempt) aux_data = b'' @@ -81,14 +86,15 @@ async def add_own_ticket( logging.info(f'🎫 Generated ticket: {format_hash(ticket_id)}') logging.debug(f'Generated ticket: id = {format_hash(ticket_id)} with entropy {format_hash(entropy)}') - self.tickets_queue[ticket_id] = ticket + async with self._ticket_queue_lock: + self.tickets_queue[ticket_id] = ticket self.own_tickets_next.append(ticket_id) # Notify new ticket is added if pubsub and epoch_index is not None: await pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.TICKET_ADD, data=[epoch_index, attempt, signature])) - def collect_tickets(self) -> List[TicketEnvelope]: + async def collect_tickets(self) -> List[TicketEnvelope]: """ Collect tickets to include in a block @@ -97,11 +103,12 @@ def collect_tickets(self) -> List[TicketEnvelope]: List[TicketEnvelope] """ - collected_tickets = sorted(self.tickets_queue.items())[:MAXIMUM_EXTRINSIC_TICKETS] + async with self._ticket_queue_lock: + collected_tickets = sorted(self.tickets_queue.items())[:MAXIMUM_EXTRINSIC_TICKETS] - # Remove from queue - for key in [key for key, _ in collected_tickets]: - self.tickets_queue.pop(key) + # Remove from queue + for key in [key for key, _ in collected_tickets]: + self.tickets_queue.pop(key) return [ticket for _, ticket in collected_tickets] @@ -111,24 +118,21 @@ def is_own_ticket(self, ticket_id: bytes) -> bool: def own_ticket_count(self) -> int: return len(self.own_tickets_next) - def clear_own_tickets(self): - for ticket_id in self.own_tickets_next: - del self.tickets_queue[ticket_id] - self.own_tickets_next = [] - - def clear_tickets(self): - self.tickets_queue = {} + async def clear_tickets(self): + async with self._ticket_queue_lock: + self.tickets_queue = {} self.own_tickets_next = [] - def process_epoch_change(self): + async def process_epoch_change(self): self.own_tickets_current = self.own_tickets_next self.own_tickets_next = [] - self.tickets_queue = {} + async with self._ticket_queue_lock: + self.tickets_queue = {} def add_guarantee(self, guarantee: Guarantee): self.guarentees_queue.append(guarantee) - def collect_guarantees(self) -> List[Guarantee]: + async def collect_guarantees(self) -> List[Guarantee]: guarentees = self.guarentees_queue self.guarentees_queue = [] return guarentees @@ -160,6 +164,30 @@ def collect_preimages(self, service_state: ServicesState) -> List[Preimage]: return preimages + async def process_block(self, block: Block): + """ + Inspects and cleans up the ExtrinsicQueue for data that is already present in the block + + Parameters + ---------- + block: A succesfully imported Block + + Returns + ------- + + """ + delete_queue = [] + for ticket_data in block.extrinsic.tickets: + # TODO: make a reverse lookup + for queued_ticket_id, queued_ticket_data in self.tickets_queue.items(): + if queued_ticket_data == ticket_data: + #del self.tickets_queue[queued_ticket_id] + delete_queue.append(queued_ticket_id) + + async with self._ticket_queue_lock: + for queued_ticket_id in delete_queue: + del self.tickets_queue[queued_ticket_id] + class WorkpackageExtrinsicAccumulator: diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index b54e3fc1..0bd5821c 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -80,7 +80,7 @@ def send(self, stream_id: int, data: bytes, end_stream=False): def connection_lost(self, exc): - logger.info(f"Connection {self.jam_connection_ulid} QUIC UDP transport closed") + logger.debug(f"Connection {self.jam_connection_ulid} QUIC UDP transport closed") self.protocol.disconnect(self) super().connection_lost(exc) @@ -148,7 +148,7 @@ def quic_event_received(self, event: QuicEvent) -> None: logger.debug(f"Connection {self.jam_connection_ulid} QUIC stream {stream_id} already closed") return - self.streams[stream_id].reset(reset_code) + self.streams[stream_id].received_reset(reset_code) elif isinstance(event, StreamDataReceived): @@ -181,5 +181,5 @@ def quic_event_received(self, event: QuicEvent) -> None: raise elif isinstance(event, ConnectionTerminated): - logger.info(f"Connection {self.jam_connection_ulid} QUIC connection terminated with code {event.error_code}") + logger.debug(f"Connection {self.jam_connection_ulid} QUIC connection terminated with code {event.error_code}") self.protocol.disconnect(self) diff --git a/pyjamaz/transport/jamnp_s/message_types.py b/pyjamaz/transport/jamnp_s/message_types.py index eabdbad1..f58aad5a 100644 --- a/pyjamaz/transport/jamnp_s/message_types.py +++ b/pyjamaz/transport/jamnp_s/message_types.py @@ -4,12 +4,11 @@ from jamcodec.base import JamCodecTypeDef, JamBytes, JamCodecType from jamcodec.mixins import Serializable -from jamcodec.types import U32, H256, U8, VarInt64, Vec, Option, Array, Map, H512, VecType, U16 +from jamcodec.types import U32, H256, U8, VarInt64, Vec, Option, Array, Map, H512, VecType, U16, Bytes from pyjamaz.graypaper_constants import VALIDATOR_COUNT from pyjamaz.models.block import Header, Guarantee, Block -from pyjamaz.models.common import WorkPackage - +from pyjamaz.models.common import WorkPackage, Authorizer, RefinementContext, WorkItem class ImplicitVec(Vec): @@ -111,12 +110,12 @@ class MsgCE132SafroleTicketDistribution(MsgCE131SafroleTicketDistribution): @dataclass class MsgCE133WorkPackageSubmission(Serializable): - core_index: int = field(metadata={'codec': U32}) + core_index: int = field(metadata={'codec': U16}) work_package: WorkPackage = field(metadata={'codec': WorkPackage.to_codec_def()}) @dataclass class MsgCE133WorkPackageSubmission(Serializable): - core_index: int = field(metadata={'codec': U32}) + core_index: int = field(metadata={'codec': U16}) work_package: WorkPackage = field(metadata={'codec': WorkPackage.to_codec_def()}) @dataclass @@ -126,7 +125,7 @@ class MsgCE133Extrinsic(Serializable): @dataclass class MsgCE134WorkPackageSharing(Serializable): - core_index: int = field(metadata={'codec': U32}) + core_index: int = field(metadata={'codec': U16}) segment_root_map: Dict[bytes, bytes] = field(metadata={'codec': Map(H256, H256)}) @@ -244,7 +243,7 @@ class MsgCE143Preimage(Serializable): @dataclass class MsgCE144CoreWRPair(Serializable): - core_index: int = field(metadata={'codec': U32}) + core_index: int = field(metadata={'codec': U16}) wr_hash: bytes = field(metadata={'codec': H256}) @dataclass diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 29fe1128..20da8949 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -50,7 +50,7 @@ from pyjamaz.transport.jamnp_s.connection import JAMConnection -from pyjamaz.pyjamaz.models.common import WorkPackage +from pyjamaz.models.common import WorkPackage logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -70,6 +70,7 @@ def __init__(self, host, port, certificate, private_key, app): self.app = app self.state_requesting_blocks = False + self.state_warp_sync = True #TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! bl_hash = app.retrieve_block_hash(0).hex() if bl_hash.startswith('0x'): bl_hash = bl_hash[2:] @@ -104,7 +105,7 @@ def __init__(self, host, port, certificate, private_key, app): async def listen(self): - logger.debug(f'Listening on {self.host}:{self.port}') + logger.info(f'Listening on {self.host}:{self.port}') server_conf = QuicConfiguration( alpn_protocols=[self.protocol_name], @@ -153,7 +154,7 @@ async def connect(self, host, port): logger.warning(f"Ignoring duplicate connection to {addr}") return - logger.debug(f"Connecting to {host}:{port}") + logger.info(f"Connecting to {host}:{port}") try: async with connect( host, @@ -194,7 +195,7 @@ def up0_send_handshake(self, conn: JAMConnection): timeslot=slot, leafs=leafs ) - logger.debug(f"Send handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") + logger.info(f"Send handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") add_stream_type = conn.direction == StreamDirection.initiator @@ -213,7 +214,7 @@ def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): block = self.app.retrieve_block_by_hash(msg.header_hash) #TODO: missch een efficientere exists check toevoegen #TODO: ook nog iets doen met slot en leafs ook mee requesten? if not block: - logger.debug(f"Received newer block from handshake: {msg.header_hash} -> initiate CE128RequestBlocks") + logger.info(f"Received newer block from handshake: {msg.header_hash} -> initiate CE128RequestBlocks") self.state_requesting_blocks = True curr_hash = self.app.retrieve_block_hash(self.app.state.timeslot.number) # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? @@ -244,7 +245,7 @@ def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement block = self.app.retrieve_block_by_hash(msg.header.hash) #TODO: missch een efficientere exists check toevoegen #TODO: ook nog iets doen met bijhorende finalized header_hash & slot? if not block: - logger.debug(f"Received new block announcement from up0: {msg.header.hash}") + logger.info(f"Received new block announcement from up0: {msg.header.hash}") self.state_requesting_blocks = True curr_hash = self.app.retrieve_block_hash(self.app.state.timeslot.number) # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? @@ -261,7 +262,7 @@ def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRequest): #TODO: missch een batch param meegeven, zodat we een grotere reeks kunnen binnenhalen maar batchen... stream = conn.open_jam_stream(StreamBlockRequest, direction=StreamDirection.initiator) - logger.debug(f"Initiate block request on stream id: {stream.stream_id} direction: {req.direction}, max_block: {req.max_blocks} header hash: {req.header_hash}") + logger.info(f"Initiate block request on stream id: {stream.stream_id} direction: {req.direction}, max_block: {req.max_blocks} header hash: {req.header_hash}") conn.send( stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), @@ -271,11 +272,11 @@ def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRe def ce128_received_block_request(self, stream: StreamBlockRequest, req: MsgCE128BlockRequestResponse): blocks = req.blocks - logger.debug(f"Parsed {len(blocks)} blocks") + logger.info(f"Parsed {len(blocks)} blocks") asyncio.create_task( self.app.import_queue_add_blocks( blocks, - process=True, + #process=True, on_success=MESSAGE_TYPES.CE128_SUCCESS, on_failure=MESSAGE_TYPES.CE128_FAILURE ) @@ -318,7 +319,7 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 blocks.append(block) if blocks: - logger.debug(f"Send {len(blocks)} blocks (direction={block_req.direction} max blocks={block_req.max_blocks})") + logger.info(f"Send {len(blocks)} blocks (direction={block_req.direction} max blocks={block_req.max_blocks})") stream.conn.send( stream.stream_id, stream.create_message(MsgCE128BlockRequestResponse(blocks=blocks).to_jam_bytes().to_bytes()), @@ -326,12 +327,12 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 ) else: #TODO: ook op max_blocks checken -> bijhouden op de stream? - logger.debug(f"No blocks to send requested: {block_req.header_hash.hex()} current: {last_block_hash.hex()}") + logger.info(f"No blocks to send requested: {block_req.header_hash.hex()} current: {last_block_hash.hex()}") stream.conn.send(stream.stream_id, b'', end_stream=True) def ce128_abort_block_request(self): - logger.debug(f"Finished, start parsing import queue {len(self.app._import_queue)}") + logger.info(f"Finished, start parsing import queue {len(self.app._import_queue)}") # Note: could happen during a block_request "session" #TODO: or do we want to clear the queue? asyncio.create_task( @@ -358,12 +359,12 @@ def ce128_block_request_failure(self, reset_code: int): def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, msg: MsgCE131SafroleTicketDistribution): - logger.debug(f"Received ticket for epoch {msg.epoch_index}") + logger.info(f"Received ticket for epoch {msg.epoch_index}") current_epoch = self.app.state.timeslot.number // EPOCH_TIMESLOTS - if msg.epoch_index != current_epoch and msg.epoch_index != current_epoch + 1: + if msg.epoch_index < current_epoch: logger.warning(f"Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") - stream.reset(1) + stream.send_reset(1) return #TODO: create helper function from this (in app? verify_ticket) @@ -384,18 +385,20 @@ def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, ms # Check if we already have this ticket if ticket_body in self.app.state.safrole.ticket_accumulator: - logger.debug(f"Ticket already in accumulator") - stream.reset(2) + logger.info(f"Ticket already in accumulator") + stream.send_reset(2) return # Add ticket to our accumulator #TODO: ticketbody can be reused from above - self.app.block_extrinsic.add_ticket( - TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof), - ring_public_keys, - entropy + asyncio.create_task( + self.app.block_extrinsic.add_ticket( + TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof), + ring_public_keys, + entropy + ) ) - + logger.info(f"Ticket added for epoch {msg.epoch_index}") #TODO: check if we should forward to (proxy) nodes via CE132 @@ -417,14 +420,14 @@ def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, ms except ValueError as e: #TODO: register this, detect bad nodes logger.error(f"Invalid ticket: {e}") - stream.reset(3) + stream.send_reset(3) return stream.conn.send(stream.stream_id, b'', end_stream=True) def ce131_ticket_distribution_success(self, reset_code: int): - logger.debug(f"Finished with code {reset_code}") + logger.info(f"Finished with code {reset_code}") # TODO: handle success, perhaps update state @@ -435,7 +438,7 @@ def ce131_ticket_distribution_failure(self, reset_code: int): def ce132_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE132SafroleTicketDistribution): stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep2, direction=StreamDirection.initiator) - logger.debug(f"Distribute ticket on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + logger.info(f"Distribute ticket on stream id: {stream.stream_id} to {conn.host}:{conn.port}") conn.send( stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), @@ -444,13 +447,13 @@ def ce132_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE132S def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, msg: MsgCE132SafroleTicketDistribution): - logger.debug(f"Received ticket for epoch {msg.epoch_index}") + logger.info(f"Received ticket for epoch {msg.epoch_index}") # Verify epoch index current_epoch = self.app.state.timeslot.number // EPOCH_TIMESLOTS - if msg.epoch_index != current_epoch and msg.epoch_index != current_epoch + 1: + if msg.epoch_index < current_epoch: logger.warning(f"Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") - stream.reset(1) + stream.send_reset(1) return # Verify ticket proof @@ -471,23 +474,25 @@ def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, ms # Check if we already have this ticket if ticket_body in self.app.state.safrole.ticket_accumulator: - logger.debug(f"Ticket already in accumulator (via CE132), ignoring") + logger.info(f"Ticket already in accumulator (via CE132), ignoring") # Don't reset, just close normally as this is expected in CE132 stream.conn.send(stream.stream_id, b'', end_stream=True) return # Add ticket to our accumulator - self.app.block_extrinsic.add_ticket( - TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof), - ring_public_keys, - entropy + asyncio.create_task( + self.app.block_extrinsic.add_ticket( + TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof), + ring_public_keys, + entropy + ) ) - + logger.info(f"Valid ticket received via CE132 (forwarded) for epoch {msg.epoch_index}") except ValueError as e: logger.error(f"Invalid ticket: {e}") - stream.reset(3) + stream.send_reset(3) return stream.conn.send(stream.stream_id, b'', end_stream=True) @@ -505,46 +510,48 @@ def ce132_ticket_distribution_failure(self, reset_code: int): def ce133_initiate_workpackage_submission(self, conn: JAMConnection, wp: MsgCE133WorkPackageSubmission, extrinsic: MsgCE133Extrinsic): stream = conn.open_jam_stream(StreamWorkPackageSubmission, direction=StreamDirection.initiator) - logger.debug(f"Initiating workpackage submission on stream id: {stream.stream_id}") + logger.info(f"Initiating workpackage submission on stream id: {stream.stream_id}") conn.send(stream.stream_id, stream.create_message(wp.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) conn.send(stream.stream_id, stream.create_message(extrinsic.to_jam_bytes().to_bytes()), end_stream=True) - def ce133_received_workpackage_submission(self, stream: StreamWorkPackageSubmission, msg: MsgCE133WorkPackageSubmission): - logger.debug(f"Received workpackage submission for core {msg.core_index}") - # TODO: process work package + # def ce133_received_workpackage_submission(self, stream: StreamWorkPackageSubmission, msg: MsgCE133WorkPackageSubmission): + # logger.debug(f"Received workpackage submission for core {msg.core_index}") + # # TODO: process work package - def ce133_received_extrinsic_submission(self, stream: StreamWorkPackageSubmission, msg: MsgCE133Extrinsic): - logger.debug(f"CE133 received extrinsic data of length {len(msg.bytes_)}") + def ce133_received_extrinsic_submission(self, stream: StreamWorkPackageSubmission, wp: WorkPackage, extrinsics: List[bytes]): + #logger.debug(f"CE133 received extrinsic data of length {len(msg.bytes_)}") # TODO: process extrinsic, close stream if successful # stream.acceptor_reset(0) - stream.conn.send(stream.stream_id, b'', end_stream=True) - - def ce133_submission_success(self, reset_code: int): - logger.debug(f"CE133 submission successful with code {reset_code}") + stream.conn.send(stream.stream_id, b'', end_stream=True) + self.app.add_work_package(wp, extrinsics) - def ce133_submission_failure(self, reset_code: int): - logger.error(f"CE133 submission failed with code {reset_code}") + # def ce133_submission_success(self, reset_code: int): + # logger.debug(f"CE133 submission successful with code {reset_code}") + # + # + # def ce133_submission_failure(self, reset_code: int): + # logger.error(f"CE133 submission failed with code {reset_code}") def ce134_initiate_workpackage_sharing(self, conn: JAMConnection, sharing: MsgCE134WorkPackageSharing, bundle: MsgCE134WorkPackageBundle): stream = conn.open_jam_stream(StreamWorkPackageSharing, direction=StreamDirection.initiator) - logger.debug(f"Initiate sharing workpage on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + logger.info(f"Initiate sharing workpage on stream id: {stream.stream_id} to {conn.host}:{conn.port}") conn.send(stream.stream_id, stream.create_message(sharing.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) conn.send(stream.stream_id, stream.create_message(bundle.to_jam_bytes().to_bytes()), end_stream=True) def ce134_received_workpackage_sharing(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageSharing): - logger.debug(f"ce134_received_workpackage_sharing for core {msg.core_index}") + logger.info(f"ce134_received_workpackage_sharing for core {msg.core_index}") # Store sharing msg in stream state for later use with bundle stream.sharing_msg = msg def ce134_received_bundle(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageBundle): - logger.debug(f"ce134_received_bundle") + logger.info(f"ce134_received_bundle") work_package = WorkPackage( authorization=msg.authorization, @@ -564,7 +571,7 @@ def ce134_received_bundle(self, stream: StreamWorkPackageSharing, msg: MsgCE134W def ce134_received_refine_response(self, stream: StreamWorkPackageSharing, msg: MsgCE134RefineResponse): - logger.debug(f"ce134_received_refine_response") + logger.info(f"ce134_received_refine_response") work_report = None #TODO!!! msg... # Guarantee the work report self.app.guarantee_work_report(work_report, self.app.current_timeslot()) @@ -572,7 +579,7 @@ def ce134_received_refine_response(self, stream: StreamWorkPackageSharing, msg: def ce134_sharing_success(self, reset_code: int): - logger.debug(f"Success with code {reset_code}") + logger.info(f"Success with code {reset_code}") def ce134_sharing_failure(self, reset_code: int): @@ -581,19 +588,19 @@ def ce134_sharing_failure(self, reset_code: int): def ce135_initiate_distribution(self, conn: JAMConnection, msg: MsgCE135GuaranteedWorkReport): stream = conn.open_jam_stream(StreamWorkReportDistribution, direction=StreamDirection.initiator) - logger.debug(f"CE135 initiating distribution on stream id: {stream.stream_id}") + logger.info(f"CE135 initiating distribution on stream id: {stream.stream_id}") conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) def ce135_received_report(self, stream: StreamWorkReportDistribution, msg: MsgCE135GuaranteedWorkReport): - logger.debug(f"CE135 received work report") + logger.info(f"CE135 received work report") # TODO: process report # stream.acceptor_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) def ce135_distribution_success(self, reset_code: int): - logger.debug(f"CE135 distribution successful with code {reset_code}") + logger.info(f"CE135 distribution successful with code {reset_code}") def ce135_distribution_failure(self, reset_code: int): @@ -602,21 +609,20 @@ def ce135_distribution_failure(self, reset_code: int): def ce136_initiate_request(self, conn: JAMConnection, msg: MsgCE136HashRequest): stream = conn.open_jam_stream(StreamWorkReportRequest, direction=StreamDirection.initiator) - logger.debug(f"CE136 initiating request on stream id: {stream.stream_id}") + logger.info(f"CE136 initiating request on stream id: {stream.stream_id}") conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) def ce136_received_request(self, stream: StreamWorkReportRequest, msg: MsgCE136HashRequest): - logger.debug(f"CE136 received request for hash {msg.hash.hex()}") + logger.info(f"CE136 received request for hash {msg.hash.hex()}") # TODO: lookup work report, send if available report = MsgCE136WorkReport(report=b'') # placeholder stream.conn.send(stream.stream_id, stream.create_message(report.to_jam_bytes().to_bytes()), end_stream=True) def ce136_received_report(self, stream: StreamWorkReportRequest, msg: MsgCE136WorkReport): - logger.debug(f"CE136 received work report of length {len(msg.report)}") + logger.info(f"CE136 received work report of length {len(msg.report)}") # TODO: process report - # stream.initiator_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) @@ -650,7 +656,7 @@ def ce137_received_shard(self, stream: StreamShardDistribution, bundle, segments def ce137_distribution_success(self, code: int): - logger.debug(f"CE137 success {code}") + logger.info(f"CE137 success {code}") def ce137_distribution_failure(self, code: int): @@ -677,7 +683,7 @@ def ce138_received_shard(self, stream: StreamAuditShardRequest, bundle, just): def ce138_request_success(self, code: int): - logger.debug(f"CE138 success {code}") + logger.info(f"CE138 success {code}") def ce138_request_failure(self, code: int): @@ -702,7 +708,7 @@ def ce139_received_shards(self, stream: StreamSegmentShardRequest, shards): def ce139_request_success(self, code: int): - logger.debug(f"CE139 success {code}") + logger.info(f"CE139 success {code}") def ce139_request_failure(self, code: int): @@ -729,7 +735,7 @@ def ce140_received_shards_justified(self, stream: StreamSegmentShardRequestJusti def ce140_request_success(self, code: int): - logger.debug(f"CE140 success {code}") + logger.info(f"CE140 success {code}") def ce140_request_failure(self, code: int): @@ -738,19 +744,19 @@ def ce140_request_failure(self, code: int): def ce141_initiate_distribution(self, conn: JAMConnection, msg: MsgCE141Assurance): stream = conn.open_jam_stream(StreamAssuranceDistribution, direction=StreamDirection.initiator) - logger.debug(f"Initiating assurance distribution on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + logger.info(f"Initiating assurance distribution on stream id: {stream.stream_id} to {conn.host}:{conn.port}") conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) def ce141_received_assurance(self, stream: StreamAssuranceDistribution, msg: MsgCE141Assurance): - logger.debug(f"Received assurance") + logger.info(f"Received assurance") # TODO: process!... # stream.acceptor_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) def ce141_distribution_success(self, reset_code: int): - logger.debug(f"Success with code {reset_code}") + logger.info(f"Success with code {reset_code}") def ce141_distribution_failure(self, reset_code: int): @@ -759,19 +765,19 @@ def ce141_distribution_failure(self, reset_code: int): def ce142_initiate_announcement(self, conn: JAMConnection, msg: MsgCE142PreimageAnnouncement): stream = conn.open_jam_stream(StreamPreimageAnnouncement, direction=StreamDirection.initiator) - logger.debug(f"Initiating preimage announcement on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + logger.info(f"Initiating preimage announcement on stream id: {stream.stream_id} to {conn.host}:{conn.port}") conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) def ce142_received_announcement(self, stream: StreamPreimageAnnouncement, msg: MsgCE142PreimageAnnouncement): - logger.debug(f"Received preimage announcement for hash {msg.hash.hex()}") + logger.info(f"Received preimage announcement for hash {msg.hash.hex()}") # TODO: check if needed, request via CE143 if so # stream.acceptor_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) def ce142_announcement_success(self, reset_code: int): - logger.debug(f"Success with code {reset_code}") + logger.info(f"Success with code {reset_code}") def ce142_announcement_failure(self, reset_code: int): @@ -780,26 +786,25 @@ def ce142_announcement_failure(self, reset_code: int): def ce143_initiate_request(self, conn: JAMConnection, msg: MsgCE143HashRequest): stream = conn.open_jam_stream(StreamPreimageRequest, direction=StreamDirection.initiator) - logger.debug(f"CE143 initiating request on stream id: {stream.stream_id}") + logger.info(f"CE143 initiating request on stream id: {stream.stream_id}") conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) def ce143_received_request(self, stream: StreamPreimageRequest, msg: MsgCE143HashRequest): - logger.debug(f"CE143 received request for hash {msg.hash.hex()}") + logger.info(f"CE143 received request for hash {msg.hash.hex()}") # TODO: lookup preimage, send if available preimage = MsgCE143Preimage(bytes_=b'') # placeholder stream.conn.send(stream.stream_id, stream.create_message(preimage.to_jam_bytes().to_bytes()), end_stream=True) def ce143_received_preimage(self, stream: StreamPreimageRequest, msg: MsgCE143Preimage): - logger.debug(f"CE143 received preimage of length {len(msg.bytes_)}") + logger.info(f"CE143 received preimage of length {len(msg.bytes_)}") # TODO: process preimage - # stream.initiator_reset(0) stream.conn.send(stream.stream_id, b'', end_stream=True) def ce143_request_success(self, reset_code: int): - logger.debug(f"CE143 request successful with code {reset_code}") + logger.info(f"CE143 request successful with code {reset_code}") def ce143_request_failure(self, reset_code: int): @@ -823,7 +828,7 @@ def ce144_received_evidence(self, stream: StreamAuditAnnouncement, msg: MsgCE144 def ce144_announcement_success(self, code: int): - logger.debug(f"CE144 success {code}") + logger.info(f"CE144 success {code}") def ce144_announcement_failure(self, code: int): @@ -841,7 +846,7 @@ def ce145_received_judgment(self, stream: StreamJudgmentPublication, msg: MsgCE1 def ce145_publication_success(self, code: int): - logger.debug(f"CE145 success {code}") + logger.info(f"CE145 success {code}") def ce145_publication_failure(self, code: int): @@ -850,7 +855,7 @@ def ce145_publication_failure(self, code: int): # Note: pubsub_ prefixed methods are driven by application level events async def pubsub_up0_broadcast_block(self, block: Block): - logger.debug(f'Broadcast {block.header.hash.hex()} to {len(self.connections)} connections') + logger.info(f'Broadcast {block.header.hash.hex()} to {len(self.connections)} connections') msg = MsgUP0Announcement( header=block.header, @@ -892,7 +897,7 @@ async def pubsub_ce131_initiate_distribute_own_ticket(self, data): if conn.is_connected(): try: stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep1, direction=StreamDirection.initiator) - logger.debug(f"Send ticker announcement on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + logger.info(f"Send ticker announcement on stream id: {stream.stream_id} to {conn.host}:{conn.port}") conn.send( stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py index 67891b3b..52e28abb 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -87,12 +87,13 @@ def receive_data(self, data: bytes, end_stream: bool = False): # TODO: check message length > 0???!!!! if self._msg_len <= 0: logger.error(f"Message size <= 0 for stream {self.stream_id} {self._msg_len}") - self.reset(self.ERROR_INVALID_SIZE) + self.send_reset(self.ERROR_INVALID_SIZE) + return # Add max size check if self._msg_len > self.protocol.MAX_MESSAGE_SIZE: logger.error(f"Message too large for stream {self.stream_id}") - self.reset(self.ERROR_TOO_LARGE) + self.send_reset(self.ERROR_TOO_LARGE) return msg_complete = False @@ -137,7 +138,7 @@ def handle_error(self, exc: Exception): else: code = self.ERROR_GENERAL logger.error(f"Stream {self.stream_id} error: {exc}") - self.reset(code) + self.send_reset(code) def handle_fin(self): @@ -145,16 +146,25 @@ def handle_fin(self): pass - def reset(self, reset_code: int): + def send_reset(self, reset_code: int): + self.conn.close_jam_stream(self, reset_code, False) + + + def receive_reset(self, reset_code: int): + #TODO: Do we really need to handle initiator and acceptor seperately here or can we merge initiator_reset and acceptor_reset? if self.direction == StreamDirection.initiator: self.initiator_reset(reset_code) else: self.acceptor_reset(reset_code) + del self.conn.streams[self.stream_id] + + # TODO: do we really need difference between a initiator and acceptor??? def initiator_reset(self, reset_code: int): - self.conn.close_jam_stream(self, reset_code, False) + raise Exception("Implement in subclass") + # TODO: do we really need difference between a initiator and acceptor??? def acceptor_reset(self, reset_code: int): - self.conn.close_jam_stream(self, reset_code, False) + raise Exception("Implement in subclass") diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py index f64786bf..911fc192 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE128 received reset code: {reset_code}") self.protocol.ce128_block_request_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -34,8 +33,8 @@ def handle_fin(self): def acceptor_reset(self, reset_code: int): + logger.debug(f"CE128 received reset code: {reset_code}") self.protocol.ce128_block_request_failure(reset_code) - super().reset(reset_code) def acceptor_message(self, data: bytes): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_131.py b/pyjamaz/transport/jamnp_s/streams/stream_131.py index a5579ac2..4166acf1 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_131.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_131.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE131 received reset code: {reset_code}") self.protocol.ce131_ticket_distribution_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -33,6 +32,11 @@ def acceptor_message(self, data: bytes): self.protocol.ce131_received_ticket(self, msg) + def acceptor_reset(self, reset_code: int): + logger.debug(f"CE131 received reset code: {reset_code}") + self.protocol.ce131_ticket_distribution_failure(reset_code) + + def handle_fin(self): super().handle_fin() self.protocol.ce131_ticket_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_132.py b/pyjamaz/transport/jamnp_s/streams/stream_132.py index c9fd2291..5dd055cb 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_132.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_132.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE132 received reset code: {reset_code}") self.protocol.ce132_ticket_distribution_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -33,6 +32,11 @@ def acceptor_message(self, data: bytes): self.protocol.ce132_received_ticket(self, msg) + def acceptor_reset(self, reset_code: int): + logger.debug(f"CE132 received reset code: {reset_code}") + self.protocol.ce132_ticket_distribution_failure(reset_code) + + def handle_fin(self): super().handle_fin() self.protocol.ce132_ticket_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_133.py b/pyjamaz/transport/jamnp_s/streams/stream_133.py index e82ea97b..99e4625f 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_133.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_133.py @@ -2,6 +2,7 @@ from jamcodec.base import JamBytes +from pyjamaz.hashing import blake2b_256_hash from pyjamaz.transport.jamnp_s.message_types import MsgCE133WorkPackageSubmission, MsgCE133Extrinsic from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection @@ -14,13 +15,13 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): super().__init__(stream_id, connection, direction) self.stream_type = StreamType.CE133_WorkPackageSubmission.value self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - self.received_wp = False + self.received_wp = None def initiator_reset(self, reset_code: int): logger.debug(f"CE133 received reset code: {reset_code}") - self.protocol.ce133_submission_failure(reset_code) - super().initiator_reset(reset_code) + #self.protocol.ce133_submission_failure(reset_code) + self.received_wp = None def initiator_message(self, data: bytes): @@ -29,22 +30,36 @@ def initiator_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - self.protocol.ce133_submission_failure(reset_code) - super().reset(reset_code) + logger.debug(f"CE133 received reset code: {reset_code}") + #self.protocol.ce133_submission_failure(reset_code) + self.received_wp = None def acceptor_message(self, data: bytes): - if not self.received_wp: + if self.received_wp is None: logger.debug(f"CE133 acceptor received work package") msg = MsgCE133WorkPackageSubmission.from_jam_bytes(JamBytes(data)) - self.protocol.ce133_received_workpackage_submission(self, msg) - self.received_wp = True + self.received_wp = msg.work_package + #self.protocol.ce133_received_workpackage_submission(self, msg) #TODO: do we need this event at protocol level? else: logger.debug(f"CE133 acceptor received extrinsic data") - msg = MsgCE133Extrinsic.from_jam_bytes(JamBytes(data)) - self.protocol.ce133_received_extrinsic_submission(self, msg) + + extrinsics_data = JamBytes(data) + extriniscs_list = [] + + for item in self.received_wp.items: + for ext in item.extrinsic: + ext_data = extrinsics_data.get_next_bytes(ext.len) + if blake2b_256_hash(ext_data) != ext.hash: + #TODO: handle at protocol level + raise ValueError("Extrinsic hash mismatch") + + extriniscs_list.append(ext_data) + + self.protocol.ce133_received_extrinsic_submission(self, self.received_wp, extriniscs_list) def handle_fin(self): super().handle_fin() + self.received_wp = None self.protocol.ce133_submission_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_134.py b/pyjamaz/transport/jamnp_s/streams/stream_134.py index f96359e4..80fef174 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_134.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_134.py @@ -20,7 +20,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE134 received reset code: {reset_code}") self.protocol.ce134_sharing_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -35,8 +34,8 @@ def initiator_message(self, data: bytes): def acceptor_reset(self, reset_code: int): + logger.debug(f"CE134 received reset code: {reset_code}") self.protocol.ce134_sharing_failure(reset_code) - super().reset(reset_code) def acceptor_message(self, data: bytes): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_135.py b/pyjamaz/transport/jamnp_s/streams/stream_135.py index 587630f0..94e189a6 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_135.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_135.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE135 received reset code: {reset_code}") self.protocol.ce135_distribution_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -28,8 +27,8 @@ def initiator_message(self, data: bytes): def acceptor_reset(self, reset_code: int): + logger.debug(f"CE135 received reset code: {reset_code}") self.protocol.ce135_distribution_failure(reset_code) - super().reset(reset_code) def acceptor_message(self, data: bytes): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_136.py b/pyjamaz/transport/jamnp_s/streams/stream_136.py index 2168ff3c..596a677d 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_136.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_136.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE136 received reset code: {reset_code}") self.protocol.ce136_request_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -34,6 +33,11 @@ def acceptor_message(self, data: bytes): self.protocol.ce136_received_request(self, msg) + def acceptor_reset(self, reset_code: int): + logger.debug(f"CE136 received reset code: {reset_code}") + self.protocol.ce136_request_failure(reset_code) + + def handle_fin(self): super().handle_fin() self.protocol.ce136_request_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_137.py b/pyjamaz/transport/jamnp_s/streams/stream_137.py index 331429dd..d0bf1b46 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_137.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_137.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE137 received reset code: {reset_code}") self.protocol.ce137_distribution_failure(reset_code) - super().initiator_reset(reset_code) def _chunks(self, data: bytes, chunk_size: int): @@ -43,6 +42,11 @@ def acceptor_message(self, data: bytes): self.protocol.ce137_received_request(self, msg) + def acceptor_reset(self, reset_code: int): + logger.debug(f"CE137 received reset code: {reset_code}") + self.protocol.ce137_distribution_failure(reset_code) + + def handle_fin(self): super().handle_fin() self.protocol.ce137_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_138.py b/pyjamaz/transport/jamnp_s/streams/stream_138.py index a6e3c759..3613332c 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_138.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_138.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE138 received reset code: {reset_code}") self.protocol.ce138_request_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -35,6 +34,11 @@ def acceptor_message(self, data: bytes): self.protocol.ce138_received_request(self, msg) + def acceptor_reset(self, reset_code: int): + logger.debug(f"CE138 received reset code: {reset_code}") + self.protocol.ce138_request_failure(reset_code) + + def handle_fin(self): super().handle_fin() self.protocol.ce138_request_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_139.py b/pyjamaz/transport/jamnp_s/streams/stream_139.py index 06748468..f5c4a173 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_139.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_139.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE139 received reset code: {reset_code}") self.protocol.ce139_request_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -34,6 +33,11 @@ def acceptor_message(self, data: bytes): self.protocol.ce139_received_request(self, msg) + def acceptor_reset(self, reset_code: int): + logger.debug(f"CE139 received reset code: {reset_code}") + self.protocol.ce139_request_failure(reset_code) + + def handle_fin(self): super().handle_fin() self.protocol.ce139_request_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_140.py b/pyjamaz/transport/jamnp_s/streams/stream_140.py index 2d6970ff..d7b40283 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_140.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_140.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE140 received reset code: {reset_code}") self.protocol.ce140_request_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -34,6 +33,11 @@ def acceptor_message(self, data: bytes): self.protocol.ce140_received_request(self, msg) + def acceptor_reset(self, reset_code: int): + logger.debug(f"CE140 received reset code: {reset_code}") + self.protocol.ce140_request_failure(reset_code) + + def handle_fin(self): super().handle_fin() self.protocol.ce140_request_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_141.py b/pyjamaz/transport/jamnp_s/streams/stream_141.py index 36c04dd2..4a4c992d 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_141.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_141.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE141 received reset code: {reset_code}") self.protocol.ce141_distribution_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -27,17 +26,17 @@ def initiator_message(self, data: bytes): self.handle_error("Unexpected data", 1) - def acceptor_reset(self, reset_code: int): - self.protocol.ce141_distribution_failure(reset_code) - super().reset(reset_code) - - def acceptor_message(self, data: bytes): logger.debug(f"CE141 acceptor received assurance") msg = MsgCE141Assurance.from_jam_bytes(JamBytes(data)) self.protocol.ce141_received_assurance(self, msg) + def acceptor_reset(self, reset_code: int): + logger.debug(f"CE141 received reset code: {reset_code}") + self.protocol.ce141_distribution_failure(reset_code) + + def handle_fin(self): super().handle_fin() self.protocol.ce141_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_142.py b/pyjamaz/transport/jamnp_s/streams/stream_142.py index 2f0fb656..58a7d6f1 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_142.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_142.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE142 received reset code: {reset_code}") self.protocol.ce142_announcement_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -30,8 +29,8 @@ def initiator_message(self, data: bytes): def acceptor_reset(self, reset_code: int): + logger.debug(f"CE142 received reset code: {reset_code}") self.protocol.ce142_announcement_failure(reset_code) - super().reset(reset_code) def acceptor_message(self, data: bytes): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_143.py b/pyjamaz/transport/jamnp_s/streams/stream_143.py index 04c79baa..948e5328 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_143.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_143.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE143 received reset code: {reset_code}") self.protocol.ce143_request_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -34,6 +33,11 @@ def acceptor_message(self, data: bytes): self.protocol.ce143_received_request(self, msg) + def acceptor_reset(self, reset_code: int): + logger.debug(f"CE143 received reset code: {reset_code}") + self.protocol.ce143_request_failure(reset_code) + + def handle_fin(self): super().handle_fin() self.protocol.ce143_request_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_144.py b/pyjamaz/transport/jamnp_s/streams/stream_144.py index ceca23e3..423117b2 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_144.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_144.py @@ -20,7 +20,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE144 received reset code: {reset_code}") self.protocol.ce144_announcement_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -40,6 +39,11 @@ def acceptor_message(self, data: bytes): self.protocol.ce144_received_evidence(self, msg) + def acceptor_reset(self, reset_code: int): + logger.debug(f"CE144 received reset code: {reset_code}") + self.protocol.ce144_announcement_failure(reset_code) + + def handle_fin(self): super().handle_fin() self.protocol.ce144_announcement_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_145.py b/pyjamaz/transport/jamnp_s/streams/stream_145.py index 39ad2176..2567b2e0 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_145.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_145.py @@ -19,7 +19,6 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): logger.debug(f"CE145 received reset code: {reset_code}") self.protocol.ce145_publication_failure(reset_code) - super().initiator_reset(reset_code) def initiator_message(self, data: bytes): @@ -33,6 +32,11 @@ def acceptor_message(self, data: bytes): self.protocol.ce145_received_judgment(self, msg) + def acceptor_reset(self, reset_code: int): + logger.debug(f"CE145 received reset code: {reset_code}") + self.protocol.ce145_publication_failure(reset_code) + + def handle_fin(self): super().handle_fin() self.protocol.ce145_publication_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/rpc/rpc.py b/pyjamaz/transport/rpc/rpc.py index 3650be0f..3993ac25 100644 --- a/pyjamaz/transport/rpc/rpc.py +++ b/pyjamaz/transport/rpc/rpc.py @@ -172,11 +172,17 @@ def rpcParameters(app, params): def rpcBestBlock(app, params): - return [list(app.retrieve_block_hash(app.state.timeslot.number)), app.state.timeslot.number] + return { + "header_hash": list(app.retrieve_block_hash(app.state.timeslot.number)), + "slot": app.state.timeslot.number + } def rpcFinalizedBlock(app, params): - return [list(app.retrieve_block_hash(app.state.timeslot.number)), app.state.timeslot.number] + return { + "header_hash": list(app.retrieve_block_hash(app.state.timeslot.number)), + "slot": app.state.timeslot.number + } def rpcParent(app, params): From 848885873021b7a3a84fca66e61b18d9634bd0ed Mon Sep 17 00:00:00 2001 From: matthijsb Date: Tue, 29 Jul 2025 20:52:14 +0200 Subject: [PATCH 41/51] - JAMNP WIP --- bootstrap_builder.py | 12 +- pyjamaz/cli.py | 41 +- pyjamaz/extrinsic.py | 20 + pyjamaz/transport/jamnp_s/connection.py | 28 +- pyjamaz/transport/jamnp_s/protocol.py | 364 ++++++++++-------- pyjamaz/transport/jamnp_s/stream_base.py | 2 + pyjamaz/transport/jamnp_s/streams/stream_0.py | 8 +- .../transport/jamnp_s/streams/stream_128.py | 12 +- .../transport/jamnp_s/streams/stream_131.py | 10 +- .../transport/jamnp_s/streams/stream_134.py | 8 +- .../transport/jamnp_s/streams/stream_135.py | 8 +- .../transport/jamnp_s/streams/stream_136.py | 8 +- .../transport/jamnp_s/streams/stream_137.py | 8 +- .../transport/jamnp_s/streams/stream_138.py | 8 +- .../transport/jamnp_s/streams/stream_139.py | 8 +- .../transport/jamnp_s/streams/stream_140.py | 8 +- .../transport/jamnp_s/streams/stream_141.py | 8 +- .../transport/jamnp_s/streams/stream_142.py | 8 +- .../transport/jamnp_s/streams/stream_144.py | 8 +- .../transport/jamnp_s/streams/stream_145.py | 8 +- pyjamaz/transport/rpc/ws_client.py | 9 +- 21 files changed, 329 insertions(+), 265 deletions(-) diff --git a/bootstrap_builder.py b/bootstrap_builder.py index 443fe183..3007e222 100644 --- a/bootstrap_builder.py +++ b/bootstrap_builder.py @@ -12,7 +12,7 @@ # Bootstrap service - Helper functions async def get_service_registry(client) -> ServiceRegistry: - best_block = await client.bestBlock() + best_block = await client.finalizedBlock() block_hash = best_block["header_hash"] services_registry = await client.serviceValue(block_hash, 0, b'\x10service_registry') @@ -20,9 +20,9 @@ async def get_service_registry(client) -> ServiceRegistry: async def create_empty_workpackage(client: WebsocketClient) -> WorkPackage: - best_block = await client.bestBlock() - block_hash = best_block[0] - block_timeslot = best_block[1] + best_block = await client.finalizedBlock() + block_hash = best_block["header_hash"] + block_timeslot = best_block["slot"] state_root = await client.stateRoot(block_hash) beefy_root = await client.beefyRoot(block_hash) @@ -72,7 +72,7 @@ async def create_bootservice_workpackage(client: WebsocketClient, instruction: I async def main(): - async with WebsocketClient("ws://127.0.0.1:19801") as client: + async with WebsocketClient("ws://127.0.0.1:19800") as client: # Init vars bootstrap_service_id = 0 registration = "test123" @@ -116,7 +116,7 @@ async def main(): {'Eject': {'target': 0, 'code_hash': '0x6c63e601e26279872a93b9b443aa52ad1c26e795647f63c0b7e0abff0d3680da'}} ) - extrinsic = [bytes(100), bytes(200), bytes(300)] + extrinsic = [bytes(100), bytes(200), bytes(400)] work_package = await create_bootservice_workpackage(client, create_instruction, extrinsic) diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 1cd061c6..86e6c3a6 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -188,9 +188,9 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, # Note: Add packages that need a different logging level here #log_level = logging.ERROR log_level = logging.INFO - log_level = logging.DEBUG + #log_level = logging.DEBUG log_package_overrides = { - "pyjamaz.transport.jamnp_s": logging.INFO, + "pyjamaz.transport.jamnp_s": logging.DEBUG, "quic": logging.WARNING, } setup_logging(log_level, log_package_overrides) @@ -285,25 +285,24 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, #await nps_protocol.connect("127.0.0.1", 40001) #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! except Exception as exc: traceback.print_exc() - else: - # for validator in app.state.safrole.validators: - # # The validators' IP-layer endpoints are given as IPv6/port combinations, - # # to be found in the first 18 bytes of validator metadata, with the first 16 bytes being the IPv6 address and - # # the latter 2 being a little endian representation of the port. - # - # validator_port = validator.get_metadata_port() - # validator_address = validator.get_metadata_ipaddress() - # - # if validator.ed25519 == app.config.keys.ed25519.public_key: - # logging.debug( - # f'Skipping own node ({validator_address}:{validator_port})' - # ) - # continue - # - # logging.debug(f'Connecting to node {validator_address}:{validator_port}') - # tg.start_soon(nps_protocol.connect, validator_address, validator_port) - tg.start_soon(nps_protocol.connect, "127.0.0.1", 40001) - pass + # else: + # # for validator in app.state.safrole.validators: + # # # The validators' IP-layer endpoints are given as IPv6/port combinations, + # # # to be found in the first 18 bytes of validator metadata, with the first 16 bytes being the IPv6 address and + # # # the latter 2 being a little endian representation of the port. + # # + # # validator_port = validator.get_metadata_port() + # # validator_address = validator.get_metadata_ipaddress() + # # + # # if validator.ed25519 == app.config.keys.ed25519.public_key: + # # logging.debug( + # # f'Skipping own node ({validator_address}:{validator_port})' + # # ) + # # continue + # # + # # logging.debug(f'Connecting to node {validator_address}:{validator_port}') + # # tg.start_soon(nps_protocol.connect, validator_address, validator_port) + # #tg.start_soon(nps_protocol.connect, "127.0.0.1", 40001) await anyio.sleep(ts - time.time()) tg.start_soon(timeslot_ticker, app) diff --git a/pyjamaz/extrinsic.py b/pyjamaz/extrinsic.py index 3f2d9fca..d56cdc77 100644 --- a/pyjamaz/extrinsic.py +++ b/pyjamaz/extrinsic.py @@ -49,14 +49,22 @@ def create_ticket_body(self, ticket_data: TicketEnvelope, ring_public_keys: List return TicketBody(id=ring_vrf_output, attempt=ticket_data.attempt) + async def add_ticket(self, ticket_data: TicketEnvelope, ring_public_keys: List[bytes], entropy: bytes): ticket_body = self.create_ticket_body(ticket_data, ring_public_keys, entropy) async with self._ticket_queue_lock: self.tickets_queue[ticket_body.id] = ticket_data + + async def add_ticket_body(self, ticket_data: TicketEnvelope, ticket_body: TicketBody): + async with self._ticket_queue_lock: + self.tickets_queue[ticket_body.id] = ticket_data + + def can_add_own_ticket(self, timeslot: int) -> bool: return len(self.own_tickets_next) < TICKET_ENTRIES and timeslot % EPOCH_TIMESLOTS < TICKET_SUBMISSION_END_SLOT + async def add_own_ticket( self, ring_public_keys: List[bytes], entropy: bytes, keypair: BandersnatchKeypair, author_index: int, epoch_index: int = None, pubsub: PubSub = None @@ -94,6 +102,7 @@ async def add_own_ticket( if pubsub and epoch_index is not None: await pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.TICKET_ADD, data=[epoch_index, attempt, signature])) + async def collect_tickets(self) -> List[TicketEnvelope]: """ Collect tickets to include in a block @@ -112,43 +121,53 @@ async def collect_tickets(self) -> List[TicketEnvelope]: return [ticket for _, ticket in collected_tickets] + def is_own_ticket(self, ticket_id: bytes) -> bool: pass + def own_ticket_count(self) -> int: return len(self.own_tickets_next) + async def clear_tickets(self): async with self._ticket_queue_lock: self.tickets_queue = {} self.own_tickets_next = [] + async def process_epoch_change(self): self.own_tickets_current = self.own_tickets_next self.own_tickets_next = [] async with self._ticket_queue_lock: self.tickets_queue = {} + def add_guarantee(self, guarantee: Guarantee): self.guarentees_queue.append(guarantee) + async def collect_guarantees(self) -> List[Guarantee]: guarentees = self.guarentees_queue self.guarentees_queue = [] return guarentees + def add_assurance(self, assurance: Assurance): self.assurances_queue.append(assurance) + def collect_assurances(self) -> List[Assurance]: assurances = self.assurances_queue self.assurances_queue = [] return assurances + def add_preimage(self, preimage: Preimage): self.preimage_queue.append(preimage) logging.info(f"🖼️ Added preimage: {format_hash(blake2b_256_hash(preimage.blob))} for service: {preimage.requester}") + def collect_preimages(self, service_state: ServicesState) -> List[Preimage]: # Check which of present preimages are actually requested preimages = [] @@ -164,6 +183,7 @@ def collect_preimages(self, service_state: ServicesState) -> List[Preimage]: return preimages + async def process_block(self, block: Block): """ Inspects and cleans up the ExtrinsicQueue for data that is already present in the block diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 0bd5821c..1156e65e 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -1,5 +1,6 @@ import asyncio import logging +from enum import Enum from aioquic.asyncio import QuicConnectionProtocol, serve from aioquic.quic.events import QuicEvent, HandshakeCompleted, StreamReset, StreamDataReceived, ConnectionTerminated @@ -11,13 +12,18 @@ logger = logging.getLogger("pyjamaz.transport.jamnp_s") +class JAMConnectionDirection(Enum): + initiator: int = 0 + acceptor: int = 1 + + class JAMConnection(QuicConnectionProtocol): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Note: should be set in wrap_protocol - self.direction:StreamDirection = None #TODO: JAMConnectionDirection! + self.direction:JAMConnectionDirection = None self.protocol = None self.jam_connection_ulid = None self.host = None @@ -42,10 +48,12 @@ def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_i def close_jam_stream(self, stream: Stream, reason:int=0, clean_close: bool = False): logger.debug(f"Connection {self.jam_connection_ulid} QUIC closing JAM stream {stream.stream_type} {stream.stream_id} for {stream.direction} (clean: {clean_close})") + if clean_close: self.send(stream.stream_id, b'', end_stream=True) else: self._quic.reset_stream(stream.stream_id, error_code=reason) + if stream.stream_id in self.streams: del self.streams[stream.stream_id] @@ -96,7 +104,7 @@ async def _keepalive(self): pass - def add_connection(self, addr, direction) -> bool: + def add_connection(self, addr: str, direction: JAMConnectionDirection) -> bool: if addr in self.protocol.conn_addr: logger.warning(f"Connection {self.jam_connection_ulid} direction: {direction} is a duplicate {addr}") self.close(error_code=2, reason_phrase="duplicate") @@ -117,17 +125,17 @@ def quic_event_received(self, event: QuicEvent) -> None: logger.debug(f'Connection {self.jam_connection_ulid} QUIC received non stream data {event}') if isinstance(event, HandshakeCompleted): - #TODO: + #TODO: enforce: # Both nodes are validators, and are neighbours in the grid structure. # At least one of the nodes is not a validator. - if self.direction == StreamDirection.initiator: + if self.direction == JAMConnectionDirection.initiator: addr = f"{self.host}:{self.port}" if not self.add_connection(addr, self.direction): return # Initiating side will send a JAM handshake message and set the stream id - stream_up = self.open_jam_stream(StreamUP, direction=self.direction) + stream_up = self.open_jam_stream(StreamUP, direction=StreamDirection.initiator) self.stream_up = stream_up self.protocol.up0_send_handshake(self) else: @@ -141,14 +149,14 @@ def quic_event_received(self, event: QuicEvent) -> None: stream_id = event.stream_id reset_code = event.error_code - logger.info(f'Connection {self.jam_connection_ulid} QUIC StreamReset {stream_id} {self.direction} code {reset_code}') - if stream_id not in self.streams: #raise Exception(f"QUIC stream {stream_id} not available") logger.debug(f"Connection {self.jam_connection_ulid} QUIC stream {stream_id} already closed") return - self.streams[stream_id].received_reset(reset_code) + logger.info(f'Connection {self.jam_connection_ulid} QUIC StreamReset {type(self.streams[stream_id])} ({stream_id}) direction={self.direction} code {reset_code}') + + self.streams[stream_id].receive_reset(reset_code) elif isinstance(event, StreamDataReceived): @@ -176,8 +184,8 @@ def quic_event_received(self, event: QuicEvent) -> None: self.streams[stream_id].receive_data(data, event.end_stream) except Exception as e: logger.error(f"Connection {self.jam_connection_ulid} Error handling stream data: {e}", exc_info=True) - # Reset the stream with an error code - self._quic.reset_stream(stream_id, error_code=2) + if stream_id and self.streams[stream_id]: + self.streams[stream_id].handle_error() raise elif isinstance(event, ConnectionTerminated): diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 20da8949..09f7701c 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -1,12 +1,15 @@ import asyncio import logging +import math import ssl +import time from typing import Dict, List from typing import Optional, cast from aioquic.asyncio import serve from aioquic.quic.configuration import QuicConfiguration +from aioquic.quic.connection import QuicConnectionState from aioquic.tls import SessionTicket from aioquic.asyncio.client import connect @@ -48,14 +51,24 @@ from pyjamaz.transport.jamnp_s.streams.stream_144 import StreamAuditAnnouncement from pyjamaz.transport.jamnp_s.streams.stream_145 import StreamJudgmentPublication -from pyjamaz.transport.jamnp_s.connection import JAMConnection +from pyjamaz.transport.jamnp_s.connection import JAMConnection, JAMConnectionDirection -from pyjamaz.models.common import WorkPackage +from pyjamaz.models.common import WorkPackage, ValidatorData logger = logging.getLogger("pyjamaz.transport.jamnp_s") +class ValidatorConnection: + validator: ValidatorData + ip: str + port: int + connection:JAMConnection + last_try:time + initiator:bool + in_grid:bool + + class JAMNPS(ProtocolType): MAX_MESSAGE_SIZE = 10000000 @@ -76,13 +89,25 @@ def __init__(self, host, port, certificate, private_key, app): if bl_hash.startswith('0x'): bl_hash = bl_hash[2:] bl_hash = bl_hash[:8] + self.validator = None + self.validator_dns = None + self.validator_port = None + self.validator_address = None + + for validator in app.state.safrole.validators: + if validator.ed25519 == self.app.config.keys.ed25519.public_key: + self.validator = validator + self.validator_dns = validator.get_connection_dns() + self.validator_port = validator.get_metadata_port() + self.validator_address = validator.get_metadata_ipaddress() + #TODO: - # callbacks like ce128_processed_block_request could be created using closure functions - # (def create_ce128_processed_block_request returning the callback), + # callbacks like ce128_finish_block_request could be created using closure functions + # (def create_ce128_finish_block_request returning the callback), # to create a local state which holds context like which connection/node initiated this event, so we can mark # malicious nodes etc... - self.pubsub.subscribe(MESSAGE_TYPES.CE128_SUCCESS, self.ce128_processed_block_request) - self.pubsub.subscribe(MESSAGE_TYPES.CE128_FAILURE, self.ce128_processed_block_request) + self.pubsub.subscribe(MESSAGE_TYPES.CE128_SUCCESS, self.ce128_finish_block_request) + self.pubsub.subscribe(MESSAGE_TYPES.CE128_FAILURE, self.ce128_finish_block_request) self.protocol_name = JAMNPS.PROTOCOL_NAME.format(bl_hash) self.session_ticket_store = SessionTicketStore() @@ -102,6 +127,9 @@ def __init__(self, host, port, certificate, private_key, app): self.conn_accepted = set() # All incomming connections self.conn_initiated = set() # All outgoing connections (who we connect to) self.conn_addr = set() # Reference with host:port pairs + self.validator_connections: Dict[bytes:, ValidatorConnection] = {} + #TODO: add a reconnect task + # self._reconnect_task = asyncio.create_task(self.reconnect_validators()) async def listen(self): @@ -118,34 +146,147 @@ async def listen(self): self.host, self.port, configuration=server_conf, - create_protocol=create_jam_connection(self, JAMConnection, StreamDirection.acceptor, self.host, self.port), + create_protocol=create_jam_connection(self, JAMConnection, JAMConnectionDirection.acceptor, self.host, self.port), #session_ticket_fetcher=self.session_ticket_store.pop, #session_ticket_handler=self.session_ticket_store.add, retry=True, ) - def is_preferred_initiator(a: bytes, b: bytes) -> bool: + def should_initiate_connection(self, validator_a: bytes, validator_b: bytes) -> bool: + connect_a = 1 if validator_a[31] > 127 else 0 + connect_b = 1 if validator_b[31] > 127 else 0 + a_less = 1 if validator_a < validator_b else 0 + return (connect_a ^ connect_b ^ a_less) == 1 + + + def add_grid_connections( + self, + validator_idx: int, + validator_queue: List[int, ValidatorData], + same_epoch: bool, + initiate_conns:List[ValidatorData], + expect_conns:List[ValidatorData] + ): + + W = math.floor(math.sqrt(len(validator_queue))) + + for (v_idx, v) in validator_queue: + if self.validator.ed25519 == v.ed25519: + continue + + should_connect = False + + if same_epoch: + if v_idx == validator_idx // W or validator_idx % W == v_idx % W: + should_connect = True + else: + if v_idx == validator_idx: + should_connect = True + + if should_connect: + if self.should_initiate_connection(self.validator.ed25519, v.ed25519): + initiate_conns.append(v) + else: + expect_conns.append(v) + + + async def update_validator_connections(self): """ - TODO: see https://docs.jamcha.in/knowledge/advanced/simple-networking/spec#required-connectivity + TODO: + listen to epoch transitions and : + The first block in the epoch has been finalized. + At least max(⌊E/30⌋,1) slots have elapsed since the beginning of the epoch (where E is the number of slots in an epoch). """ - if len(a) != 32 or len(b) != 32: - raise ValueError("pubkey must be 32 bytes") - a_flag = a[31] > 127 - b_flag = b[31] > 127 - lex_lt = a < b + if not self.validator: + raise Exception("This node is not a validator") + + prev_validators:List[int, ValidatorData] = [(i,v) for i,v in enumerate(self.app.state.validator_archive.validators)] + next_validators:List[int, ValidatorData] = [(i,v) for i,v in enumerate(self.app.state.validator_queue.validators)] + active_validators:List[int, ValidatorData] = [(i,v) for i,v in enumerate(self.app.state.safrole.validators)] + + # Determine our index in the active validator queue + validator_idx = None + for v_idx, v in enumerate(active_validators): + if v.ed25519 == self.validator.ed25519: + validator_idx = v_idx + + if validator_idx is None: + logger.debug(f'Current validator {v.ed25519.hex()} is not present in the validator queue') + return + + #Note: Primarily for the purpose of block and preimage announcements + initiate_grid_connections:List[ValidatorData] = [] + expected_grid_connections:List[ValidatorData] = [] + self.add_grid_connections(validator_idx=validator_idx, validator_queue=prev_validators, same_epoch=False, initiate_conns=initiate_grid_connections, expect_conns=expected_grid_connections) + self.add_grid_connections(validator_idx=validator_idx, validator_queue=active_validators, same_epoch=True, initiate_conns=initiate_grid_connections, expect_conns=expected_grid_connections) + self.add_grid_connections(validator_idx=validator_idx, validator_queue=next_validators, same_epoch=False, initiate_conns=initiate_grid_connections, expect_conns=expected_grid_connections) + initiate_grid_connections = set(initiate_grid_connections) + expected_grid_connections = set(expected_grid_connections) + + # Note: All validators in the previous, current, and next epochs should ensure they are connected to all other such validators. + initiate_connections:List[ValidatorData] = [] + expected_connections:List[ValidatorData] = [] + for v in active_validators: + if v.ed25519 == self.validator.ed25519: + continue + + if self.should_initiate_connection(self.validator.ed25519, v.ed25519): + initiate_connections.append(v) + else: + expected_connections.append(v) + + print("CONNECT TO VALIDATORS: ", initiate_connections) + print("EXPECTING CONNECTIONS: ", expected_connections) + + initiate_connections = set(initiate_connections) + expected_connections = set(expected_connections) + + new_connections = initiate_connections | expected_connections | initiate_grid_connections | expected_grid_connections + current_connections = set(self.validator_connections.keys()) + + # Check if our set changed and need to disconnect irrelevant connections + disconnect_validators = current_connections - new_connections + for ed25519 in disconnect_validators: + if ed25519 in self.validator_connections: + self.disconnect(self.validator_connections[ed25519].connection) + + for v in list(new_connections): + is_initiator:bool = v.ed25519 in initiate_connections or v.ed25519 in initiate_grid_connections + in_grid:bool = v.ed25519 in initiate_grid_connections or v.ed25519 in expected_grid_connections + + if v.ed25519 not in self.validator_connections: + ip, port = (v.get_metadata_ipaddress(), v.get_metadata_port()) + self.validator_connections[v.ed25519] = ValidatorConnection( + validator=v, + ip=ip, + port=port, + connection=None, + last_try=None, + initiator=is_initiator, + in_grid=in_grid + ) + # Note: + #await self.connect(ip, port) + + # Note: Some properties might have changed for existing connections + vc:ValidatorConnection = self.validator_connections[v.ed25519] + vc.initiator = is_initiator + vc.in_grid = in_grid - return a_flag ^ b_flag ^ lex_lt + # Start an active connection + if vc.in_grid and vc.initiator: + if vc.connection is None or vc.connection._quic._state > QuicConnectionState.CONNECTED: + await self.connect(ip, port, v.ed25519) - async def connect(self, host, port): + async def connect(self, host: str, port: int, validator_key: Optional[bytes]): configuration = QuicConfiguration( alpn_protocols=[self.protocol_name], is_client=True, #verify_mode=ssl.CERT_REQUIRED, verify_mode=ssl.CERT_NONE, - #idle_timeout=300000 ) configuration.load_cert_chain(certfile=self.cert, keyfile=self.pk) @@ -161,7 +302,7 @@ async def connect(self, host, port): port, configuration=configuration, # session_ticket_handler=save_session_ticket, - create_protocol=create_jam_connection(self, JAMConnection, StreamDirection.initiator, host, port), + create_protocol=create_jam_connection(self, JAMConnection, JAMConnectionDirection.initiator, host, port, validator_key), ) as client: client = cast(JAMConnection, client) await client.wait_closed() @@ -170,8 +311,8 @@ async def connect(self, host, port): logger.warning(f"💩 Cannot connect to {host}:{port} {exc}") - def disconnect(self, connection: JAMConnection): - if connection.direction == StreamDirection.initiator: + def disconnect(self, connection: JAMConnection, validator_key: Optional[bytes]): + if connection.direction == JAMConnectionDirection.initiator: if connection.jam_connection_ulid in self.conn_initiated: self.conn_initiated.remove(connection.jam_connection_ulid) else: @@ -184,6 +325,9 @@ def disconnect(self, connection: JAMConnection): if connection.addr and connection.addr in self.conn_addr: self.conn_addr.remove(connection.addr) + if validator_key in self.validator_connections: + self.validator_connections[validator_key].connection = None + def up0_send_handshake(self, conn: JAMConnection): # Create a presistent UP0 stream for this connection @@ -197,7 +341,7 @@ def up0_send_handshake(self, conn: JAMConnection): ) logger.info(f"Send handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") - add_stream_type = conn.direction == StreamDirection.initiator + add_stream_type = conn.direction == JAMConnectionDirection.initiator conn.send( conn.stream_up.stream_id, @@ -293,7 +437,6 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 next_hash = block_req.header_hash if block_header and block_req.max_blocks > 0: - #direction = 1 + block_req.direction * -2 for x in range(block_req.max_blocks): @@ -343,21 +486,11 @@ def ce128_abort_block_request(self): ) - async def ce128_processed_block_request(self, *args, **kwargs): + async def ce128_finish_block_request(self, *args, **kwargs): #TODO: add reason, origin/connection etc self.state_requesting_blocks = False - def up0_failure(self, reset_code: int, direction:StreamDirection): - logger.error(f"Failed with code {reset_code} ({direction}") - # TODO: handle UP0 reset, e.g., reconnect - - - def ce128_block_request_failure(self, reset_code: int): - logger.error(f"Failed with code {reset_code}") - self.state_requesting_blocks = False # Reset state on failure - - def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, msg: MsgCE131SafroleTicketDistribution): logger.info(f"Received ticket for epoch {msg.epoch_index}") @@ -367,35 +500,31 @@ def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, ms stream.send_reset(1) return - #TODO: create helper function from this (in app? verify_ticket) try: # Get ring public keys from current validators ring_public_keys = [v.bandersnatch for v in self.app.state.safrole.validators] - # Get entropy for the epoch entropy = self.app.state.entropy.entropy[2] - # Validate the ticket using the block extrinsic accumulator #TODO: duplicate with Safrole.create_ticket_body??? + ticket_envelope = TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof) ticket_body = self.app.block_extrinsic.create_ticket_body( - TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof), + ticket_envelope, ring_public_keys, entropy ) - + # Check if we already have this ticket if ticket_body in self.app.state.safrole.ticket_accumulator: logger.info(f"Ticket already in accumulator") stream.send_reset(2) return - + # Add ticket to our accumulator - #TODO: ticketbody can be reused from above asyncio.create_task( - self.app.block_extrinsic.add_ticket( - TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof), - ring_public_keys, - entropy + self.app.block_extrinsic.add_ticket_body( + ticket_envelope, + ticket_body ) ) @@ -426,16 +555,6 @@ def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, ms stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce131_ticket_distribution_success(self, reset_code: int): - logger.info(f"Finished with code {reset_code}") - # TODO: handle success, perhaps update state - - - def ce131_ticket_distribution_failure(self, reset_code: int): - logger.error(f"Failed with code {reset_code}") - # TODO: handle failure, e.g., retry or log - - def ce132_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE132SafroleTicketDistribution): stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep2, direction=StreamDirection.initiator) logger.info(f"Distribute ticket on stream id: {stream.stream_id} to {conn.host}:{conn.port}") @@ -456,35 +575,30 @@ def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, ms stream.send_reset(1) return - # Verify ticket proof - #TODO: duplicate code from CE131 for now, make helper function try: # Get ring public keys from current validators ring_public_keys = [v.bandersnatch for v in self.app.state.safrole.validators] - # Get entropy for the epoch entropy = self.app.state.entropy.entropy[2] - - # Validate the ticket + # TODO: duplicate with Safrole.create_ticket_body??? + ticket_envelope = TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof) ticket_body = self.app.block_extrinsic.create_ticket_body( - TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof), + ticket_envelope, ring_public_keys, entropy ) - + # Check if we already have this ticket if ticket_body in self.app.state.safrole.ticket_accumulator: logger.info(f"Ticket already in accumulator (via CE132), ignoring") - # Don't reset, just close normally as this is expected in CE132 stream.conn.send(stream.stream_id, b'', end_stream=True) return - + # Add ticket to our accumulator asyncio.create_task( - self.app.block_extrinsic.add_ticket( - TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof), - ring_public_keys, - entropy + self.app.block_extrinsic.add_ticket_body( + ticket_envelope, + ticket_body ) ) @@ -578,14 +692,6 @@ def ce134_received_refine_response(self, stream: StreamWorkPackageSharing, msg: stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce134_sharing_success(self, reset_code: int): - logger.info(f"Success with code {reset_code}") - - - def ce134_sharing_failure(self, reset_code: int): - logger.error(f"Failed with code {reset_code}") - - def ce135_initiate_distribution(self, conn: JAMConnection, msg: MsgCE135GuaranteedWorkReport): stream = conn.open_jam_stream(StreamWorkReportDistribution, direction=StreamDirection.initiator) logger.info(f"CE135 initiating distribution on stream id: {stream.stream_id}") @@ -599,14 +705,6 @@ def ce135_received_report(self, stream: StreamWorkReportDistribution, msg: MsgCE stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce135_distribution_success(self, reset_code: int): - logger.info(f"CE135 distribution successful with code {reset_code}") - - - def ce135_distribution_failure(self, reset_code: int): - logger.error(f"CE135 distribution failed with code {reset_code}") - - def ce136_initiate_request(self, conn: JAMConnection, msg: MsgCE136HashRequest): stream = conn.open_jam_stream(StreamWorkReportRequest, direction=StreamDirection.initiator) logger.info(f"CE136 initiating request on stream id: {stream.stream_id}") @@ -626,14 +724,6 @@ def ce136_received_report(self, stream: StreamWorkReportRequest, msg: MsgCE136Wo stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce136_request_success(self, reset_code: int): - logger.debug(f"CE136 request successful with code {reset_code}") - - - def ce136_request_failure(self, reset_code: int): - logger.error(f"CE136 request failed with code {reset_code}") - - def ce137_initiate_request(self, conn: JAMConnection, req: MsgCE137ShardRequest): stream = conn.open_jam_stream(StreamShardDistribution, direction=StreamDirection.initiator) conn.send(stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) @@ -655,14 +745,6 @@ def ce137_received_shard(self, stream: StreamShardDistribution, bundle, segments stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce137_distribution_success(self, code: int): - logger.info(f"CE137 success {code}") - - - def ce137_distribution_failure(self, code: int): - logger.error(f"CE137 failure {code}") - - # Similar for CE138, CE139, CE140 def ce138_initiate_request(self, conn: JAMConnection, req: MsgCE138ShardRequest): stream = conn.open_jam_stream(StreamAuditShardRequest, direction=StreamDirection.initiator) @@ -682,14 +764,6 @@ def ce138_received_shard(self, stream: StreamAuditShardRequest, bundle, just): stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce138_request_success(self, code: int): - logger.info(f"CE138 success {code}") - - - def ce138_request_failure(self, code: int): - logger.error(f"CE138 failure {code}") - - def ce139_initiate_request(self, conn: JAMConnection, req: MsgCE139SegmentRequest): stream = conn.open_jam_stream(StreamSegmentShardRequest, direction=StreamDirection.initiator) conn.send(stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) @@ -707,14 +781,6 @@ def ce139_received_shards(self, stream: StreamSegmentShardRequest, shards): stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce139_request_success(self, code: int): - logger.info(f"CE139 success {code}") - - - def ce139_request_failure(self, code: int): - logger.error(f"CE139 failure {code}") - - def ce140_initiate_request(self, conn: JAMConnection, req: MsgCE140SegmentRequest): stream = conn.open_jam_stream(StreamSegmentShardRequestJustification, direction=StreamDirection.initiator) conn.send(stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) @@ -734,14 +800,6 @@ def ce140_received_shards_justified(self, stream: StreamSegmentShardRequestJusti stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce140_request_success(self, code: int): - logger.info(f"CE140 success {code}") - - - def ce140_request_failure(self, code: int): - logger.error(f"CE140 failure {code}") - - def ce141_initiate_distribution(self, conn: JAMConnection, msg: MsgCE141Assurance): stream = conn.open_jam_stream(StreamAssuranceDistribution, direction=StreamDirection.initiator) logger.info(f"Initiating assurance distribution on stream id: {stream.stream_id} to {conn.host}:{conn.port}") @@ -755,14 +813,6 @@ def ce141_received_assurance(self, stream: StreamAssuranceDistribution, msg: Msg stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce141_distribution_success(self, reset_code: int): - logger.info(f"Success with code {reset_code}") - - - def ce141_distribution_failure(self, reset_code: int): - logger.error(f"Failed with code {reset_code}") - - def ce142_initiate_announcement(self, conn: JAMConnection, msg: MsgCE142PreimageAnnouncement): stream = conn.open_jam_stream(StreamPreimageAnnouncement, direction=StreamDirection.initiator) logger.info(f"Initiating preimage announcement on stream id: {stream.stream_id} to {conn.host}:{conn.port}") @@ -776,14 +826,6 @@ def ce142_received_announcement(self, stream: StreamPreimageAnnouncement, msg: M stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce142_announcement_success(self, reset_code: int): - logger.info(f"Success with code {reset_code}") - - - def ce142_announcement_failure(self, reset_code: int): - logger.error(f"Failed with code {reset_code}") - - def ce143_initiate_request(self, conn: JAMConnection, msg: MsgCE143HashRequest): stream = conn.open_jam_stream(StreamPreimageRequest, direction=StreamDirection.initiator) logger.info(f"CE143 initiating request on stream id: {stream.stream_id}") @@ -827,14 +869,6 @@ def ce144_received_evidence(self, stream: StreamAuditAnnouncement, msg: MsgCE144 stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce144_announcement_success(self, code: int): - logger.info(f"CE144 success {code}") - - - def ce144_announcement_failure(self, code: int): - logger.error(f"CE144 failure {code}") - - def ce145_initiate_publication(self, conn: JAMConnection, msg: MsgCE145JudgmentPublication): stream = conn.open_jam_stream(StreamJudgmentPublication, direction=StreamDirection.initiator) conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) @@ -845,14 +879,6 @@ def ce145_received_judgment(self, stream: StreamJudgmentPublication, msg: MsgCE1 stream.conn.send(stream.stream_id, b'', end_stream=True) - def ce145_publication_success(self, code: int): - logger.info(f"CE145 success {code}") - - - def ce145_publication_failure(self, code: int): - logger.error(f"CE145 failure {code}") - - # Note: pubsub_ prefixed methods are driven by application level events async def pubsub_up0_broadcast_block(self, block: Block): logger.info(f'Broadcast {block.header.hash.hex()} to {len(self.connections)} connections') @@ -921,8 +947,21 @@ def verify_bundle(self, msg): return True -def create_jam_connection(protocol: JAMNPS, quic_connection: JAMConnection, direction: StreamDirection, host: str, port: int): +def create_jam_connection( + protocol: JAMNPS, + quic_connection: JAMConnection, + direction: JAMConnectionDirection, + host: str, + port: int, + validator_key: Optional[bytes] +): def create_connection(*args, **kwargs): + """ + TODO: check ALPN + The (ASCII-encoded) protocol identifier should be either jamnp-s/V/H or jamnp-s/V/H/builder. Here V is the protocol version, 0, and H is the first 8 nibbles of the hash of the chain's genesis header, in lower-case hexadecimal. + The /builder suffix should always be permitted by the side accepting the connection, but only used by the side initiating the connection if it is connecting as a work-package builder. Note that guarantors should accept work-package submission streams (CE 133) on all connections, regardless of how they were opened. The purpose of identifying as a builder at connection time is merely to request use of a slot reserved for builders, increasing the likelihood of a successful connection. + Validators should accept connections from other nodes too, with a reasonable number of slots (e.g. 20) reserved for work-package builders. Builders may reasonably be required to prove their credentials through submission of a valid work-package in order to retain their connection. + """ conn = quic_connection(*args, **kwargs) conn.protocol = protocol conn.direction = direction @@ -931,12 +970,23 @@ def create_connection(*args, **kwargs): protocol.connections[conn.jam_connection_ulid] = conn # Note: for accepting connections addr & port are known after the QUIC handshake, see JAMConnection::HandshakeComplete - if direction == StreamDirection.initiator: + if direction == JAMConnectionDirection.initiator: conn.host = host conn.port = port protocol.conn_initiated.add(conn.jam_connection_ulid) else: protocol.conn_accepted.add(conn.jam_connection_ulid) + """ + TODO: if incomming connection is not a validator: + Validators should accept connections from other nodes too, with a reasonable number of slots (e.g. 20) reserved for work-package builders. Builders may reasonably be required to prove their credentials through submission of a valid work-package in order to retain their connection. + """ + + if validator_key: + if protocol.validator_connections[validator_key]["connection"]: + protocol.disconnect(protocol.validator_connections[validator_key]["connection"], validator_key) + + protocol.validator_connections[validator_key]["connection"] = conn + protocol.validator_connections[validator_key]["last_try"] = time.time() return conn diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py index 52e28abb..af0eff71 100644 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ b/pyjamaz/transport/jamnp_s/stream_base.py @@ -137,7 +137,9 @@ def handle_error(self, exc: Exception): code = self.ERROR_TOO_LARGE else: code = self.ERROR_GENERAL + logger.error(f"Stream {self.stream_id} error: {exc}") + self.send_reset(code) diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py index 741937e0..576e7a62 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_0.py @@ -54,11 +54,15 @@ def initiator_message(self, data: bytes): def initiator_reset(self, reset_code: int): - self.protocol.up0_failure(reset_code, self.direction) + # TODO: handle UP0 reset, e.g., reconnect + #self.protocol.up0_failure(reset_code, self.direction) + pass def acceptor_reset(self, reset_code: int): - self.protocol.up0_failure(reset_code, self.direction) + # TODO: handle UP0 reset, e.g., reconnect + #self.protocol.up0_failure(reset_code, self.direction) + pass def acceptor_message(self, data: bytes): diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py index 911fc192..16a6aa83 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_128.py @@ -17,8 +17,11 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE128 received reset code: {reset_code}") - self.protocol.ce128_block_request_failure(reset_code) + self.protocol.ce128_finish_block_request(reset_code) + + + def acceptor_reset(self, reset_code: int): + self.protocol.ce128_finish_block_request(reset_code) def initiator_message(self, data: bytes): @@ -32,11 +35,6 @@ def handle_fin(self): self.protocol.ce128_abort_block_request() # Or success callback - def acceptor_reset(self, reset_code: int): - logger.debug(f"CE128 received reset code: {reset_code}") - self.protocol.ce128_block_request_failure(reset_code) - - def acceptor_message(self, data: bytes): logger.debug(f"CE128 acceptor stream {self.stream_id} received block request") self.protocol.ce128_send_block_request(self, MsgCE128BlockRequest.from_jam_bytes(JamBytes(data))) diff --git a/pyjamaz/transport/jamnp_s/streams/stream_131.py b/pyjamaz/transport/jamnp_s/streams/stream_131.py index 4166acf1..3206d3d8 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_131.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_131.py @@ -17,13 +17,12 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE131 received reset code: {reset_code}") - self.protocol.ce131_ticket_distribution_failure(reset_code) + pass def initiator_message(self, data: bytes): logger.warning(f"Unexpected data in CE131 initiator: {len(data)} bytes") - self.handle_error("Unexpected data", 1) + self.handle_error(None) def acceptor_message(self, data: bytes): @@ -33,10 +32,9 @@ def acceptor_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - logger.debug(f"CE131 received reset code: {reset_code}") - self.protocol.ce131_ticket_distribution_failure(reset_code) + pass def handle_fin(self): super().handle_fin() - self.protocol.ce131_ticket_distribution_success(0) \ No newline at end of file + logger.info(f"Finished with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_134.py b/pyjamaz/transport/jamnp_s/streams/stream_134.py index 80fef174..12ccbc6f 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_134.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_134.py @@ -18,8 +18,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE134 received reset code: {reset_code}") - self.protocol.ce134_sharing_failure(reset_code) + pass def initiator_message(self, data: bytes): @@ -34,8 +33,7 @@ def initiator_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - logger.debug(f"CE134 received reset code: {reset_code}") - self.protocol.ce134_sharing_failure(reset_code) + pass def acceptor_message(self, data: bytes): @@ -52,4 +50,4 @@ def acceptor_message(self, data: bytes): def handle_fin(self): super().handle_fin() - self.protocol.ce134_sharing_success(0) \ No newline at end of file + logger.info(f"Success with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_135.py b/pyjamaz/transport/jamnp_s/streams/stream_135.py index 94e189a6..d01094c5 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_135.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_135.py @@ -17,8 +17,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE135 received reset code: {reset_code}") - self.protocol.ce135_distribution_failure(reset_code) + pass def initiator_message(self, data: bytes): @@ -27,8 +26,7 @@ def initiator_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - logger.debug(f"CE135 received reset code: {reset_code}") - self.protocol.ce135_distribution_failure(reset_code) + pass def acceptor_message(self, data: bytes): @@ -39,4 +37,4 @@ def acceptor_message(self, data: bytes): def handle_fin(self): super().handle_fin() - self.protocol.ce135_distribution_success(0) \ No newline at end of file + logger.info(f"CE135 distribution successful with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_136.py b/pyjamaz/transport/jamnp_s/streams/stream_136.py index 596a677d..ae1c8258 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_136.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_136.py @@ -17,8 +17,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE136 received reset code: {reset_code}") - self.protocol.ce136_request_failure(reset_code) + pass def initiator_message(self, data: bytes): @@ -34,10 +33,9 @@ def acceptor_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - logger.debug(f"CE136 received reset code: {reset_code}") - self.protocol.ce136_request_failure(reset_code) + pass def handle_fin(self): super().handle_fin() - self.protocol.ce136_request_success(0) \ No newline at end of file + logger.debug(f"CE136 request successful with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_137.py b/pyjamaz/transport/jamnp_s/streams/stream_137.py index d0bf1b46..934f95fb 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_137.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_137.py @@ -17,8 +17,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE137 received reset code: {reset_code}") - self.protocol.ce137_distribution_failure(reset_code) + pass def _chunks(self, data: bytes, chunk_size: int): @@ -43,10 +42,9 @@ def acceptor_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - logger.debug(f"CE137 received reset code: {reset_code}") - self.protocol.ce137_distribution_failure(reset_code) + pass def handle_fin(self): super().handle_fin() - self.protocol.ce137_distribution_success(0) \ No newline at end of file + logger.info(f"CE137 success with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_138.py b/pyjamaz/transport/jamnp_s/streams/stream_138.py index 3613332c..54f94bd8 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_138.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_138.py @@ -17,8 +17,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE138 received reset code: {reset_code}") - self.protocol.ce138_request_failure(reset_code) + pass def initiator_message(self, data: bytes): @@ -35,10 +34,9 @@ def acceptor_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - logger.debug(f"CE138 received reset code: {reset_code}") - self.protocol.ce138_request_failure(reset_code) + pass def handle_fin(self): super().handle_fin() - self.protocol.ce138_request_success(0) \ No newline at end of file + logger.info(f"CE138 success FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_139.py b/pyjamaz/transport/jamnp_s/streams/stream_139.py index f5c4a173..946b4990 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_139.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_139.py @@ -17,8 +17,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE139 received reset code: {reset_code}") - self.protocol.ce139_request_failure(reset_code) + pass def initiator_message(self, data: bytes): @@ -34,10 +33,9 @@ def acceptor_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - logger.debug(f"CE139 received reset code: {reset_code}") - self.protocol.ce139_request_failure(reset_code) + pass def handle_fin(self): super().handle_fin() - self.protocol.ce139_request_success(0) \ No newline at end of file + logger.info(f"CE139 success with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_140.py b/pyjamaz/transport/jamnp_s/streams/stream_140.py index d7b40283..61822ba6 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_140.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_140.py @@ -17,8 +17,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE140 received reset code: {reset_code}") - self.protocol.ce140_request_failure(reset_code) + pass def initiator_message(self, data: bytes): @@ -34,10 +33,9 @@ def acceptor_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - logger.debug(f"CE140 received reset code: {reset_code}") - self.protocol.ce140_request_failure(reset_code) + pass def handle_fin(self): super().handle_fin() - self.protocol.ce140_request_success(0) \ No newline at end of file + logger.info(f"CE140 success with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_141.py b/pyjamaz/transport/jamnp_s/streams/stream_141.py index 4a4c992d..5ae78b23 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_141.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_141.py @@ -17,8 +17,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE141 received reset code: {reset_code}") - self.protocol.ce141_distribution_failure(reset_code) + pass def initiator_message(self, data: bytes): @@ -33,10 +32,9 @@ def acceptor_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - logger.debug(f"CE141 received reset code: {reset_code}") - self.protocol.ce141_distribution_failure(reset_code) + pass def handle_fin(self): super().handle_fin() - self.protocol.ce141_distribution_success(0) \ No newline at end of file + logger.info(f"Success with code with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_142.py b/pyjamaz/transport/jamnp_s/streams/stream_142.py index 58a7d6f1..3b4524bc 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_142.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_142.py @@ -17,8 +17,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE142 received reset code: {reset_code}") - self.protocol.ce142_announcement_failure(reset_code) + pass def initiator_message(self, data: bytes): @@ -29,8 +28,7 @@ def initiator_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - logger.debug(f"CE142 received reset code: {reset_code}") - self.protocol.ce142_announcement_failure(reset_code) + pass def acceptor_message(self, data: bytes): @@ -41,4 +39,4 @@ def acceptor_message(self, data: bytes): def handle_fin(self): super().handle_fin() - self.protocol.ce142_announcement_success(0) \ No newline at end of file + logger.info(f"Success with code with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_144.py b/pyjamaz/transport/jamnp_s/streams/stream_144.py index 423117b2..dbcca941 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_144.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_144.py @@ -18,8 +18,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE144 received reset code: {reset_code}") - self.protocol.ce144_announcement_failure(reset_code) + pass def initiator_message(self, data: bytes): @@ -40,10 +39,9 @@ def acceptor_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - logger.debug(f"CE144 received reset code: {reset_code}") - self.protocol.ce144_announcement_failure(reset_code) + pass def handle_fin(self): super().handle_fin() - self.protocol.ce144_announcement_success(0) \ No newline at end of file + logger.info(f"CE144 success with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_145.py b/pyjamaz/transport/jamnp_s/streams/stream_145.py index 2567b2e0..2978bdfc 100644 --- a/pyjamaz/transport/jamnp_s/streams/stream_145.py +++ b/pyjamaz/transport/jamnp_s/streams/stream_145.py @@ -17,8 +17,7 @@ def __init__(self, stream_id: int, connection, direction: StreamDirection): def initiator_reset(self, reset_code: int): - logger.debug(f"CE145 received reset code: {reset_code}") - self.protocol.ce145_publication_failure(reset_code) + pass def initiator_message(self, data: bytes): @@ -33,10 +32,9 @@ def acceptor_message(self, data: bytes): def acceptor_reset(self, reset_code: int): - logger.debug(f"CE145 received reset code: {reset_code}") - self.protocol.ce145_publication_failure(reset_code) + pass def handle_fin(self): super().handle_fin() - self.protocol.ce145_publication_success(0) \ No newline at end of file + logger.info(f"CE145 success with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/rpc/ws_client.py b/pyjamaz/transport/rpc/ws_client.py index 8b1e978d..39353c07 100644 --- a/pyjamaz/transport/rpc/ws_client.py +++ b/pyjamaz/transport/rpc/ws_client.py @@ -125,7 +125,14 @@ async def bestBlock(self) -> Optional[dict]: res = await self._send_and_wait("bestBlock", None) if not res: return None - res[0] = bytes(res[0]) + res["header_hash"] = bytes(res["header_hash"]) + return res + + async def finalizedBlock(self) -> Optional[dict]: + res = await self._send_and_wait("finalizedBlock", None) + if not res: + return None + res["header_hash"] = bytes(res["header_hash"]) return res async def listServices(self) -> List[int]: From cc2ad1cc4f6314bb4a19940ae1cf0cb58c731f49 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Mon, 23 Mar 2026 15:31:06 +0100 Subject: [PATCH 42/51] - fixed merging issues --- pyjamaz/app.py | 2 +- pyjamaz/cli.py | 4 ++-- pyjamaz/extrinsic.py | 4 ++-- pyjamaz/transport/jamnp_s/message_types.py | 2 +- pyjamaz/transport/jamnp_s/protocol.py | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyjamaz/app.py b/pyjamaz/app.py index 30adc9ac..cc94baaf 100644 --- a/pyjamaz/app.py +++ b/pyjamaz/app.py @@ -732,7 +732,7 @@ async def store_block_header(self, header: Header): b'block_number:' + header.hash, header.timeslot.to_bytes(length=4, byteorder='little') ) self.block_db.put( - b'block_child:' + block.header.parent, block.header.hash + b'block_child:' + header.parent, header.hash ) def retrieve_block(self, timeslot: int) -> Optional[Block]: diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 3b87d808..7975c0ff 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -27,7 +27,7 @@ from pyjamaz.logger import setup_logging from pyjamaz.models.app import Trace, TraceGenesis from pyjamaz.models.state import STORAGE_KEY_MAPPING, ServiceAccount -from pyjamaz.rpc.ws_server import start_rpc_server, WebSocketServer +from pyjamaz.transport.rpc.ws_server import start_rpc_server, WebSocketServer from pyjamaz.settings import GP_VERSION, APP_VERSION, STORAGE_ENGINE, DEBUG from pyjamaz.storage import InMemoryStorageEngine, RocksDBStorageEngine from pyjamaz.models.block import Block, Header, Extrinsic @@ -411,7 +411,7 @@ async def timeslot_ticker(app: PyjamazApp): ) # Process tickets - app.block_extrinsic.process_epoch_change() + await app.block_extrinsic.process_epoch_change() DEBUG and logging.debug(f"Current tickets {[i.hex() for i in app.block_extrinsic.own_tickets_current]}") safrole_state = safrole_output.post_state diff --git a/pyjamaz/extrinsic.py b/pyjamaz/extrinsic.py index 79cba0dc..615b54b2 100644 --- a/pyjamaz/extrinsic.py +++ b/pyjamaz/extrinsic.py @@ -115,7 +115,7 @@ def is_own_ticket(self, ticket_id: bytes) -> bool: def own_ticket_count(self) -> int: return len(self.own_tickets_next) - def clear_own_tickets(self): + async def clear_own_tickets(self): async with self._ticket_queue_lock: for ticket_id in self.own_tickets_next: del self.tickets_queue[ticket_id] @@ -126,7 +126,7 @@ async def clear_tickets(self): self.tickets_queue = {} self.own_tickets_next = [] - def process_epoch_change(self): + async def process_epoch_change(self): self.own_tickets_current = self.own_tickets_next self.own_tickets_next = [] async with self._ticket_queue_lock: diff --git a/pyjamaz/transport/jamnp_s/message_types.py b/pyjamaz/transport/jamnp_s/message_types.py index f58aad5a..59012afb 100644 --- a/pyjamaz/transport/jamnp_s/message_types.py +++ b/pyjamaz/transport/jamnp_s/message_types.py @@ -8,7 +8,7 @@ from pyjamaz.graypaper_constants import VALIDATOR_COUNT from pyjamaz.models.block import Header, Guarantee, Block -from pyjamaz.models.common import WorkPackage, Authorizer, RefinementContext, WorkItem +from pyjamaz.models.common import WorkPackage, RefinementContext, WorkItem class ImplicitVec(Vec): diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index 09f7701c..c41ef996 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -4,7 +4,7 @@ import ssl import time -from typing import Dict, List +from typing import Dict, List, Tuple from typing import Optional, cast from aioquic.asyncio import serve @@ -163,7 +163,7 @@ def should_initiate_connection(self, validator_a: bytes, validator_b: bytes) -> def add_grid_connections( self, validator_idx: int, - validator_queue: List[int, ValidatorData], + validator_queue: List[Tuple[int, ValidatorData]], same_epoch: bool, initiate_conns:List[ValidatorData], expect_conns:List[ValidatorData] From 4baef400d1972cd10bef4eeff1ec7ac67de84d58 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Mon, 23 Mar 2026 16:50:51 +0100 Subject: [PATCH 43/51] - fixed merging issues --- pyjamaz/app.py | 10 +++---- pyjamaz/cli.py | 9 ++---- pyjamaz/transport/jamnp_s/connection.py | 2 +- pyjamaz/transport/jamnp_s/protocol.py | 40 +++++++++++++------------ scripts/init_nodes.sh | 3 ++ 5 files changed, 33 insertions(+), 31 deletions(-) create mode 100644 scripts/init_nodes.sh diff --git a/pyjamaz/app.py b/pyjamaz/app.py index cc94baaf..58143c0a 100644 --- a/pyjamaz/app.py +++ b/pyjamaz/app.py @@ -947,24 +947,24 @@ async def produce_block( ring_public_keys = [v.bandersnatch for v in safrole_state.validators] ring_context = RingContext(self.config.ring_data, ring_public_keys) - self.block_extrinsic.add_own_ticket( + await self.block_extrinsic.add_own_ticket( ring_context, entropy, self.config.keys.bandersnatch, self.get_author_index(), pubsub=self.pubsub ) - self.block_extrinsic.add_own_ticket( + await self.block_extrinsic.add_own_ticket( ring_context, entropy, self.config.keys.bandersnatch, self.get_author_index(), pubsub=self.pubsub ) - self.block_extrinsic.add_own_ticket( + await self.block_extrinsic.add_own_ticket( ring_context, entropy, self.config.keys.bandersnatch, self.get_author_index(), pubsub=self.pubsub ) extrinsic = Extrinsic( - tickets=self.block_extrinsic.collect_tickets(), + tickets=await self.block_extrinsic.collect_tickets(), disputes=ExtrinsicDisputes(verdicts=[], culprits=[], faults=[]), preimages=self.block_extrinsic.collect_preimages(self.working_state.services), assurances=self.block_extrinsic.collect_assurances(), - guarantees=self.block_extrinsic.collect_guarantees(), + guarantees=await self.block_extrinsic.collect_guarantees(), ) header = Header( diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 7975c0ff..7b062e13 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -1,5 +1,6 @@ import bisect import logging +import re import traceback from asyncio import CancelledError from datetime import datetime, timezone @@ -331,12 +332,8 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, ) logging.debug(f'Connecting to bootnode {conn["key"]} at {conn["addr"]}:{conn["port"]}') - # tg.start_soon(nps_protocol.connect, conn["addr"], conn["port"]) try: - # await nps_protocol.connect(conn["addr"], conn["port"]) - # await nps_protocol.connect("54.39.18.64", 40000) - await nps_protocol.connect("localhost", conn["port"]) - # await nps_protocol.connect("127.0.0.1", 40001) #!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + await nps_protocol.connect(conn["addr"], int(conn["port"]), None) except Exception as exc: traceback.print_exc() # else: @@ -450,7 +447,7 @@ async def timeslot_ticker(app: PyjamazApp): # Rollback state from DB app.working_state = app.retrieve_jam_state() # TODO Make transactional - app.block_extrinsic.clear_tickets() + await app.block_extrinsic.clear_tickets() else: logging.info(f'💤 Waiting for block #{timeslot} | epoch #{epoch} | phase #{phase}') diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 1156e65e..3c6837d1 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -32,7 +32,7 @@ def __init__(self, *args, **kwargs): self.stream_up = None self.streams = {} - #self._keepalive_task = asyncio.create_task(self._keepalive()) + self._keepalive_task = asyncio.create_task(self._keepalive()) def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_id: int=None): diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index c41ef996..c06bee4f 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -94,7 +94,7 @@ def __init__(self, host, port, certificate, private_key, app): self.validator_port = None self.validator_address = None - for validator in app.state.safrole.validators: + for validator in app.working_state.safrole.validators: if validator.ed25519 == self.app.config.keys.ed25519.public_key: self.validator = validator self.validator_dns = validator.get_connection_dns() @@ -146,7 +146,7 @@ async def listen(self): self.host, self.port, configuration=server_conf, - create_protocol=create_jam_connection(self, JAMConnection, JAMConnectionDirection.acceptor, self.host, self.port), + create_protocol=create_jam_connection(self, JAMConnection, JAMConnectionDirection.acceptor, self.host, self.port, None), #session_ticket_fetcher=self.session_ticket_store.pop, #session_ticket_handler=self.session_ticket_store.add, retry=True, @@ -202,9 +202,9 @@ async def update_validator_connections(self): if not self.validator: raise Exception("This node is not a validator") - prev_validators:List[int, ValidatorData] = [(i,v) for i,v in enumerate(self.app.state.validator_archive.validators)] - next_validators:List[int, ValidatorData] = [(i,v) for i,v in enumerate(self.app.state.validator_queue.validators)] - active_validators:List[int, ValidatorData] = [(i,v) for i,v in enumerate(self.app.state.safrole.validators)] + prev_validators:List[int, ValidatorData] = [(i,v) for i,v in enumerate(self.app.working_state.validator_archive.validators)] + next_validators:List[int, ValidatorData] = [(i,v) for i,v in enumerate(self.app.working_state.validator_queue.validators)] + active_validators:List[int, ValidatorData] = [(i,v) for i,v in enumerate(self.app.working_state.safrole.validators)] # Determine our index in the active validator queue validator_idx = None @@ -311,7 +311,7 @@ async def connect(self, host: str, port: int, validator_key: Optional[bytes]): logger.warning(f"💩 Cannot connect to {host}:{port} {exc}") - def disconnect(self, connection: JAMConnection, validator_key: Optional[bytes]): + def disconnect(self, connection: JAMConnection, validator_key: Optional[bytes] = None): if connection.direction == JAMConnectionDirection.initiator: if connection.jam_connection_ulid in self.conn_initiated: self.conn_initiated.remove(connection.jam_connection_ulid) @@ -325,13 +325,14 @@ def disconnect(self, connection: JAMConnection, validator_key: Optional[bytes]): if connection.addr and connection.addr in self.conn_addr: self.conn_addr.remove(connection.addr) + validator_key = validator_key or getattr(connection, 'validator_key', None) if validator_key in self.validator_connections: self.validator_connections[validator_key].connection = None def up0_send_handshake(self, conn: JAMConnection): # Create a presistent UP0 stream for this connection - slot = self.app.state.timeslot.number + slot = self.app.working_state.timeslot.number header_hash = self.app.retrieve_block_hash(slot) leafs = [] #TODO: For now, we only work with finalized blocks handshake = MsgUP0Handshake( @@ -360,7 +361,7 @@ def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): if not block: logger.info(f"Received newer block from handshake: {msg.header_hash} -> initiate CE128RequestBlocks") self.state_requesting_blocks = True - curr_hash = self.app.retrieve_block_hash(self.app.state.timeslot.number) + curr_hash = self.app.retrieve_block_hash(self.app.working_state.timeslot.number) # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? self.ce128_initiate_block_request( conn, @@ -391,7 +392,7 @@ def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement if not block: logger.info(f"Received new block announcement from up0: {msg.header.hash}") self.state_requesting_blocks = True - curr_hash = self.app.retrieve_block_hash(self.app.state.timeslot.number) + curr_hash = self.app.retrieve_block_hash(self.app.working_state.timeslot.number) # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? self.ce128_initiate_block_request( conn, @@ -431,7 +432,7 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 block:Block = None blocks:List[Block] = [] first_block_hash = bytes(32) - last_block_hash = self.app.retrieve_block_hash(self.app.state.timeslot.number) + last_block_hash = self.app.retrieve_block_hash(self.app.working_state.timeslot.number) #TODO: check <=0 -> bad request block_header:Header = self.app.retrieve_block_header(block_req.header_hash) next_hash = block_req.header_hash @@ -475,7 +476,7 @@ def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE1 def ce128_abort_block_request(self): - logger.info(f"Finished, start parsing import queue {len(self.app._import_queue)}") + logger.info(f"Finished, start parsing import queue {len(self.app.import_queue)}") # Note: could happen during a block_request "session" #TODO: or do we want to clear the queue? asyncio.create_task( @@ -494,7 +495,7 @@ async def ce128_finish_block_request(self, *args, **kwargs): def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, msg: MsgCE131SafroleTicketDistribution): logger.info(f"Received ticket for epoch {msg.epoch_index}") - current_epoch = self.app.state.timeslot.number // EPOCH_TIMESLOTS + current_epoch = self.app.working_state.timeslot.number // EPOCH_TIMESLOTS if msg.epoch_index < current_epoch: logger.warning(f"Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") stream.send_reset(1) @@ -502,9 +503,9 @@ def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, ms try: # Get ring public keys from current validators - ring_public_keys = [v.bandersnatch for v in self.app.state.safrole.validators] + ring_public_keys = [v.bandersnatch for v in self.app.working_state.safrole.validators] # Get entropy for the epoch - entropy = self.app.state.entropy.entropy[2] + entropy = self.app.working_state.entropy.entropy[2] # Validate the ticket using the block extrinsic accumulator #TODO: duplicate with Safrole.create_ticket_body??? ticket_envelope = TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof) @@ -515,7 +516,7 @@ def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, ms ) # Check if we already have this ticket - if ticket_body in self.app.state.safrole.ticket_accumulator: + if ticket_body in self.app.working_state.safrole.ticket_accumulator: logger.info(f"Ticket already in accumulator") stream.send_reset(2) return @@ -569,7 +570,7 @@ def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, ms logger.info(f"Received ticket for epoch {msg.epoch_index}") # Verify epoch index - current_epoch = self.app.state.timeslot.number // EPOCH_TIMESLOTS + current_epoch = self.app.working_state.timeslot.number // EPOCH_TIMESLOTS if msg.epoch_index < current_epoch: logger.warning(f"Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") stream.send_reset(1) @@ -577,9 +578,9 @@ def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, ms try: # Get ring public keys from current validators - ring_public_keys = [v.bandersnatch for v in self.app.state.safrole.validators] + ring_public_keys = [v.bandersnatch for v in self.app.working_state.safrole.validators] # Get entropy for the epoch - entropy = self.app.state.entropy.entropy[2] + entropy = self.app.working_state.entropy.entropy[2] # TODO: duplicate with Safrole.create_ticket_body??? ticket_envelope = TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof) ticket_body = self.app.block_extrinsic.create_ticket_body( @@ -589,7 +590,7 @@ def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, ms ) # Check if we already have this ticket - if ticket_body in self.app.state.safrole.ticket_accumulator: + if ticket_body in self.app.working_state.safrole.ticket_accumulator: logger.info(f"Ticket already in accumulator (via CE132), ignoring") stream.conn.send(stream.stream_id, b'', end_stream=True) return @@ -967,6 +968,7 @@ def create_connection(*args, **kwargs): conn.direction = direction conn.jam_connection_ulid = ULID() + conn.validator_key = validator_key protocol.connections[conn.jam_connection_ulid] = conn # Note: for accepting connections addr & port are known after the QUIC handshake, see JAMConnection::HandshakeComplete diff --git a/scripts/init_nodes.sh b/scripts/init_nodes.sh new file mode 100644 index 00000000..4a86fbd7 --- /dev/null +++ b/scripts/init_nodes.sh @@ -0,0 +1,3 @@ +python pyjamaz/cli.py init --db-path ./pyjamaz/data/alice --seed 0x0000000000000000000000000000000000000000000000000000000000000000 --force-overwrite +python pyjamaz/cli.py init --db-path ./pyjamaz/data/bob --seed 0x0000000000000000000000000000000000000000000000000000000000000001 --force-overwrite +python pyjamaz/cli.py init --db-path ./pyjamaz/data/john --seed 0x0000000000000000000000000000000000000000000000000000000000000002 --force-overwrite From c80ff93620f22e84a64ccba78455d495c72ed614 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Mon, 30 Mar 2026 13:48:37 +0200 Subject: [PATCH 44/51] - refactoring WIP --- pyjamaz/cli.py | 9 - pyjamaz/transport/jamnp_s/connection.py | 108 +- pyjamaz/transport/jamnp_s/network.py | 59 + pyjamaz/transport/jamnp_s/peers.py | 69 ++ pyjamaz/transport/jamnp_s/protocol.py | 1028 ++--------------- pyjamaz/transport/jamnp_s/stream_base.py | 172 --- pyjamaz/transport/jamnp_s/stream_manager.py | 227 ++++ pyjamaz/transport/jamnp_s/stream_map.py | 40 - pyjamaz/transport/jamnp_s/streams/__init__.py | 54 + pyjamaz/transport/jamnp_s/streams/base.py | 62 + pyjamaz/transport/jamnp_s/streams/ce128.py | 140 +++ pyjamaz/transport/jamnp_s/streams/ce129.py | 14 + pyjamaz/transport/jamnp_s/streams/ce131.py | 119 ++ pyjamaz/transport/jamnp_s/streams/ce132.py | 105 ++ pyjamaz/transport/jamnp_s/streams/ce133.py | 93 ++ pyjamaz/transport/jamnp_s/streams/ce134.py | 109 ++ pyjamaz/transport/jamnp_s/streams/ce135.py | 41 + pyjamaz/transport/jamnp_s/streams/ce136.py | 48 + pyjamaz/transport/jamnp_s/streams/ce137.py | 73 ++ pyjamaz/transport/jamnp_s/streams/ce138.py | 56 + pyjamaz/transport/jamnp_s/streams/ce139.py | 55 + pyjamaz/transport/jamnp_s/streams/ce140.py | 55 + pyjamaz/transport/jamnp_s/streams/ce141.py | 41 + pyjamaz/transport/jamnp_s/streams/ce142.py | 43 + pyjamaz/transport/jamnp_s/streams/ce143.py | 63 + pyjamaz/transport/jamnp_s/streams/ce144.py | 73 ++ pyjamaz/transport/jamnp_s/streams/ce145.py | 39 + pyjamaz/transport/jamnp_s/streams/context.py | 54 + pyjamaz/transport/jamnp_s/streams/stream_0.py | 76 -- .../transport/jamnp_s/streams/stream_128.py | 40 - .../transport/jamnp_s/streams/stream_129.py | 28 - .../transport/jamnp_s/streams/stream_131.py | 40 - .../transport/jamnp_s/streams/stream_132.py | 42 - .../transport/jamnp_s/streams/stream_133.py | 65 -- .../transport/jamnp_s/streams/stream_134.py | 53 - .../transport/jamnp_s/streams/stream_135.py | 40 - .../transport/jamnp_s/streams/stream_136.py | 41 - .../transport/jamnp_s/streams/stream_137.py | 50 - .../transport/jamnp_s/streams/stream_138.py | 42 - .../transport/jamnp_s/streams/stream_139.py | 41 - .../transport/jamnp_s/streams/stream_140.py | 41 - .../transport/jamnp_s/streams/stream_141.py | 40 - .../transport/jamnp_s/streams/stream_142.py | 42 - .../transport/jamnp_s/streams/stream_143.py | 43 - .../transport/jamnp_s/streams/stream_144.py | 47 - .../transport/jamnp_s/streams/stream_145.py | 40 - pyjamaz/transport/jamnp_s/streams/up0.py | 154 +++ pyjamaz/transport/jamnp_s/types.py | 115 ++ .../transport/jamnp_s/validator_manager.py | 233 ++++ 49 files changed, 2312 insertions(+), 2050 deletions(-) create mode 100644 pyjamaz/transport/jamnp_s/network.py create mode 100644 pyjamaz/transport/jamnp_s/peers.py delete mode 100644 pyjamaz/transport/jamnp_s/stream_base.py create mode 100644 pyjamaz/transport/jamnp_s/stream_manager.py delete mode 100644 pyjamaz/transport/jamnp_s/stream_map.py create mode 100644 pyjamaz/transport/jamnp_s/streams/base.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce128.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce129.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce131.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce132.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce133.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce134.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce135.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce136.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce137.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce138.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce139.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce140.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce141.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce142.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce143.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce144.py create mode 100644 pyjamaz/transport/jamnp_s/streams/ce145.py create mode 100644 pyjamaz/transport/jamnp_s/streams/context.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_0.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_128.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_129.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_131.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_132.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_133.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_134.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_135.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_136.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_137.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_138.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_139.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_140.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_141.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_142.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_143.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_144.py delete mode 100644 pyjamaz/transport/jamnp_s/streams/stream_145.py create mode 100644 pyjamaz/transport/jamnp_s/streams/up0.py create mode 100644 pyjamaz/transport/jamnp_s/types.py create mode 100644 pyjamaz/transport/jamnp_s/validator_manager.py diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 7b062e13..ec511e8f 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -123,13 +123,6 @@ async def cli_import_block(self, block: Block, dry_run=False): return cli_import_block -def wrap_produced_block_jamnp(app: PyjamazApp, traces_dir, np_protocol: JAMNPS): - async def produced_block_jamnp(block: Block): - await np_protocol.pubsub_up0_broadcast_block(block) - - return produced_block_jamnp - - async def initialize_app( read_state=True, storage_engine='memory', @@ -313,8 +306,6 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, #initial_block_hash = app.retrieve_block_hash(0).hex() nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app) app.protocol = nps_protocol - app.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, wrap_produced_block_jamnp(app, record_traces, nps_protocol)) #TODO: is this function wrapping still necesary? - app.pubsub.subscribe(MESSAGE_TYPES.TICKET_ADD, nps_protocol.pubsub_ce131_initiate_distribute_own_ticket) tg.start_soon(nps_protocol.listen) if bootnode: diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 3c6837d1..deee2a0d 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -2,12 +2,10 @@ import logging from enum import Enum -from aioquic.asyncio import QuicConnectionProtocol, serve +from aioquic.asyncio import QuicConnectionProtocol from aioquic.quic.events import QuicEvent, HandshakeCompleted, StreamReset, StreamDataReceived, ConnectionTerminated -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamDirection -from pyjamaz.transport.jamnp_s.stream_map import StreamLookup -from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP +from pyjamaz.transport.jamnp_s.types import StreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -25,6 +23,7 @@ def __init__(self, *args, **kwargs): # Note: should be set in wrap_protocol self.direction:JAMConnectionDirection = None self.protocol = None + self.stream_manager = None self.jam_connection_ulid = None self.host = None self.port = None @@ -35,34 +34,34 @@ def __init__(self, *args, **kwargs): self._keepalive_task = asyncio.create_task(self._keepalive()) - def open_jam_stream(self, StreamCls: Stream, direction:StreamDirection, stream_id: int=None): - if stream_id is None: - stream_id = self._quic.get_next_available_stream_id() - logger.debug(f"Connection {self.jam_connection_ulid} QUIC initiating stream {stream_id} for {direction}") - else: - logger.debug(f"Connection {self.jam_connection_ulid} QUIC accepting stream {stream_id} for {direction}") - - self.streams[stream_id] = StreamCls(stream_id, connection=self, direction=direction) - return self.streams[stream_id] + def close_stream(self, stream_id: int, clean_close: bool = True, reason: int = 0): + logger.debug( + f"Connection {self.jam_connection_ulid} QUIC closing stream {stream_id} (clean: {clean_close})" + ) + if clean_close: + self.send(stream_id, b"", end_stream=True) + return - def close_jam_stream(self, stream: Stream, reason:int=0, clean_close: bool = False): - logger.debug(f"Connection {self.jam_connection_ulid} QUIC closing JAM stream {stream.stream_type} {stream.stream_id} for {stream.direction} (clean: {clean_close})") + self.reset_stream(stream_id, reason) - if clean_close: - self.send(stream.stream_id, b'', end_stream=True) - else: - self._quic.reset_stream(stream.stream_id, error_code=reason) - if stream.stream_id in self.streams: - del self.streams[stream.stream_id] + def reset_stream(self, stream_id: int, error_code: int): + logger.debug( + f"Connection {self.jam_connection_ulid} QUIC resetting stream {stream_id} with code {error_code}" + ) + self._quic.reset_stream(stream_id, error_code=error_code) + self.transmit() def is_connected(self) -> bool: """Check if the connection is active and ready""" - return (self._quic is not None and - not self._quic._close_pending and - self.stream_up is not None) + return ( + self._quic is not None + and not self._quic._close_pending + and self.stream_up is not None + and not self.stream_up.closed + ) def send(self, stream_id: int, data: bytes, end_stream=False): @@ -89,7 +88,9 @@ def send(self, stream_id: int, data: bytes, end_stream=False): def connection_lost(self, exc): logger.debug(f"Connection {self.jam_connection_ulid} QUIC UDP transport closed") - self.protocol.disconnect(self) + self._keepalive_task.cancel() + if self.protocol is not None: + self.protocol.disconnect(self) super().connection_lost(exc) @@ -104,19 +105,6 @@ async def _keepalive(self): pass - def add_connection(self, addr: str, direction: JAMConnectionDirection) -> bool: - if addr in self.protocol.conn_addr: - logger.warning(f"Connection {self.jam_connection_ulid} direction: {direction} is a duplicate {addr}") - self.close(error_code=2, reason_phrase="duplicate") - self.protocol.disconnect(self) - return False - else: - self.addr = addr - self.protocol.conn_addr.add(self.addr) - logger.debug(f'Connection {self.jam_connection_ulid} direction: {direction} added {self.addr}') - return True - - def quic_event_received(self, event: QuicEvent) -> None: if event and hasattr(event, "stream_id"): if event.stream_id != 0: @@ -131,61 +119,33 @@ def quic_event_received(self, event: QuicEvent) -> None: if self.direction == JAMConnectionDirection.initiator: addr = f"{self.host}:{self.port}" - if not self.add_connection(addr, self.direction): + if not self.protocol.peer_registry.activate(self, addr): return # Initiating side will send a JAM handshake message and set the stream id - stream_up = self.open_jam_stream(StreamUP, direction=StreamDirection.initiator) + stream_up = self.stream_manager.open_outgoing(self, StreamKind.UP0_BlockAnnouncement) self.stream_up = stream_up - self.protocol.up0_send_handshake(self) + self.protocol.handler(StreamKind.UP0_BlockAnnouncement).send_handshake(self) else: # Note: it seems only now we have this info available from the accepting side - self.host, self.port = self._quic._network_paths[0].addr + self.host, self.port = self._quic._network_paths[0].addr[0:2] addr = f"{self.host}:{self.port}" - if not self.add_connection(addr, self.direction): + if not self.protocol.peer_registry.activate(self, addr): return elif isinstance(event, StreamReset): - stream_id = event.stream_id - reset_code = event.error_code - - if stream_id not in self.streams: - #raise Exception(f"QUIC stream {stream_id} not available") - logger.debug(f"Connection {self.jam_connection_ulid} QUIC stream {stream_id} already closed") - return - - logger.info(f'Connection {self.jam_connection_ulid} QUIC StreamReset {type(self.streams[stream_id])} ({stream_id}) direction={self.direction} code {reset_code}') - - self.streams[stream_id].receive_reset(reset_code) + self.stream_manager.receive_reset(self, event.stream_id, event.error_code) elif isinstance(event, StreamDataReceived): - stream_id = event.stream_id data = bytes(event.data) - + logger.debug(f"Connection {self.jam_connection_ulid} StreamDataReceived: stream_id={stream_id}, data_len={len(data)}, end_stream={event.end_stream}") try: - if stream_id not in self.streams: - # Remote side initiated a new stream – first byte is stream type - stream_type = int(data[0]) - stream_cls = StreamLookup.get(stream_type) - if stream_cls is None: - raise Exception(f"QUIC stream {stream_id} is not mapped (type {stream_type})") - - stream_obj = self.open_jam_stream(stream_cls, direction=StreamDirection.acceptor, stream_id=stream_id) - - if stream_cls == StreamUP and (self.stream_up is None or self.stream_up.stream_id != stream_id): - self.stream_up = stream_obj - - # Strip stream type byte for further processing - data = data[1:] - - self.streams[stream_id].receive_data(data, event.end_stream) + self.stream_manager.receive_stream_data(self, stream_id, data, event.end_stream) except Exception as e: logger.error(f"Connection {self.jam_connection_ulid} Error handling stream data: {e}", exc_info=True) - if stream_id and self.streams[stream_id]: - self.streams[stream_id].handle_error() raise elif isinstance(event, ConnectionTerminated): diff --git a/pyjamaz/transport/jamnp_s/network.py b/pyjamaz/transport/jamnp_s/network.py new file mode 100644 index 00000000..ece81481 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/network.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +import ssl +from typing import Optional, TYPE_CHECKING + +from aioquic.quic.configuration import QuicConfiguration +from ulid import ULID + +from pyjamaz.transport.jamnp_s.connection import JAMConnection, JAMConnectionDirection + +if TYPE_CHECKING: + from pyjamaz.transport.jamnp_s.protocol import JAMNPS + + +def build_client_configuration(protocol_name: str, certificate: str, private_key: str) -> QuicConfiguration: + configuration = QuicConfiguration( + alpn_protocols=[protocol_name], + is_client=True, + verify_mode=ssl.CERT_NONE, + ) + configuration.load_cert_chain(certfile=certificate, keyfile=private_key) + return configuration + + +def build_server_configuration(protocol_name: str, certificate: str, private_key: str) -> QuicConfiguration: + configuration = QuicConfiguration( + alpn_protocols=[protocol_name], + is_client=False, + ) + configuration.load_cert_chain(certificate, private_key) + return configuration + + +def create_connection_factory( + protocol: "JAMNPS", + direction: JAMConnectionDirection, + host: str, + port: int, + validator_key: Optional[bytes], +): + def create_connection(*args, **kwargs): + conn = JAMConnection(*args, **kwargs) + conn.protocol = protocol + conn.direction = direction + conn.stream_manager = protocol.stream_manager + conn.jam_connection_ulid = ULID() + conn.validator_key = validator_key + + if direction == JAMConnectionDirection.initiator: + conn.host = host + conn.port = port + + protocol.peer_registry.register(conn) + if validator_key is not None: + protocol.validator_manager.bind_connection(validator_key, conn) + + return conn + + return create_connection diff --git a/pyjamaz/transport/jamnp_s/peers.py b/pyjamaz/transport/jamnp_s/peers.py new file mode 100644 index 00000000..47335ecb --- /dev/null +++ b/pyjamaz/transport/jamnp_s/peers.py @@ -0,0 +1,69 @@ +from __future__ import annotations + +import logging +from typing import TYPE_CHECKING + +from pyjamaz.transport.jamnp_s.connection import JAMConnectionDirection + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + +if TYPE_CHECKING: + from pyjamaz.transport.jamnp_s.connection import JAMConnection + from pyjamaz.transport.jamnp_s.protocol import JAMNPS + + +class PeerRegistry: + def __init__(self, protocol: "JAMNPS") -> None: + self.protocol = protocol + self.connections = {} + self.conn_accepted = set() + self.conn_initiated = set() + self.conn_addr = set() + + def register(self, connection: "JAMConnection") -> None: + self.connections[connection.jam_connection_ulid] = connection + if connection.direction == JAMConnectionDirection.initiator: + self.conn_initiated.add(connection.jam_connection_ulid) + else: + self.conn_accepted.add(connection.jam_connection_ulid) + + def activate(self, connection: "JAMConnection", addr: str) -> bool: + if addr in self.conn_addr and connection.addr != addr: + logger.warning( + f"Connection {connection.jam_connection_ulid} direction: {connection.direction} is a duplicate {addr}" + ) + connection.close(error_code=2, reason_phrase="duplicate") + self.unregister(connection) + return False + + connection.addr = addr + self.conn_addr.add(addr) + + if ( + connection.direction == JAMConnectionDirection.initiator + and self.protocol.conn_out is None + ): + self.protocol.conn_out = connection + + logger.debug( + f"Connection {connection.jam_connection_ulid} direction: {connection.direction} added {connection.addr}" + ) + return True + + def unregister(self, connection: "JAMConnection") -> None: + self.conn_initiated.discard(connection.jam_connection_ulid) + self.conn_accepted.discard(connection.jam_connection_ulid) + self.connections.pop(connection.jam_connection_ulid, None) + + if connection.addr: + self.conn_addr.discard(connection.addr) + + if self.protocol.conn_out is connection: + self.protocol.conn_out = next( + ( + conn + for conn in self.connections.values() + if conn.direction == JAMConnectionDirection.initiator and conn.addr is not None + ), + None, + ) diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py index c06bee4f..23ac2bcc 100644 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ b/pyjamaz/transport/jamnp_s/protocol.py @@ -1,308 +1,139 @@ -import asyncio -import logging -import math -import ssl -import time +from __future__ import annotations -from typing import Dict, List, Tuple +import logging from typing import Optional, cast from aioquic.asyncio import serve -from aioquic.quic.configuration import QuicConfiguration -from aioquic.quic.connection import QuicConnectionState -from aioquic.tls import SessionTicket - from aioquic.asyncio.client import connect -from ulid import ULID from pyjamaz.constants import MESSAGE_TYPES -from pyjamaz.models.block import Block, Header, TicketEnvelope -from pyjamaz.graypaper_constants import EPOCH_TIMESLOTS -from pyjamaz.transport.jamnp_s.stream_base import StreamDirection -from pyjamaz.transport.jamnp_s.streams.stream_138 import StreamAuditShardRequest -from pyjamaz.transport.jamnp_s.streams.stream_139 import StreamSegmentShardRequest - -from pyjamaz.transport.types import ProtocolType -from pyjamaz.transport.jamnp_s.message_types import MsgCE128BlockRequestDirection, MsgCE128BlockRequest, \ - MsgUP0Handshake, MsgUP0Announcement, MsgCE128BlockRequestResponse, \ - MsgCE131SafroleTicketDistribution, MsgCE132SafroleTicketDistribution, \ - MsgCE134WorkPackageSharing, MsgCE134WorkPackageBundle, MsgCE134RefineResponse, \ - MsgCE141Assurance, MsgCE142PreimageAnnouncement, MsgCE143HashRequest, MsgCE143Preimage, \ - MsgCE133WorkPackageSubmission, MsgCE133Extrinsic, MsgCE135GuaranteedWorkReport, \ - MsgCE136HashRequest, MsgCE136WorkReport, \ - MsgCE137ShardRequest, MsgCE137BundleShard, MsgCE137SegmentShard, MsgCE137Justification, \ - MsgCE138ShardRequest, MsgCE138BundleShard, MsgCE138Justification, \ - MsgCE139SegmentRequest, MsgCE139SegmentShard, \ - MsgCE140SegmentRequest, MsgCE140SegmentShard, MsgCE140Justification, \ - MsgCE144Announcement, MsgCE144Evidence, \ - MsgCE145JudgmentPublication, MsgCE131SafroleTicket, MsgCE132SafroleTicket -from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest -from pyjamaz.transport.jamnp_s.streams.stream_131 import StreamSafroleTicketDistributionStep1 -from pyjamaz.transport.jamnp_s.streams.stream_132 import StreamSafroleTicketDistributionStep2 -from pyjamaz.transport.jamnp_s.streams.stream_134 import StreamWorkPackageSharing -from pyjamaz.transport.jamnp_s.streams.stream_141 import StreamAssuranceDistribution -from pyjamaz.transport.jamnp_s.streams.stream_142 import StreamPreimageAnnouncement -from pyjamaz.transport.jamnp_s.streams.stream_143 import StreamPreimageRequest -from pyjamaz.transport.jamnp_s.streams.stream_133 import StreamWorkPackageSubmission -from pyjamaz.transport.jamnp_s.streams.stream_135 import StreamWorkReportDistribution -from pyjamaz.transport.jamnp_s.streams.stream_136 import StreamWorkReportRequest -from pyjamaz.transport.jamnp_s.streams.stream_137 import StreamShardDistribution -from pyjamaz.transport.jamnp_s.streams.stream_140 import StreamSegmentShardRequestJustification -from pyjamaz.transport.jamnp_s.streams.stream_144 import StreamAuditAnnouncement -from pyjamaz.transport.jamnp_s.streams.stream_145 import StreamJudgmentPublication - from pyjamaz.transport.jamnp_s.connection import JAMConnection, JAMConnectionDirection - -from pyjamaz.models.common import WorkPackage, ValidatorData +from pyjamaz.transport.jamnp_s.streams import ProtocolContext, ProtocolSharedState, register_handlers +from pyjamaz.transport.jamnp_s.network import ( + build_client_configuration, + build_server_configuration, + create_connection_factory, +) +from pyjamaz.transport.jamnp_s.peers import PeerRegistry +from pyjamaz.transport.jamnp_s.stream_manager import StreamManager +from pyjamaz.transport.jamnp_s.types import StreamKind +from pyjamaz.transport.jamnp_s.validator_manager import ValidatorConnectionManager +from pyjamaz.transport.types import ProtocolType logger = logging.getLogger("pyjamaz.transport.jamnp_s") - -class ValidatorConnection: - validator: ValidatorData - ip: str - port: int - connection:JAMConnection - last_try:time - initiator:bool - in_grid:bool - - class JAMNPS(ProtocolType): - MAX_MESSAGE_SIZE = 10000000 PROTOCOL_NAME = "jamnp-s/0/{}" - - #TODO: add role enum: validator, guarantors, light_client, builder, ... def __init__(self, host, port, certificate, private_key, app): self.host = host self.port = port self.pubsub = app.pubsub self.app = app - - self.state_requesting_blocks = False - self.state_warp_sync = True #TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + self.cert = certificate + self.pk = private_key bl_hash = app.retrieve_block_hash(0).hex() - if bl_hash.startswith('0x'): bl_hash = bl_hash[2:] + if bl_hash.startswith("0x"): + bl_hash = bl_hash[2:] bl_hash = bl_hash[:8] - self.validator = None - self.validator_dns = None - self.validator_port = None - self.validator_address = None + self.protocol_name = JAMNPS.PROTOCOL_NAME.format(bl_hash) + self.configuration = build_client_configuration(self.protocol_name, certificate, private_key) + self.conn_out = None + + self.peer_registry = PeerRegistry(self) + self.stream_manager = StreamManager(self.MAX_MESSAGE_SIZE) + self.validator_manager = ValidatorConnectionManager( + app=self.app, + connect_callback=self.connect, + disconnect_callback=self.disconnect, + ) + self.context = ProtocolContext( + app=self.app, + peer_registry=self.peer_registry, + validator_manager=self.validator_manager, + stream_manager=self.stream_manager, + state=ProtocolSharedState(), + ) + self.protocol_handlers = register_handlers(self.stream_manager, self.context) - for validator in app.working_state.safrole.validators: - if validator.ed25519 == self.app.config.keys.ed25519.public_key: - self.validator = validator - self.validator_dns = validator.get_connection_dns() - self.validator_port = validator.get_metadata_port() - self.validator_address = validator.get_metadata_ipaddress() + # Hook up signals that require protocol handling + up0_handler = self.handler(StreamKind.UP0_BlockAnnouncement) + ce128_handler = self.handler(StreamKind.CE128_BlockRequest) + ce131_handler = self.handler(StreamKind.CE131_SafroleTicketDistributionStep1) - #TODO: - # callbacks like ce128_finish_block_request could be created using closure functions - # (def create_ce128_finish_block_request returning the callback), - # to create a local state which holds context like which connection/node initiated this event, so we can mark - # malicious nodes etc... - self.pubsub.subscribe(MESSAGE_TYPES.CE128_SUCCESS, self.ce128_finish_block_request) - self.pubsub.subscribe(MESSAGE_TYPES.CE128_FAILURE, self.ce128_finish_block_request) + self.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, up0_handler.broadcast_block) + self.pubsub.subscribe(MESSAGE_TYPES.CE128_SUCCESS, ce128_handler.finish_block_request) + self.pubsub.subscribe(MESSAGE_TYPES.CE128_FAILURE, ce128_handler.finish_block_request) + self.pubsub.subscribe(MESSAGE_TYPES.TICKET_ADD, ce131_handler.broadcast_own_ticket) - self.protocol_name = JAMNPS.PROTOCOL_NAME.format(bl_hash) - self.session_ticket_store = SessionTicketStore() - self.configuration = QuicConfiguration( - alpn_protocols=[self.protocol_name], - is_client=True, - #quic_logger=quic_logger, - #verify_mode=ssl.CERT_REQUIRED, - verify_mode=ssl.CERT_NONE, - #idle_timeout=300000 - ) - self.cert = certificate - self.pk = private_key - self.configuration.load_cert_chain(certificate, private_key) + @property + def validator(self): + return self.validator_manager.validator - self.connections = {} # All connections - self.conn_accepted = set() # All incomming connections - self.conn_initiated = set() # All outgoing connections (who we connect to) - self.conn_addr = set() # Reference with host:port pairs - self.validator_connections: Dict[bytes:, ValidatorConnection] = {} - #TODO: add a reconnect task - # self._reconnect_task = asyncio.create_task(self.reconnect_validators()) + @property + def validator_dns(self): + return self.validator_manager.validator_dns + @property + def validator_port(self): + return self.validator_manager.validator_port - async def listen(self): - logger.info(f'Listening on {self.host}:{self.port}') + @property + def validator_address(self): + return self.validator_manager.validator_address - server_conf = QuicConfiguration( - alpn_protocols=[self.protocol_name], - is_client=False, - #verify_mode=ssl.CERT_NONE, - ) - server_conf.load_cert_chain(self.cert, self.pk) + def handler(self, kind: StreamKind): + return self.protocol_handlers[kind] + async def listen(self): + logger.info(f"Listening on {self.host}:{self.port}") + + server_conf = build_server_configuration(self.protocol_name, self.cert, self.pk) await serve( self.host, self.port, configuration=server_conf, - create_protocol=create_jam_connection(self, JAMConnection, JAMConnectionDirection.acceptor, self.host, self.port, None), - #session_ticket_fetcher=self.session_ticket_store.pop, - #session_ticket_handler=self.session_ticket_store.add, + create_protocol=create_connection_factory( + self, + JAMConnectionDirection.acceptor, + self.host, + self.port, + None, + ), retry=True, ) - def should_initiate_connection(self, validator_a: bytes, validator_b: bytes) -> bool: - connect_a = 1 if validator_a[31] > 127 else 0 - connect_b = 1 if validator_b[31] > 127 else 0 - a_less = 1 if validator_a < validator_b else 0 - return (connect_a ^ connect_b ^ a_less) == 1 - - - def add_grid_connections( - self, - validator_idx: int, - validator_queue: List[Tuple[int, ValidatorData]], - same_epoch: bool, - initiate_conns:List[ValidatorData], - expect_conns:List[ValidatorData] - ): - - W = math.floor(math.sqrt(len(validator_queue))) - - for (v_idx, v) in validator_queue: - if self.validator.ed25519 == v.ed25519: - continue - - should_connect = False - - if same_epoch: - if v_idx == validator_idx // W or validator_idx % W == v_idx % W: - should_connect = True - else: - if v_idx == validator_idx: - should_connect = True - - if should_connect: - if self.should_initiate_connection(self.validator.ed25519, v.ed25519): - initiate_conns.append(v) - else: - expect_conns.append(v) - + return self.validator_manager.should_initiate_connection(validator_a, validator_b) async def update_validator_connections(self): - """ - TODO: - listen to epoch transitions and : - The first block in the epoch has been finalized. - At least max(⌊E/30⌋,1) slots have elapsed since the beginning of the epoch (where E is the number of slots in an epoch). - """ - - if not self.validator: - raise Exception("This node is not a validator") - - prev_validators:List[int, ValidatorData] = [(i,v) for i,v in enumerate(self.app.working_state.validator_archive.validators)] - next_validators:List[int, ValidatorData] = [(i,v) for i,v in enumerate(self.app.working_state.validator_queue.validators)] - active_validators:List[int, ValidatorData] = [(i,v) for i,v in enumerate(self.app.working_state.safrole.validators)] - - # Determine our index in the active validator queue - validator_idx = None - for v_idx, v in enumerate(active_validators): - if v.ed25519 == self.validator.ed25519: - validator_idx = v_idx - - if validator_idx is None: - logger.debug(f'Current validator {v.ed25519.hex()} is not present in the validator queue') - return - - #Note: Primarily for the purpose of block and preimage announcements - initiate_grid_connections:List[ValidatorData] = [] - expected_grid_connections:List[ValidatorData] = [] - self.add_grid_connections(validator_idx=validator_idx, validator_queue=prev_validators, same_epoch=False, initiate_conns=initiate_grid_connections, expect_conns=expected_grid_connections) - self.add_grid_connections(validator_idx=validator_idx, validator_queue=active_validators, same_epoch=True, initiate_conns=initiate_grid_connections, expect_conns=expected_grid_connections) - self.add_grid_connections(validator_idx=validator_idx, validator_queue=next_validators, same_epoch=False, initiate_conns=initiate_grid_connections, expect_conns=expected_grid_connections) - initiate_grid_connections = set(initiate_grid_connections) - expected_grid_connections = set(expected_grid_connections) - - # Note: All validators in the previous, current, and next epochs should ensure they are connected to all other such validators. - initiate_connections:List[ValidatorData] = [] - expected_connections:List[ValidatorData] = [] - for v in active_validators: - if v.ed25519 == self.validator.ed25519: - continue - - if self.should_initiate_connection(self.validator.ed25519, v.ed25519): - initiate_connections.append(v) - else: - expected_connections.append(v) - - print("CONNECT TO VALIDATORS: ", initiate_connections) - print("EXPECTING CONNECTIONS: ", expected_connections) - - initiate_connections = set(initiate_connections) - expected_connections = set(expected_connections) - - new_connections = initiate_connections | expected_connections | initiate_grid_connections | expected_grid_connections - current_connections = set(self.validator_connections.keys()) - - # Check if our set changed and need to disconnect irrelevant connections - disconnect_validators = current_connections - new_connections - for ed25519 in disconnect_validators: - if ed25519 in self.validator_connections: - self.disconnect(self.validator_connections[ed25519].connection) - - for v in list(new_connections): - is_initiator:bool = v.ed25519 in initiate_connections or v.ed25519 in initiate_grid_connections - in_grid:bool = v.ed25519 in initiate_grid_connections or v.ed25519 in expected_grid_connections - - if v.ed25519 not in self.validator_connections: - ip, port = (v.get_metadata_ipaddress(), v.get_metadata_port()) - self.validator_connections[v.ed25519] = ValidatorConnection( - validator=v, - ip=ip, - port=port, - connection=None, - last_try=None, - initiator=is_initiator, - in_grid=in_grid - ) - # Note: - #await self.connect(ip, port) - - # Note: Some properties might have changed for existing connections - vc:ValidatorConnection = self.validator_connections[v.ed25519] - vc.initiator = is_initiator - vc.in_grid = in_grid - - # Start an active connection - if vc.in_grid and vc.initiator: - if vc.connection is None or vc.connection._quic._state > QuicConnectionState.CONNECTED: - await self.connect(ip, port, v.ed25519) - + await self.validator_manager.update_connections() async def connect(self, host: str, port: int, validator_key: Optional[bytes]): - configuration = QuicConfiguration( - alpn_protocols=[self.protocol_name], - is_client=True, - #verify_mode=ssl.CERT_REQUIRED, - verify_mode=ssl.CERT_NONE, - ) - configuration.load_cert_chain(certfile=self.cert, keyfile=self.pk) + configuration = build_client_configuration(self.protocol_name, self.cert, self.pk) addr = f"{host}:{port}" - if addr in self.conn_addr: + if addr in self.peer_registry.conn_addr: logger.warning(f"Ignoring duplicate connection to {addr}") return logger.info(f"Connecting to {host}:{port}") try: async with connect( + host, + port, + configuration=configuration, + create_protocol=create_connection_factory( + self, + JAMConnectionDirection.initiator, host, port, - configuration=configuration, - # session_ticket_handler=save_session_ticket, - create_protocol=create_jam_connection(self, JAMConnection, JAMConnectionDirection.initiator, host, port, validator_key), + validator_key, + ), ) as client: client = cast(JAMConnection, client) await client.wait_closed() @@ -310,698 +141,7 @@ async def connect(self, host: str, port: int, validator_key: Optional[bytes]): except ConnectionError as exc: logger.warning(f"💩 Cannot connect to {host}:{port} {exc}") - def disconnect(self, connection: JAMConnection, validator_key: Optional[bytes] = None): - if connection.direction == JAMConnectionDirection.initiator: - if connection.jam_connection_ulid in self.conn_initiated: - self.conn_initiated.remove(connection.jam_connection_ulid) - else: - if connection.jam_connection_ulid in self.conn_accepted: - self.conn_accepted.remove(connection.jam_connection_ulid) - - if connection.jam_connection_ulid in self.connections: - del self.connections[connection.jam_connection_ulid] - - if connection.addr and connection.addr in self.conn_addr: - self.conn_addr.remove(connection.addr) - - validator_key = validator_key or getattr(connection, 'validator_key', None) - if validator_key in self.validator_connections: - self.validator_connections[validator_key].connection = None - - - def up0_send_handshake(self, conn: JAMConnection): - # Create a presistent UP0 stream for this connection - slot = self.app.working_state.timeslot.number - header_hash = self.app.retrieve_block_hash(slot) - leafs = [] #TODO: For now, we only work with finalized blocks - handshake = MsgUP0Handshake( - header_hash=header_hash, - timeslot=slot, - leafs=leafs - ) - logger.info(f"Send handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}") - - add_stream_type = conn.direction == JAMConnectionDirection.initiator - - conn.send( - conn.stream_up.stream_id, - conn.stream_up.create_message(handshake.to_jam_bytes().to_bytes(), add_stream_type=add_stream_type), - ) - - - def up0_received_handshake(self, conn: JAMConnection, msg: MsgUP0Handshake): - # Note: For now we employ a very simple strategy, where we sync blocks from the first node that announced a finalized block greater than we have - if self.state_requesting_blocks: - logger.debug(f"Skipping handshake block header check, already importing blocks") - return - - block = self.app.retrieve_block_by_hash(msg.header_hash) #TODO: missch een efficientere exists check toevoegen - #TODO: ook nog iets doen met slot en leafs ook mee requesten? - if not block: - logger.info(f"Received newer block from handshake: {msg.header_hash} -> initiate CE128RequestBlocks") - self.state_requesting_blocks = True - curr_hash = self.app.retrieve_block_hash(self.app.working_state.timeslot.number) - # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? - self.ce128_initiate_block_request( - conn, - MsgCE128BlockRequest( - curr_hash, #msg.header_hash, - MsgCE128BlockRequestDirection.ASC.value, #MsgCE128BlockRequestDirection.DESC.value, - 10 - ) - ) - - - def up0_received_announcement(self, conn: JAMConnection, msg: MsgUP0Announcement): - # Note: For now we employ a very simple strategy, where we sync blocks from the first node that announced a finalized block greater than we have - if self.state_requesting_blocks: - logger.debug(f"Skipping block header announcement check, already importing blocks") - return - - """ - TODO: send to other nodes (other than this connection) - Except when: - A descendant of the block is announced instead. - The block is not a descendant of the latest finalized block. - The block, or a descendant of the block, has been announced by the other side of the stream. - """ - - block = self.app.retrieve_block_by_hash(msg.header.hash) #TODO: missch een efficientere exists check toevoegen - #TODO: ook nog iets doen met bijhorende finalized header_hash & slot? - if not block: - logger.info(f"Received new block announcement from up0: {msg.header.hash}") - self.state_requesting_blocks = True - curr_hash = self.app.retrieve_block_hash(self.app.working_state.timeslot.number) - # TODO: max_blocks could be derived using current block timeslot and received blockhash timeslot? - self.ce128_initiate_block_request( - conn, - MsgCE128BlockRequest( - curr_hash, #msg.header.hash, - MsgCE128BlockRequestDirection.ASC.value, #MsgCE128BlockRequestDirection.DESC.value, - 10 - ) - ) - - - def ce128_initiate_block_request(self, conn: JAMConnection, req: MsgCE128BlockRequest): - #TODO: missch een batch param meegeven, zodat we een grotere reeks kunnen binnenhalen maar batchen... - stream = conn.open_jam_stream(StreamBlockRequest, direction=StreamDirection.initiator) - logger.info(f"Initiate block request on stream id: {stream.stream_id} direction: {req.direction}, max_block: {req.max_blocks} header hash: {req.header_hash}") - conn.send( - stream.stream_id, - stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), - end_stream=True - ) - - - def ce128_received_block_request(self, stream: StreamBlockRequest, req: MsgCE128BlockRequestResponse): - blocks = req.blocks - logger.info(f"Parsed {len(blocks)} blocks") - asyncio.create_task( - self.app.import_queue_add_blocks( - blocks, - #process=True, - on_success=MESSAGE_TYPES.CE128_SUCCESS, - on_failure=MESSAGE_TYPES.CE128_FAILURE - ) - ) - - - def ce128_send_block_request(self, stream: StreamBlockRequest, block_req: MsgCE128BlockRequest): - block:Block = None - blocks:List[Block] = [] - first_block_hash = bytes(32) - last_block_hash = self.app.retrieve_block_hash(self.app.working_state.timeslot.number) - #TODO: check <=0 -> bad request - block_header:Header = self.app.retrieve_block_header(block_req.header_hash) - next_hash = block_req.header_hash - - if block_header and block_req.max_blocks > 0: - - for x in range(block_req.max_blocks): - - if block_req.direction == MsgCE128BlockRequestDirection.ASC.value: - # Note: exclusive request block hash - block_child_hash: bytes = self.app.retrieve_block_child_hash(next_hash) - if block_child_hash: - block = self.app.retrieve_block_by_hash(block_child_hash) - next_hash = block.header.hash - else: - break - elif block_req.direction == MsgCE128BlockRequestDirection.DESC.value: - # Note: inclusive request block hash - block = self.app.retrieve_block_by_hash(next_hash) - if not block or block.header.timeslot == 0 or block.header.hash == bytes(32): - break - - next_hash = block.header.parent - else: - # TODO: error - raise Exception("ce128_send_block_request HMmmmmmz?????") - - blocks.append(block) - - if blocks: - logger.info(f"Send {len(blocks)} blocks (direction={block_req.direction} max blocks={block_req.max_blocks})") - stream.conn.send( - stream.stream_id, - stream.create_message(MsgCE128BlockRequestResponse(blocks=blocks).to_jam_bytes().to_bytes()), - end_stream=True - ) - else: - #TODO: ook op max_blocks checken -> bijhouden op de stream? - logger.info(f"No blocks to send requested: {block_req.header_hash.hex()} current: {last_block_hash.hex()}") - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce128_abort_block_request(self): - logger.info(f"Finished, start parsing import queue {len(self.app.import_queue)}") - # Note: could happen during a block_request "session" - #TODO: or do we want to clear the queue? - asyncio.create_task( - self.app.process_import_queue( - on_success=MESSAGE_TYPES.CE128_SUCCESS, - on_failure=MESSAGE_TYPES.CE128_FAILURE - ) - ) - - - async def ce128_finish_block_request(self, *args, **kwargs): - #TODO: add reason, origin/connection etc - self.state_requesting_blocks = False - - - def ce131_received_ticket(self, stream: StreamSafroleTicketDistributionStep1, msg: MsgCE131SafroleTicketDistribution): - logger.info(f"Received ticket for epoch {msg.epoch_index}") - - current_epoch = self.app.working_state.timeslot.number // EPOCH_TIMESLOTS - if msg.epoch_index < current_epoch: - logger.warning(f"Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") - stream.send_reset(1) - return - - try: - # Get ring public keys from current validators - ring_public_keys = [v.bandersnatch for v in self.app.working_state.safrole.validators] - # Get entropy for the epoch - entropy = self.app.working_state.entropy.entropy[2] - # Validate the ticket using the block extrinsic accumulator - #TODO: duplicate with Safrole.create_ticket_body??? - ticket_envelope = TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof) - ticket_body = self.app.block_extrinsic.create_ticket_body( - ticket_envelope, - ring_public_keys, - entropy - ) - - # Check if we already have this ticket - if ticket_body in self.app.working_state.safrole.ticket_accumulator: - logger.info(f"Ticket already in accumulator") - stream.send_reset(2) - return - - # Add ticket to our accumulator - asyncio.create_task( - self.app.block_extrinsic.add_ticket_body( - ticket_envelope, - ticket_body - ) - ) - - logger.info(f"Ticket added for epoch {msg.epoch_index}") - - #TODO: check if we should forward to (proxy) nodes via CE132 - # Forward to other connections via CE132 - # for conn in self.connections.values(): - # if conn != stream.conn and conn.is_connected(): - # self.ce132_initiate_ticket_distribution( - # conn, - # MsgCE132SafroleTicketDistribution( - # epoch_index=msg.epoch_index, - # ticket=MsgCE132SafroleTicket( - # attempt=msg.ticket.attempt, - # proof=msg.ticket.proof - # ) - # ) - # ) - # logger.debug(f"Forwarded ticket to {len(self.connections) - 1} peers via CE132") - - except ValueError as e: - #TODO: register this, detect bad nodes - logger.error(f"Invalid ticket: {e}") - stream.send_reset(3) - return - - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce132_initiate_ticket_distribution(self, conn: JAMConnection, msg: MsgCE132SafroleTicketDistribution): - stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep2, direction=StreamDirection.initiator) - logger.info(f"Distribute ticket on stream id: {stream.stream_id} to {conn.host}:{conn.port}") - conn.send( - stream.stream_id, - stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), - end_stream=True - ) - - - def ce132_received_ticket(self, stream: StreamSafroleTicketDistributionStep2, msg: MsgCE132SafroleTicketDistribution): - logger.info(f"Received ticket for epoch {msg.epoch_index}") - - # Verify epoch index - current_epoch = self.app.working_state.timeslot.number // EPOCH_TIMESLOTS - if msg.epoch_index < current_epoch: - logger.warning(f"Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") - stream.send_reset(1) - return - - try: - # Get ring public keys from current validators - ring_public_keys = [v.bandersnatch for v in self.app.working_state.safrole.validators] - # Get entropy for the epoch - entropy = self.app.working_state.entropy.entropy[2] - # TODO: duplicate with Safrole.create_ticket_body??? - ticket_envelope = TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof) - ticket_body = self.app.block_extrinsic.create_ticket_body( - ticket_envelope, - ring_public_keys, - entropy - ) - - # Check if we already have this ticket - if ticket_body in self.app.working_state.safrole.ticket_accumulator: - logger.info(f"Ticket already in accumulator (via CE132), ignoring") - stream.conn.send(stream.stream_id, b'', end_stream=True) - return - - # Add ticket to our accumulator - asyncio.create_task( - self.app.block_extrinsic.add_ticket_body( - ticket_envelope, - ticket_body - ) - ) - - logger.info(f"Valid ticket received via CE132 (forwarded) for epoch {msg.epoch_index}") - - except ValueError as e: - logger.error(f"Invalid ticket: {e}") - stream.send_reset(3) - return - - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce132_ticket_distribution_success(self, reset_code: int): - logger.debug(f"Success with code {reset_code}") - # TODO: handle success, perhaps update state - - - def ce132_ticket_distribution_failure(self, reset_code: int): - logger.error(f"Failed with code {reset_code}") - # TODO: handle failure - - - def ce133_initiate_workpackage_submission(self, conn: JAMConnection, wp: MsgCE133WorkPackageSubmission, extrinsic: MsgCE133Extrinsic): - stream = conn.open_jam_stream(StreamWorkPackageSubmission, direction=StreamDirection.initiator) - logger.info(f"Initiating workpackage submission on stream id: {stream.stream_id}") - conn.send(stream.stream_id, stream.create_message(wp.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) - conn.send(stream.stream_id, stream.create_message(extrinsic.to_jam_bytes().to_bytes()), end_stream=True) - - - # def ce133_received_workpackage_submission(self, stream: StreamWorkPackageSubmission, msg: MsgCE133WorkPackageSubmission): - # logger.debug(f"Received workpackage submission for core {msg.core_index}") - # # TODO: process work package - - - def ce133_received_extrinsic_submission(self, stream: StreamWorkPackageSubmission, wp: WorkPackage, extrinsics: List[bytes]): - #logger.debug(f"CE133 received extrinsic data of length {len(msg.bytes_)}") - # TODO: process extrinsic, close stream if successful - # stream.acceptor_reset(0) - - stream.conn.send(stream.stream_id, b'', end_stream=True) - self.app.add_work_package(wp, extrinsics) - - - # def ce133_submission_success(self, reset_code: int): - # logger.debug(f"CE133 submission successful with code {reset_code}") - # - # - # def ce133_submission_failure(self, reset_code: int): - # logger.error(f"CE133 submission failed with code {reset_code}") - - - def ce134_initiate_workpackage_sharing(self, conn: JAMConnection, sharing: MsgCE134WorkPackageSharing, bundle: MsgCE134WorkPackageBundle): - stream = conn.open_jam_stream(StreamWorkPackageSharing, direction=StreamDirection.initiator) - logger.info(f"Initiate sharing workpage on stream id: {stream.stream_id} to {conn.host}:{conn.port}") - conn.send(stream.stream_id, stream.create_message(sharing.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) - conn.send(stream.stream_id, stream.create_message(bundle.to_jam_bytes().to_bytes()), end_stream=True) - - - def ce134_received_workpackage_sharing(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageSharing): - logger.info(f"ce134_received_workpackage_sharing for core {msg.core_index}") - # Store sharing msg in stream state for later use with bundle - stream.sharing_msg = msg - - - def ce134_received_bundle(self, stream: StreamWorkPackageSharing, msg: MsgCE134WorkPackageBundle): - logger.info(f"ce134_received_bundle") - - work_package = WorkPackage( - authorization=msg.authorization, - auth_code_host=msg.auth_code_host, - authorizer=msg.authorizer, - context=msg.context, - items=msg.items - ) - - work_report = self.app.process_work_package(work_package) - - report_hash = work_report.hash() - signature = self.app.config.keys.ed25519.sign(report_hash) - - response = MsgCE134RefineResponse(report_hash=report_hash, signature=signature) - stream.conn.send(stream.stream_id, stream.create_message(response.to_jam_bytes().to_bytes()), end_stream=True) - - - def ce134_received_refine_response(self, stream: StreamWorkPackageSharing, msg: MsgCE134RefineResponse): - logger.info(f"ce134_received_refine_response") - work_report = None #TODO!!! msg... - # Guarantee the work report - self.app.guarantee_work_report(work_report, self.app.current_timeslot()) - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce135_initiate_distribution(self, conn: JAMConnection, msg: MsgCE135GuaranteedWorkReport): - stream = conn.open_jam_stream(StreamWorkReportDistribution, direction=StreamDirection.initiator) - logger.info(f"CE135 initiating distribution on stream id: {stream.stream_id}") - conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) - - - def ce135_received_report(self, stream: StreamWorkReportDistribution, msg: MsgCE135GuaranteedWorkReport): - logger.info(f"CE135 received work report") - # TODO: process report - # stream.acceptor_reset(0) - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce136_initiate_request(self, conn: JAMConnection, msg: MsgCE136HashRequest): - stream = conn.open_jam_stream(StreamWorkReportRequest, direction=StreamDirection.initiator) - logger.info(f"CE136 initiating request on stream id: {stream.stream_id}") - conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) - - - def ce136_received_request(self, stream: StreamWorkReportRequest, msg: MsgCE136HashRequest): - logger.info(f"CE136 received request for hash {msg.hash.hex()}") - # TODO: lookup work report, send if available - report = MsgCE136WorkReport(report=b'') # placeholder - stream.conn.send(stream.stream_id, stream.create_message(report.to_jam_bytes().to_bytes()), end_stream=True) - - - def ce136_received_report(self, stream: StreamWorkReportRequest, msg: MsgCE136WorkReport): - logger.info(f"CE136 received work report of length {len(msg.report)}") - # TODO: process report - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce137_initiate_request(self, conn: JAMConnection, req: MsgCE137ShardRequest): - stream = conn.open_jam_stream(StreamShardDistribution, direction=StreamDirection.initiator) - conn.send(stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) - - - def ce137_received_request(self, stream: StreamShardDistribution, msg: MsgCE137ShardRequest): - # TODO: fetch and send bundle shard, segment shards, justification - bundle = MsgCE137BundleShard(bytes_ = b'') - segments = [MsgCE137SegmentShard(bytes_ = b'') for _ in range(10)] - just = MsgCE137Justification(bytes_ = b'') - stream.conn.send(stream.stream_id, stream.create_message(bundle.to_jam_bytes().to_bytes()), end_stream=False) - for s in segments: - stream.conn.send(stream.stream_id, stream.create_message(s.to_jam_bytes().to_bytes()), end_stream=False) - stream.conn.send(stream.stream_id, stream.create_message(just.to_jam_bytes().to_bytes()), end_stream=True) - - - def ce137_received_shard(self, stream: StreamShardDistribution, bundle, segments, just): - # TODO: process - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - # Similar for CE138, CE139, CE140 - def ce138_initiate_request(self, conn: JAMConnection, req: MsgCE138ShardRequest): - stream = conn.open_jam_stream(StreamAuditShardRequest, direction=StreamDirection.initiator) - conn.send(stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) - - - def ce138_received_request(self, stream: StreamAuditShardRequest, msg: MsgCE138ShardRequest): - # TODO: send bundle shard and justification - bundle = MsgCE138BundleShard(bytes_ = b'') - just = MsgCE138Justification(bytes_ = b'') - stream.conn.send(stream.stream_id, stream.create_message(bundle.to_jam_bytes().to_bytes()), end_stream=False) - stream.conn.send(stream.stream_id, stream.create_message(just.to_jam_bytes().to_bytes()), end_stream=True) - - - def ce138_received_shard(self, stream: StreamAuditShardRequest, bundle, just): - # TODO: process - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce139_initiate_request(self, conn: JAMConnection, req: MsgCE139SegmentRequest): - stream = conn.open_jam_stream(StreamSegmentShardRequest, direction=StreamDirection.initiator) - conn.send(stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) - - - def ce139_received_request(self, stream: StreamSegmentShardRequest, msg: MsgCE139SegmentRequest): - # TODO: send segment shards - shards = [MsgCE139SegmentShard(bytes_ = b'') for _ in msg.segment_indices] - for s in shards: - stream.conn.send(stream.stream_id, stream.create_message(s.to_jam_bytes().to_bytes()), end_stream=(s == shards[-1])) - - - def ce139_received_shards(self, stream: StreamSegmentShardRequest, shards): - # TODO: process - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce140_initiate_request(self, conn: JAMConnection, req: MsgCE140SegmentRequest): - stream = conn.open_jam_stream(StreamSegmentShardRequestJustification, direction=StreamDirection.initiator) - conn.send(stream.stream_id, stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) - - - def ce140_received_request(self, stream: StreamSegmentShardRequestJustification, msg: MsgCE140SegmentRequest): - # TODO: send shards and justifications - for idx in msg.segment_indices: - shard = MsgCE140SegmentShard(bytes_ = b'') - just = MsgCE140Justification(bytes_ = b'') - stream.conn.send(stream.stream_id, stream.create_message(shard.to_jam_bytes().to_bytes()), end_stream=False) - stream.conn.send(stream.stream_id, stream.create_message(just.to_jam_bytes().to_bytes()), end_stream=(idx == msg.segment_indices[-1])) - - - def ce140_received_shards_justified(self, stream: StreamSegmentShardRequestJustification, data): - # TODO: parse and process - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce141_initiate_distribution(self, conn: JAMConnection, msg: MsgCE141Assurance): - stream = conn.open_jam_stream(StreamAssuranceDistribution, direction=StreamDirection.initiator) - logger.info(f"Initiating assurance distribution on stream id: {stream.stream_id} to {conn.host}:{conn.port}") - conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) - - - def ce141_received_assurance(self, stream: StreamAssuranceDistribution, msg: MsgCE141Assurance): - logger.info(f"Received assurance") - # TODO: process!... - # stream.acceptor_reset(0) - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce142_initiate_announcement(self, conn: JAMConnection, msg: MsgCE142PreimageAnnouncement): - stream = conn.open_jam_stream(StreamPreimageAnnouncement, direction=StreamDirection.initiator) - logger.info(f"Initiating preimage announcement on stream id: {stream.stream_id} to {conn.host}:{conn.port}") - conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) - - - def ce142_received_announcement(self, stream: StreamPreimageAnnouncement, msg: MsgCE142PreimageAnnouncement): - logger.info(f"Received preimage announcement for hash {msg.hash.hex()}") - # TODO: check if needed, request via CE143 if so - # stream.acceptor_reset(0) - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce143_initiate_request(self, conn: JAMConnection, msg: MsgCE143HashRequest): - stream = conn.open_jam_stream(StreamPreimageRequest, direction=StreamDirection.initiator) - logger.info(f"CE143 initiating request on stream id: {stream.stream_id}") - conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) - - - def ce143_received_request(self, stream: StreamPreimageRequest, msg: MsgCE143HashRequest): - logger.info(f"CE143 received request for hash {msg.hash.hex()}") - # TODO: lookup preimage, send if available - preimage = MsgCE143Preimage(bytes_=b'') # placeholder - stream.conn.send(stream.stream_id, stream.create_message(preimage.to_jam_bytes().to_bytes()), end_stream=True) - - - def ce143_received_preimage(self, stream: StreamPreimageRequest, msg: MsgCE143Preimage): - logger.info(f"CE143 received preimage of length {len(msg.bytes_)}") - # TODO: process preimage - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce143_request_success(self, reset_code: int): - logger.info(f"CE143 request successful with code {reset_code}") - - - def ce143_request_failure(self, reset_code: int): - logger.error(f"CE143 request failed with code {reset_code}") - - - def ce144_initiate_announcement(self, conn: JAMConnection, ann: MsgCE144Announcement, evidence: MsgCE144Evidence): - stream = conn.open_jam_stream(StreamAuditAnnouncement, direction=StreamDirection.initiator) - conn.send(stream.stream_id, stream.create_message(ann.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=False) - conn.send(stream.stream_id, stream.create_message(evidence.to_jam_bytes().to_bytes()), end_stream=True) - - - def ce144_received_announcement(self, stream: StreamAuditAnnouncement, msg: MsgCE144Announcement): - # TODO: process announcement - pass - - - def ce144_received_evidence(self, stream: StreamAuditAnnouncement, msg: MsgCE144Evidence): - # TODO: process evidence - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - def ce145_initiate_publication(self, conn: JAMConnection, msg: MsgCE145JudgmentPublication): - stream = conn.open_jam_stream(StreamJudgmentPublication, direction=StreamDirection.initiator) - conn.send(stream.stream_id, stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), end_stream=True) - - - def ce145_received_judgment(self, stream: StreamJudgmentPublication, msg: MsgCE145JudgmentPublication): - # TODO: process judgment - stream.conn.send(stream.stream_id, b'', end_stream=True) - - - # Note: pubsub_ prefixed methods are driven by application level events - async def pubsub_up0_broadcast_block(self, block: Block): - logger.info(f'Broadcast {block.header.hash.hex()} to {len(self.connections)} connections') - - msg = MsgUP0Announcement( - header=block.header, - header_hash=block.header.hash, - timeslot=block.header.timeslot - ).to_jam_bytes().to_bytes() - - for client_id, client in self.connections.items(): - # Check if UP0 stream is established - if not client.is_connected(): - logger.debug( - f"Skipping broadcast to {client.host}:{client.port} - connection is not fully established yet") - continue - - logger.debug(f"Send block header to client {client.host}:{client.port} with hash {block.header.hash.hex()}") - client.send( - client.stream_up.stream_id, - client.stream_up.create_message(msg), - end_stream=False - ) - - - async def pubsub_ce131_initiate_distribute_own_ticket(self, data): - #TODO: wrap in a dataclass and pass that in the signal - epoch_index: int = data[0] - attempt: int = data[1] - proof: bytes = data[2] - - msg = MsgCE131SafroleTicketDistribution( - epoch_index=epoch_index, - ticket=MsgCE131SafroleTicket( - attempt=attempt, - proof=proof - ) - ) - - distributed_count = 0 - for conn in self.connections.values(): - if conn.is_connected(): - try: - stream = conn.open_jam_stream(StreamSafroleTicketDistributionStep1, direction=StreamDirection.initiator) - logger.info(f"Send ticker announcement on stream id: {stream.stream_id} to {conn.host}:{conn.port}") - conn.send( - stream.stream_id, - stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), - end_stream=True - ) - - distributed_count += 1 - except Exception as e: - logger.error(f"Failed to distribute ticket to peer {conn.host}:{conn.port}: {e}") - - logger.info(f"Distributed ticket to {distributed_count} peers") - return distributed_count - - - # Temp placeholders - def verify_ticket_proof(self, msg): - return True - - - def verify_bundle(self, msg): - return True - - -def create_jam_connection( - protocol: JAMNPS, - quic_connection: JAMConnection, - direction: JAMConnectionDirection, - host: str, - port: int, - validator_key: Optional[bytes] -): - def create_connection(*args, **kwargs): - """ - TODO: check ALPN - The (ASCII-encoded) protocol identifier should be either jamnp-s/V/H or jamnp-s/V/H/builder. Here V is the protocol version, 0, and H is the first 8 nibbles of the hash of the chain's genesis header, in lower-case hexadecimal. - The /builder suffix should always be permitted by the side accepting the connection, but only used by the side initiating the connection if it is connecting as a work-package builder. Note that guarantors should accept work-package submission streams (CE 133) on all connections, regardless of how they were opened. The purpose of identifying as a builder at connection time is merely to request use of a slot reserved for builders, increasing the likelihood of a successful connection. - Validators should accept connections from other nodes too, with a reasonable number of slots (e.g. 20) reserved for work-package builders. Builders may reasonably be required to prove their credentials through submission of a valid work-package in order to retain their connection. - """ - conn = quic_connection(*args, **kwargs) - conn.protocol = protocol - conn.direction = direction - - conn.jam_connection_ulid = ULID() - conn.validator_key = validator_key - protocol.connections[conn.jam_connection_ulid] = conn - - # Note: for accepting connections addr & port are known after the QUIC handshake, see JAMConnection::HandshakeComplete - if direction == JAMConnectionDirection.initiator: - conn.host = host - conn.port = port - protocol.conn_initiated.add(conn.jam_connection_ulid) - else: - protocol.conn_accepted.add(conn.jam_connection_ulid) - """ - TODO: if incomming connection is not a validator: - Validators should accept connections from other nodes too, with a reasonable number of slots (e.g. 20) reserved for work-package builders. Builders may reasonably be required to prove their credentials through submission of a valid work-package in order to retain their connection. - """ - - if validator_key: - if protocol.validator_connections[validator_key]["connection"]: - protocol.disconnect(protocol.validator_connections[validator_key]["connection"], validator_key) - - protocol.validator_connections[validator_key]["connection"] = conn - protocol.validator_connections[validator_key]["last_try"] = time.time() - - return conn - - return create_connection - - -class SessionTicketStore: - - def __init__(self) -> None: - self.tickets: Dict[bytes, SessionTicket] = {} - - def add(self, ticket: SessionTicket) -> None: - self.tickets[ticket.ticket] = ticket - - def pop(self, label: bytes) -> Optional[SessionTicket]: - return self.tickets.pop(label, None) + self.stream_manager.cleanup_connection(connection) + self.peer_registry.unregister(connection) + self.validator_manager.on_disconnect(connection, validator_key) diff --git a/pyjamaz/transport/jamnp_s/stream_base.py b/pyjamaz/transport/jamnp_s/stream_base.py deleted file mode 100644 index af0eff71..00000000 --- a/pyjamaz/transport/jamnp_s/stream_base.py +++ /dev/null @@ -1,172 +0,0 @@ -import logging -from enum import Enum -from typing import Optional - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamType(Enum): - UP0_BlockAnnouncement: int = 0 - CE128_BlockRequest: int = 128 - CE129_StateRequest: int = 129 - CE131_SafroleTicketDistributionStep1: int = 131 - CE132_SafroleTicketDistributionStep2: int = 132 - CE133_WorkPackageSubmission: int = 133 - CE134_WorkPackageSharing: int = 134 - CE135_WorkReportDistribution: int = 135 - CE136_WorkReportRequest: int = 136 - CE137_ShardDistribution: int = 137 - CE138_AuditShardRequest: int = 138 - CE139_SegmentShardRequest: int = 139 - CE140_SegmentShardRequestJustification: int = 140 - CE141_AssuranceDistribution: int = 141 - CE142_PreimageAnnouncement: int = 142 - CE143_PreimageRequest: int = 143 - CE144_AuditAnnouncement: int = 144 - CE145_JudgmentPublication: int = 145 - - -class StreamDirection(Enum): - initiator: int = 0 - acceptor: int = 1 - - -class Stream: - - # Standard reset codes - ERROR_GENERAL = 1 - ERROR_INVALID_MESSAGE = 2 - ERROR_INVALID_SIZE = 3 - ERROR_TOO_LARGE = 4 - ERROR_VALIDATION_FAILED = 5 - - #TODO: typings on connection - def __init__(self, stream_id: int, connection, direction: StreamDirection): - self.stream_id = stream_id - self.stream_type = None # Note: override in subclass - self.stream_type_byte = None # Note: override in subclass - self.conn = connection - self.protocol = connection.protocol - self.direction = direction - - self._msg_buffer = b"" - self._msg_len = -1 - - - def _reset_msg(self): - self._msg_buffer = b"" - self._msg_len = -1 - - - def initiator_message(self, data: bytes): - raise Exception("Override this method in Stream subclass") - - - def acceptor_message(self, data: bytes): - raise Exception("Override this method in Stream subclass") - - - def create_message(self, payload: bytes, add_stream_type:bool=False): - if add_stream_type: - # Initial messages over a new stream should de prefixed with a stream type byte - return self.stream_type_byte + (len(payload)).to_bytes(length=4, byteorder='little') + payload - else: - return (len(payload)).to_bytes(length=4, byteorder='little') + payload - - - def receive_data(self, data: bytes, end_stream: bool = False): - try: - # Note: Parse bytes until stream data is empty: https://github.com/microsoft/msquic/discussions/2037 - while len(data) > 0: - - if self._msg_len == -1: - # byte_data = bytes(event.data) - self._msg_len = int.from_bytes(data[0:4], byteorder='little') - data = data[4:] - logger.debug(f"Received new message for stream {self.stream_id} with a msg length: {self._msg_len} ({len(data)} received)") - # TODO: check message length > 0???!!!! - if self._msg_len <= 0: - logger.error(f"Message size <= 0 for stream {self.stream_id} {self._msg_len}") - self.send_reset(self.ERROR_INVALID_SIZE) - return - - # Add max size check - if self._msg_len > self.protocol.MAX_MESSAGE_SIZE: - logger.error(f"Message too large for stream {self.stream_id}") - self.send_reset(self.ERROR_TOO_LARGE) - return - - msg_complete = False - if len(self._msg_buffer) + len(data) >= self._msg_len: - # If we received a full message (or more than 1 message) - end_offset = self._msg_len - len(self._msg_buffer) - self._msg_buffer += data[:end_offset] - data = data[end_offset:] # Note: this packet could contain start of a new message - msg_complete = True - else: - # Otherwise append only, we expect more data to finish this message - self._msg_buffer += data - logger.debug(f"Appending to message for stream {self.stream_id} ({self._msg_buffer} bytes of {self._msg_len})") - data = [] # Note: no new packet can be present in this data - - # If we assembled a new message, parse it - if msg_complete: - logger.debug(f"Message complete for stream {self.stream_id}") - try: - if self.direction == StreamDirection.initiator: - self.initiator_message(self._msg_buffer) - else: - self.acceptor_message(self._msg_buffer) - except Exception as e: - self.handle_error(e) - finally: - self._reset_msg() - - if end_stream: - logger.debug(f"Received FIN on stream {self.stream_id}") - self.handle_fin() - - except Exception as e: - self.handle_error(e) - - - def handle_error(self, exc: Exception): - if isinstance(exc, ValueError): - code = self.ERROR_INVALID_MESSAGE - elif isinstance(exc, MemoryError): - code = self.ERROR_TOO_LARGE - else: - code = self.ERROR_GENERAL - - logger.error(f"Stream {self.stream_id} error: {exc}") - - self.send_reset(code) - - - def handle_fin(self): - #self.conn.close_jam_stream(self, clean_close=True) - pass - - - def send_reset(self, reset_code: int): - self.conn.close_jam_stream(self, reset_code, False) - - - def receive_reset(self, reset_code: int): - #TODO: Do we really need to handle initiator and acceptor seperately here or can we merge initiator_reset and acceptor_reset? - if self.direction == StreamDirection.initiator: - self.initiator_reset(reset_code) - else: - self.acceptor_reset(reset_code) - - del self.conn.streams[self.stream_id] - - - # TODO: do we really need difference between a initiator and acceptor??? - def initiator_reset(self, reset_code: int): - raise Exception("Implement in subclass") - - - # TODO: do we really need difference between a initiator and acceptor??? - def acceptor_reset(self, reset_code: int): - raise Exception("Implement in subclass") diff --git a/pyjamaz/transport/jamnp_s/stream_manager.py b/pyjamaz/transport/jamnp_s/stream_manager.py new file mode 100644 index 00000000..0e1cde03 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/stream_manager.py @@ -0,0 +1,227 @@ +from __future__ import annotations + +import logging +from typing import Dict, Tuple, TYPE_CHECKING + +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamDirection, StreamHandler, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + +if TYPE_CHECKING: + from pyjamaz.transport.jamnp_s.connection import JAMConnection + + +class StreamManager: + def __init__(self, max_message_size: int) -> None: + self.max_message_size = max_message_size + self.handlers: Dict[StreamKind, StreamHandler] = {} + self._streams: Dict[Tuple[int, int], ManagedStream] = {} + self._pending_kind_buffers: Dict[Tuple[int, int], bytearray] = {} + + def register_handler(self, handler: StreamHandler) -> None: + self.handlers[handler.kind] = handler + + def open_outgoing(self, connection: "JAMConnection", kind: StreamKind) -> ManagedStream: + stream_id = connection._quic.get_next_available_stream_id() + stream = ManagedStream( + stream_id=stream_id, + stream_kind=kind, + conn=connection, + direction=StreamDirection.initiator, + ) + self._register_stream(stream) + return stream + + def receive_stream_data( + self, + connection: "JAMConnection", + stream_id: int, + data: bytes, + end_stream: bool = False, + ) -> None: + stream = self._streams.get(self._key(connection, stream_id)) + + if stream is None: + stream = self._resolve_incoming_stream(connection, stream_id, data, end_stream) + if stream is None: + return + + pending = self._pending_kind_buffers.pop(self._key(connection, stream_id), bytearray()) + data = bytes(pending[1:]) + + self._process_stream_bytes(stream, data, end_stream) + + def receive_reset(self, connection: "JAMConnection", stream_id: int, reset_code: int) -> None: + key = self._key(connection, stream_id) + stream = self._streams.get(key) + if stream is None: + self._pending_kind_buffers.pop(key, None) + logger.debug(f"Connection {connection.jam_connection_ulid} QUIC stream {stream_id} already closed") + return + + logger.info( + f"Connection {connection.jam_connection_ulid} QUIC StreamReset {stream.stream_kind} ({stream_id}) direction={stream.direction} code {reset_code}" + ) + + try: + if stream.handler is not None: + stream.handler.on_reset(stream, reset_code) + finally: + self._cleanup_stream(stream) + + def send_reset(self, stream: ManagedStream, reset_code: int) -> None: + stream.conn.reset_stream(stream.stream_id, reset_code) + self._cleanup_stream(stream) + + def close_stream(self, stream: ManagedStream, reason: int = 0, clean_close: bool = True) -> None: + stream.conn.close_stream(stream.stream_id, clean_close=clean_close, reason=reason) + self._cleanup_stream(stream) + + def cleanup_connection(self, connection: "JAMConnection") -> None: + conn_key = id(connection) + + for key, stream in list(self._streams.items()): + if key[0] != conn_key: + continue + self._cleanup_stream(stream) + + for key in [key for key in self._pending_kind_buffers if key[0] == conn_key]: + self._pending_kind_buffers.pop(key, None) + + connection.streams.clear() + connection.stream_up = None + + def _resolve_incoming_stream( + self, + connection: "JAMConnection", + stream_id: int, + data: bytes, + end_stream: bool, + ) -> ManagedStream | None: + key = self._key(connection, stream_id) + pending = self._pending_kind_buffers.setdefault(key, bytearray()) + if data: + pending.extend(data) + + if len(pending) < 1: + if end_stream: + self._reset_unknown_stream( + connection, stream_id, ManagedStream.ERROR_INVALID_MESSAGE, "Stream ended before stream kind was received" + ) + return None + + try: + kind = StreamKind(pending[0]) + except ValueError: + self._reset_unknown_stream( + connection, + stream_id, + ManagedStream.ERROR_INVALID_MESSAGE, + f"QUIC stream {stream_id} is not mapped (type {pending[0]})", + ) + return None + + stream = ManagedStream( + stream_id=stream_id, + stream_kind=kind, + conn=connection, + direction=StreamDirection.acceptor, + ) + self._register_stream(stream) + return stream + + def _process_stream_bytes(self, stream: ManagedStream, data: bytes, end_stream: bool) -> None: + if data: + stream.recv_buffer.extend(data) + + try: + while True: + if stream.expected_len is None: + if len(stream.recv_buffer) < 4: + break + + stream.expected_len = int.from_bytes(stream.recv_buffer[:4], byteorder="little") + del stream.recv_buffer[:4] + + logger.debug( + f"Received new message for stream {stream.stream_id} with a msg length: {stream.expected_len} ({len(stream.recv_buffer)} buffered)" + ) + + if stream.expected_len <= 0: + raise ValueError(f"Message size <= 0 for stream {stream.stream_id} {stream.expected_len}") + if stream.expected_len > self.max_message_size: + raise MemoryError(f"Message too large for stream {stream.stream_id}") + + if len(stream.recv_buffer) < stream.expected_len: + break + + message = bytes(stream.recv_buffer[:stream.expected_len]) + del stream.recv_buffer[:stream.expected_len] + stream.expected_len = None + + logger.debug(f"Message complete for stream {stream.stream_id}") + if stream.handler is not None: + stream.handler.on_message(stream, message) + + if stream.closed: + return + + if not end_stream: + return + + if stream.expected_len is not None or len(stream.recv_buffer) > 0: + raise ValueError(f"Incomplete framed message on FIN for stream {stream.stream_id}") + + logger.debug(f"Received FIN on stream {stream.stream_id}") + if stream.handler is not None: + stream.handler.on_fin(stream) + except Exception as exc: + stream.handle_error(exc) + return + + self._cleanup_stream(stream) + + def _register_stream(self, stream: ManagedStream) -> None: + handler = self.handlers.get(stream.stream_kind) + if handler is None: + raise ValueError(f"Unsupported stream kind: {stream.stream_kind}") + + stream.handler = handler + self._streams[self._key(stream.conn, stream.stream_id)] = stream + stream.conn.streams[stream.stream_id] = stream + if stream.stream_kind == StreamKind.UP0_BlockAnnouncement: + stream.conn.stream_up = stream + + handler.init_stream(stream) + + def _cleanup_stream(self, stream: ManagedStream) -> None: + if stream.closed: + return + + stream.closed = True + + if stream.handler is not None: + on_close = getattr(stream.handler, "on_close", None) + if callable(on_close): + on_close(stream) + + self._streams.pop(self._key(stream.conn, stream.stream_id), None) + stream.conn.streams.pop(stream.stream_id, None) + + if stream.conn.stream_up is stream: + stream.conn.stream_up = None + + def _reset_unknown_stream( + self, + connection: "JAMConnection", + stream_id: int, + error_code: int, + reason: str, + ) -> None: + logger.error(reason) + connection.reset_stream(stream_id, error_code) + self._pending_kind_buffers.pop(self._key(connection, stream_id), None) + + @staticmethod + def _key(connection: "JAMConnection", stream_id: int) -> Tuple[int, int]: + return id(connection), stream_id diff --git a/pyjamaz/transport/jamnp_s/stream_map.py b/pyjamaz/transport/jamnp_s/stream_map.py deleted file mode 100644 index 27cce470..00000000 --- a/pyjamaz/transport/jamnp_s/stream_map.py +++ /dev/null @@ -1,40 +0,0 @@ -from pyjamaz.transport.jamnp_s.stream_base import StreamType -from pyjamaz.transport.jamnp_s.streams.stream_0 import StreamUP -from pyjamaz.transport.jamnp_s.streams.stream_128 import StreamBlockRequest -from pyjamaz.transport.jamnp_s.streams.stream_131 import StreamSafroleTicketDistributionStep1 -from pyjamaz.transport.jamnp_s.streams.stream_132 import StreamSafroleTicketDistributionStep2 -from pyjamaz.transport.jamnp_s.streams.stream_134 import StreamWorkPackageSharing -from pyjamaz.transport.jamnp_s.streams.stream_141 import StreamAssuranceDistribution -from pyjamaz.transport.jamnp_s.streams.stream_142 import StreamPreimageAnnouncement -from pyjamaz.transport.jamnp_s.streams.stream_143 import StreamPreimageRequest -from pyjamaz.transport.jamnp_s.streams.stream_133 import StreamWorkPackageSubmission -from pyjamaz.transport.jamnp_s.streams.stream_135 import StreamWorkReportDistribution -from pyjamaz.transport.jamnp_s.streams.stream_136 import StreamWorkReportRequest -from pyjamaz.transport.jamnp_s.streams.stream_137 import StreamShardDistribution -from pyjamaz.transport.jamnp_s.streams.stream_138 import StreamAuditShardRequest -from pyjamaz.transport.jamnp_s.streams.stream_139 import StreamSegmentShardRequest -from pyjamaz.transport.jamnp_s.streams.stream_140 import StreamSegmentShardRequestJustification -from pyjamaz.transport.jamnp_s.streams.stream_144 import StreamAuditAnnouncement -from pyjamaz.transport.jamnp_s.streams.stream_145 import StreamJudgmentPublication - - -StreamLookup = { - StreamType.UP0_BlockAnnouncement.value: StreamUP, - StreamType.CE128_BlockRequest.value: StreamBlockRequest, - #TODO: StreamType.CE129_:, - StreamType.CE131_SafroleTicketDistributionStep1.value: StreamSafroleTicketDistributionStep1, - StreamType.CE132_SafroleTicketDistributionStep2.value: StreamSafroleTicketDistributionStep2, - StreamType.CE134_WorkPackageSharing.value: StreamWorkPackageSharing, - StreamType.CE133_WorkPackageSubmission.value: StreamWorkPackageSubmission, - StreamType.CE135_WorkReportDistribution.value: StreamWorkReportDistribution, - StreamType.CE136_WorkReportRequest.value: StreamWorkReportRequest, - StreamType.CE137_ShardDistribution.value: StreamShardDistribution, - StreamType.CE138_AuditShardRequest.value: StreamAuditShardRequest, - StreamType.CE139_SegmentShardRequest.value: StreamSegmentShardRequest, - StreamType.CE140_SegmentShardRequestJustification.value: StreamSegmentShardRequestJustification, - StreamType.CE141_AssuranceDistribution.value: StreamAssuranceDistribution, - StreamType.CE142_PreimageAnnouncement.value: StreamPreimageAnnouncement, - StreamType.CE143_PreimageRequest.value: StreamPreimageRequest, - StreamType.CE144_AuditAnnouncement.value: StreamAuditAnnouncement, - StreamType.CE145_JudgmentPublication.value: StreamJudgmentPublication, -} \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/__init__.py b/pyjamaz/transport/jamnp_s/streams/__init__.py index e69de29b..48d3788d 100644 --- a/pyjamaz/transport/jamnp_s/streams/__init__.py +++ b/pyjamaz/transport/jamnp_s/streams/__init__.py @@ -0,0 +1,54 @@ +from pyjamaz.transport.jamnp_s.streams.ce129 import CE129Handler +from pyjamaz.transport.jamnp_s.streams.ce128 import CE128Handler +from pyjamaz.transport.jamnp_s.streams.ce131 import CE131Handler +from pyjamaz.transport.jamnp_s.streams.ce132 import CE132Handler +from pyjamaz.transport.jamnp_s.streams.ce133 import CE133Handler +from pyjamaz.transport.jamnp_s.streams.ce134 import CE134Handler +from pyjamaz.transport.jamnp_s.streams.ce135 import CE135Handler +from pyjamaz.transport.jamnp_s.streams.ce136 import CE136Handler +from pyjamaz.transport.jamnp_s.streams.ce137 import CE137Handler +from pyjamaz.transport.jamnp_s.streams.ce138 import CE138Handler +from pyjamaz.transport.jamnp_s.streams.ce139 import CE139Handler +from pyjamaz.transport.jamnp_s.streams.ce140 import CE140Handler +from pyjamaz.transport.jamnp_s.streams.ce141 import CE141Handler +from pyjamaz.transport.jamnp_s.streams.ce142 import CE142Handler +from pyjamaz.transport.jamnp_s.streams.ce143 import CE143Handler +from pyjamaz.transport.jamnp_s.streams.ce144 import CE144Handler +from pyjamaz.transport.jamnp_s.streams.ce145 import CE145Handler +from pyjamaz.transport.jamnp_s.streams.context import ProtocolContext, ProtocolSharedState +from pyjamaz.transport.jamnp_s.streams.up0 import UP0Handler + + +def register_handlers(stream_manager, context: ProtocolContext): + handlers = [ + UP0Handler(context), + CE128Handler(context), + CE129Handler(context), + CE131Handler(context), + CE132Handler(context), + CE133Handler(context), + CE134Handler(context), + CE135Handler(context), + CE136Handler(context), + CE137Handler(context), + CE138Handler(context), + CE139Handler(context), + CE140Handler(context), + CE141Handler(context), + CE142Handler(context), + CE143Handler(context), + CE144Handler(context), + CE145Handler(context), + ] + + for handler in handlers: + context.register_handler(handler) + stream_manager.register_handler(handler) + return context.handlers + + +__all__ = [ + "ProtocolContext", + "ProtocolSharedState", + "register_handlers", +] diff --git a/pyjamaz/transport/jamnp_s/streams/base.py b/pyjamaz/transport/jamnp_s/streams/base.py new file mode 100644 index 00000000..8a86dc59 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/base.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamDirection + +if TYPE_CHECKING: + from pyjamaz.transport.jamnp_s.connection import JAMConnection + from pyjamaz.transport.jamnp_s.streams.context import ProtocolContext + from pyjamaz.transport.jamnp_s.types import StreamKind + + +class ContextualStreamHandler: + kind: "StreamKind" + + def __init__(self, context: "ProtocolContext") -> None: + self.context = context + + def init_stream(self, stream: ManagedStream) -> None: + pass + + def on_message(self, stream: ManagedStream, data: bytes) -> None: + if stream.direction == StreamDirection.initiator: + self.initiator_message(stream, data) + else: + self.acceptor_message(stream, data) + + def on_fin(self, stream: ManagedStream) -> None: + if stream.direction == StreamDirection.initiator: + self.initiator_fin(stream) + else: + self.acceptor_fin(stream) + + def on_reset(self, stream: ManagedStream, reset_code: int) -> None: + if stream.direction == StreamDirection.initiator: + self.initiator_reset(stream, reset_code) + else: + self.acceptor_reset(stream, reset_code) + + def on_close(self, stream: ManagedStream) -> None: + pass + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + raise NotImplementedError + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + raise NotImplementedError + + def initiator_fin(self, stream: ManagedStream) -> None: + pass + + def acceptor_fin(self, stream: ManagedStream) -> None: + pass + + def initiator_reset(self, stream: ManagedStream, reset_code: int) -> None: + pass + + def acceptor_reset(self, stream: ManagedStream, reset_code: int) -> None: + pass + + def open_outgoing(self, conn: "JAMConnection") -> ManagedStream: + return self.context.stream_manager.open_outgoing(conn, self.kind) diff --git a/pyjamaz/transport/jamnp_s/streams/ce128.py b/pyjamaz/transport/jamnp_s/streams/ce128.py new file mode 100644 index 00000000..962551ed --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce128.py @@ -0,0 +1,140 @@ +from __future__ import annotations + +import asyncio +import logging +from dataclasses import dataclass +from typing import List + +from jamcodec.base import JamBytes + +from pyjamaz.constants import MESSAGE_TYPES +from pyjamaz.models.block import Block, Header +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import ( + MsgCE128BlockRequest, + MsgCE128BlockRequestDirection, + MsgCE128BlockRequestResponse, +) +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +@dataclass +class CE128StreamState: + active: bool = True + + +class CE128Handler(ContextualStreamHandler): + kind = StreamKind.CE128_BlockRequest + + def __init__(self, context) -> None: + super().__init__(context) + self._streams = {} + + def init_stream(self, stream: ManagedStream) -> None: + self._streams[stream.stream_key] = CE128StreamState() + + def initiate_block_request(self, conn, req: MsgCE128BlockRequest) -> ManagedStream: + stream = self.open_outgoing(conn) + logger.info( + f"Initiate block request on stream id: {stream.stream_id} direction: {req.direction}, " + f"max_block: {req.max_blocks} header hash: {req.header_hash}" + ) + conn.send( + stream.stream_id, + stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + return stream + + async def finish_block_request(self, *args, **kwargs) -> None: + self.context.state_requesting_blocks = False + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug( + f"CE128 initiated stream {stream.stream_id} received block request response: {len(data)} bytes" + ) + req = MsgCE128BlockRequestResponse.from_jam_bytes(JamBytes(data)) + blocks = req.blocks + logger.info(f"Parsed {len(blocks)} blocks") + asyncio.create_task( + self.context.app.import_queue_add_blocks( + blocks, + on_success=MESSAGE_TYPES.CE128_SUCCESS, + on_failure=MESSAGE_TYPES.CE128_FAILURE, + ) + ) + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug(f"CE128 acceptor stream {stream.stream_id} received block request") + self._send_block_request_response( + stream, + MsgCE128BlockRequest.from_jam_bytes(JamBytes(data)), + ) + + def initiator_fin(self, stream: ManagedStream) -> None: + self._abort_block_request() + + def acceptor_fin(self, stream: ManagedStream) -> None: + self._abort_block_request() + + def initiator_reset(self, stream: ManagedStream, reset_code: int) -> None: + asyncio.create_task(self.finish_block_request(reset_code)) + + def acceptor_reset(self, stream: ManagedStream, reset_code: int) -> None: + asyncio.create_task(self.finish_block_request(reset_code)) + + def on_close(self, stream: ManagedStream) -> None: + self._streams.pop(stream.stream_key, None) + + def _send_block_request_response(self, stream: ManagedStream, block_req: MsgCE128BlockRequest) -> None: + block: Block = None + blocks: List[Block] = [] + last_block_hash = self.context.app.retrieve_block_hash(self.context.app.working_state.timeslot.number) + block_header: Header = self.context.app.retrieve_block_header(block_req.header_hash) + next_hash = block_req.header_hash + + if block_header and block_req.max_blocks > 0: + for _ in range(block_req.max_blocks): + if block_req.direction == MsgCE128BlockRequestDirection.ASC.value: + block_child_hash: bytes = self.context.app.retrieve_block_child_hash(next_hash) + if block_child_hash: + block = self.context.app.retrieve_block_by_hash(block_child_hash) + next_hash = block.header.hash + else: + break + elif block_req.direction == MsgCE128BlockRequestDirection.DESC.value: + block = self.context.app.retrieve_block_by_hash(next_hash) + if not block or block.header.timeslot == 0 or block.header.hash == bytes(32): + break + next_hash = block.header.parent + else: + raise RuntimeError("Unsupported CE128 block request direction") + + blocks.append(block) + + if blocks: + logger.info( + f"Send {len(blocks)} blocks (direction={block_req.direction} max blocks={block_req.max_blocks})" + ) + stream.conn.send( + stream.stream_id, + stream.create_message(MsgCE128BlockRequestResponse(blocks=blocks).to_jam_bytes().to_bytes()), + end_stream=True, + ) + return + + logger.info( + f"No blocks to send requested: {block_req.header_hash.hex()} current: {last_block_hash.hex()}" + ) + stream.conn.send(stream.stream_id, b"", end_stream=True) + + def _abort_block_request(self) -> None: + logger.info(f"Finished, start parsing import queue {len(self.context.app.import_queue)}") + asyncio.create_task( + self.context.app.process_import_queue( + on_success=MESSAGE_TYPES.CE128_SUCCESS, + on_failure=MESSAGE_TYPES.CE128_FAILURE, + ) + ) diff --git a/pyjamaz/transport/jamnp_s/streams/ce129.py b/pyjamaz/transport/jamnp_s/streams/ce129.py new file mode 100644 index 00000000..580430cf --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce129.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + + +class CE129Handler(ContextualStreamHandler): + kind = StreamKind.CE129_StateRequest + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + pass + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + pass diff --git a/pyjamaz/transport/jamnp_s/streams/ce131.py b/pyjamaz/transport/jamnp_s/streams/ce131.py new file mode 100644 index 00000000..c2076231 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce131.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +import asyncio +import logging +from dataclasses import dataclass + +from jamcodec.base import JamBytes + +from pyjamaz.graypaper_constants import EPOCH_TIMESLOTS +from pyjamaz.models.block import TicketEnvelope +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import ( + MsgCE131SafroleTicket, + MsgCE131SafroleTicketDistribution, +) +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +@dataclass +class CE131StreamState: + active: bool = True + + +class CE131Handler(ContextualStreamHandler): + kind = StreamKind.CE131_SafroleTicketDistributionStep1 + + def __init__(self, context) -> None: + super().__init__(context) + self._streams = {} + + def init_stream(self, stream: ManagedStream) -> None: + self._streams[stream.stream_key] = CE131StreamState() + + async def broadcast_own_ticket(self, data) -> int: + epoch_index, attempt, proof = data + msg = MsgCE131SafroleTicketDistribution( + epoch_index=epoch_index, + ticket=MsgCE131SafroleTicket(attempt=attempt, proof=proof), + ) + + distributed_count = 0 + for conn in self.context.connections.values(): + if not conn.is_connected(): + continue + + try: + stream = self.open_outgoing(conn) + logger.info(f"Send ticker announcement on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + conn.send( + stream.stream_id, + stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + distributed_count += 1 + except Exception as exc: + logger.error(f"Failed to distribute ticket to peer {conn.host}:{conn.port}: {exc}") + + logger.info(f"Distributed ticket to {distributed_count} peers") + return distributed_count + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.warning(f"Unexpected data in CE131 initiator: {len(data)} bytes") + raise ValueError("Unexpected data in CE131 initiator") + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug(f"CE131 acceptor stream {stream.stream_id} received ticket") + self._handle_received_ticket( + stream, + MsgCE131SafroleTicketDistribution.from_jam_bytes(JamBytes(data)), + ) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.info("Finished with FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.info("Finished with FIN") + + def on_close(self, stream: ManagedStream) -> None: + self._streams.pop(stream.stream_key, None) + + def _handle_received_ticket(self, stream: ManagedStream, msg: MsgCE131SafroleTicketDistribution) -> None: + logger.info(f"Received ticket for epoch {msg.epoch_index}") + + current_epoch = self.context.app.working_state.timeslot.number // EPOCH_TIMESLOTS + if msg.epoch_index < current_epoch: + logger.warning(f"Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") + stream.send_reset(1) + return + + try: + ring_public_keys = [v.bandersnatch for v in self.context.app.working_state.safrole.validators] + entropy = self.context.app.working_state.entropy.entropy[2] + ticket_envelope = TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof) + ticket_body = self.context.app.block_extrinsic.create_ticket_body( + ticket_envelope, + ring_public_keys, + entropy, + ) + + if ticket_body in self.context.app.working_state.safrole.ticket_accumulator: + logger.info("Ticket already in accumulator") + stream.send_reset(2) + return + + asyncio.create_task( + self.context.app.block_extrinsic.add_ticket_body( + ticket_envelope, + ticket_body, + ) + ) + logger.info(f"Ticket added for epoch {msg.epoch_index}") + except ValueError as exc: + logger.error(f"Invalid ticket: {exc}") + stream.send_reset(3) + return + + stream.conn.send(stream.stream_id, b"", end_stream=True) diff --git a/pyjamaz/transport/jamnp_s/streams/ce132.py b/pyjamaz/transport/jamnp_s/streams/ce132.py new file mode 100644 index 00000000..4c747de1 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce132.py @@ -0,0 +1,105 @@ +from __future__ import annotations + +import asyncio +import logging +from dataclasses import dataclass + +from jamcodec.base import JamBytes + +from pyjamaz.graypaper_constants import EPOCH_TIMESLOTS +from pyjamaz.models.block import TicketEnvelope +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import MsgCE132SafroleTicketDistribution +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +@dataclass +class CE132StreamState: + active: bool = True + + +class CE132Handler(ContextualStreamHandler): + kind = StreamKind.CE132_SafroleTicketDistributionStep2 + + def __init__(self, context) -> None: + super().__init__(context) + self._streams = {} + + def init_stream(self, stream: ManagedStream) -> None: + self._streams[stream.stream_key] = CE132StreamState() + + def initiate_ticket_distribution(self, conn, msg: MsgCE132SafroleTicketDistribution) -> ManagedStream: + stream = self.open_outgoing(conn) + logger.info(f"Distribute ticket on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + conn.send( + stream.stream_id, + stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.warning(f"Unexpected data in CE132 initiator: {len(data)} bytes") + raise ValueError("Unexpected data in CE132 initiator") + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug(f"CE132 acceptor stream {stream.stream_id} received ticket") + self._handle_received_ticket( + stream, + MsgCE132SafroleTicketDistribution.from_jam_bytes(JamBytes(data)), + ) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.debug("Success with code 0") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.debug("Success with code 0") + + def initiator_reset(self, stream: ManagedStream, reset_code: int) -> None: + logger.error(f"Failed with code {reset_code}") + + def acceptor_reset(self, stream: ManagedStream, reset_code: int) -> None: + logger.error(f"Failed with code {reset_code}") + + def on_close(self, stream: ManagedStream) -> None: + self._streams.pop(stream.stream_key, None) + + def _handle_received_ticket(self, stream: ManagedStream, msg: MsgCE132SafroleTicketDistribution) -> None: + logger.info(f"Received ticket for epoch {msg.epoch_index}") + + current_epoch = self.context.app.working_state.timeslot.number // EPOCH_TIMESLOTS + if msg.epoch_index < current_epoch: + logger.warning(f"Invalid epoch index {msg.epoch_index}, current epoch is {current_epoch}") + stream.send_reset(1) + return + + try: + ring_public_keys = [v.bandersnatch for v in self.context.app.working_state.safrole.validators] + entropy = self.context.app.working_state.entropy.entropy[2] + ticket_envelope = TicketEnvelope(attempt=msg.ticket.attempt, signature=msg.ticket.proof) + ticket_body = self.context.app.block_extrinsic.create_ticket_body( + ticket_envelope, + ring_public_keys, + entropy, + ) + + if ticket_body in self.context.app.working_state.safrole.ticket_accumulator: + logger.info("Ticket already in accumulator (via CE132), ignoring") + stream.conn.send(stream.stream_id, b"", end_stream=True) + return + + asyncio.create_task( + self.context.app.block_extrinsic.add_ticket_body( + ticket_envelope, + ticket_body, + ) + ) + logger.info(f"Valid ticket received via CE132 (forwarded) for epoch {msg.epoch_index}") + except ValueError as exc: + logger.error(f"Invalid ticket: {exc}") + stream.send_reset(3) + return + + stream.conn.send(stream.stream_id, b"", end_stream=True) diff --git a/pyjamaz/transport/jamnp_s/streams/ce133.py b/pyjamaz/transport/jamnp_s/streams/ce133.py new file mode 100644 index 00000000..b68fec4c --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce133.py @@ -0,0 +1,93 @@ +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import List, Optional + +from jamcodec.base import JamBytes + +from pyjamaz.hashing import blake2b_256_hash +from pyjamaz.models.common import WorkPackage +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import MsgCE133Extrinsic, MsgCE133WorkPackageSubmission +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +@dataclass +class CE133StreamState: + work_package: Optional[WorkPackage] = None + + +class CE133Handler(ContextualStreamHandler): + kind = StreamKind.CE133_WorkPackageSubmission + + def __init__(self, context) -> None: + super().__init__(context) + self._streams = {} + + def init_stream(self, stream: ManagedStream) -> None: + self._streams[stream.stream_key] = CE133StreamState() + + def initiate_workpackage_submission( + self, + conn, + wp: MsgCE133WorkPackageSubmission, + extrinsic: MsgCE133Extrinsic, + ) -> ManagedStream: + stream = self.open_outgoing(conn) + logger.info(f"Initiating workpackage submission on stream id: {stream.stream_id}") + conn.send( + stream.stream_id, + stream.create_message(wp.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=False, + ) + conn.send( + stream.stream_id, + stream.create_message(extrinsic.to_jam_bytes().to_bytes()), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.warning(f"Unexpected data in CE133 initiator: {len(data)} bytes") + raise ValueError("Unexpected data in CE133 initiator") + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + state = self._streams[stream.stream_key] + + if state.work_package is None: + logger.debug("CE133 acceptor received work package") + msg = MsgCE133WorkPackageSubmission.from_jam_bytes(JamBytes(data)) + state.work_package = msg.work_package + return + + logger.debug("CE133 acceptor received extrinsic data") + extrinsics_list: List[bytes] = [] + extrinsics_data = JamBytes(data) + + for item in state.work_package.items: + for ext in item.extrinsic: + ext_data = extrinsics_data.get_next_bytes(ext.len) + if blake2b_256_hash(ext_data) != ext.hash: + raise ValueError("Extrinsic hash mismatch") + extrinsics_list.append(ext_data) + + stream.conn.send(stream.stream_id, b"", end_stream=True) + self.context.app.add_work_package(state.work_package, extrinsics_list) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.debug("CE133 submission successful with FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.debug("CE133 submission successful with FIN") + + def initiator_reset(self, stream: ManagedStream, reset_code: int) -> None: + logger.debug(f"CE133 received reset code: {reset_code}") + + def acceptor_reset(self, stream: ManagedStream, reset_code: int) -> None: + logger.debug(f"CE133 received reset code: {reset_code}") + + def on_close(self, stream: ManagedStream) -> None: + self._streams.pop(stream.stream_key, None) diff --git a/pyjamaz/transport/jamnp_s/streams/ce134.py b/pyjamaz/transport/jamnp_s/streams/ce134.py new file mode 100644 index 00000000..edc84020 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce134.py @@ -0,0 +1,109 @@ +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import Optional + +from jamcodec.base import JamBytes + +from pyjamaz.models.common import WorkPackage +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import ( + MsgCE134RefineResponse, + MsgCE134WorkPackageBundle, + MsgCE134WorkPackageSharing, +) +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +@dataclass +class CE134StreamState: + received_mappings: bool = False + sharing_msg: Optional[MsgCE134WorkPackageSharing] = None + + +class CE134Handler(ContextualStreamHandler): + kind = StreamKind.CE134_WorkPackageSharing + + def __init__(self, context) -> None: + super().__init__(context) + self._streams = {} + + def init_stream(self, stream: ManagedStream) -> None: + self._streams[stream.stream_key] = CE134StreamState() + + def initiate_workpackage_sharing( + self, + conn, + sharing: MsgCE134WorkPackageSharing, + bundle: MsgCE134WorkPackageBundle, + ) -> ManagedStream: + stream = self.open_outgoing(conn) + logger.info(f"Initiate sharing workpage on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + conn.send( + stream.stream_id, + stream.create_message(sharing.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=False, + ) + conn.send( + stream.stream_id, + stream.create_message(bundle.to_jam_bytes().to_bytes()), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + state = self._streams[stream.stream_key] + if state.received_mappings: + logger.warning(f"Unexpected data in CE134 initiator after mappings: {len(data)} bytes") + raise ValueError("Unexpected data in CE134 initiator after mappings") + + logger.debug("CE134 initiator received refine response") + msg = MsgCE134RefineResponse.from_jam_bytes(JamBytes(data)) + state.received_mappings = True + self._handle_refine_response(stream, msg) + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + state = self._streams[stream.stream_key] + if not state.received_mappings: + logger.debug("CE134 acceptor received mappings") + msg = MsgCE134WorkPackageSharing.from_jam_bytes(JamBytes(data)) + state.received_mappings = True + state.sharing_msg = msg + logger.info(f"ce134_received_workpackage_sharing for core {msg.core_index}") + return + + logger.debug("CE134 acceptor received bundle") + msg = MsgCE134WorkPackageBundle.from_jam_bytes(JamBytes(data)) + self._handle_bundle(stream, msg) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.info("Success with FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.info("Success with FIN") + + def on_close(self, stream: ManagedStream) -> None: + self._streams.pop(stream.stream_key, None) + + def _handle_bundle(self, stream: ManagedStream, msg: MsgCE134WorkPackageBundle) -> None: + logger.info("ce134_received_bundle") + work_report = self.context.app.process_work_package(msg.work_package) + + report_hash = work_report.hash() + signature = self.context.app.config.keys.ed25519.sign(report_hash) + response = MsgCE134RefineResponse(report_hash=report_hash, signature=signature) + stream.conn.send( + stream.stream_id, + stream.create_message(response.to_jam_bytes().to_bytes()), + end_stream=True, + ) + + def _handle_refine_response(self, stream: ManagedStream, msg: MsgCE134RefineResponse) -> None: + logger.info("ce134_received_refine_response") + work_report = None + if work_report is not None: + self.context.app.guarantee_work_report(work_report, self.context.app.current_timeslot()) + stream.conn.send(stream.stream_id, b"", end_stream=True) diff --git a/pyjamaz/transport/jamnp_s/streams/ce135.py b/pyjamaz/transport/jamnp_s/streams/ce135.py new file mode 100644 index 00000000..aa19906c --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce135.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import MsgCE135GuaranteedWorkReport +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class CE135Handler(ContextualStreamHandler): + kind = StreamKind.CE135_WorkReportDistribution + + def initiate_distribution(self, conn, msg: MsgCE135GuaranteedWorkReport) -> ManagedStream: + stream = self.open_outgoing(conn) + logger.info(f"CE135 initiating distribution on stream id: {stream.stream_id}") + conn.send( + stream.stream_id, + stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.warning(f"Unexpected data in CE135 initiator: {len(data)} bytes") + raise ValueError("Unexpected data in CE135 initiator") + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE135 acceptor received work report") + MsgCE135GuaranteedWorkReport.from_jam_bytes(JamBytes(data)) + logger.info("CE135 received work report") + stream.conn.send(stream.stream_id, b"", end_stream=True) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.info("CE135 distribution successful with FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.info("CE135 distribution successful with FIN") diff --git a/pyjamaz/transport/jamnp_s/streams/ce136.py b/pyjamaz/transport/jamnp_s/streams/ce136.py new file mode 100644 index 00000000..e0bd5259 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce136.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import MsgCE136HashRequest, MsgCE136WorkReport +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class CE136Handler(ContextualStreamHandler): + kind = StreamKind.CE136_WorkReportRequest + + def initiate_request(self, conn, msg: MsgCE136HashRequest) -> ManagedStream: + stream = self.open_outgoing(conn) + logger.info(f"CE136 initiating request on stream id: {stream.stream_id}") + conn.send( + stream.stream_id, + stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE136 initiator received work report") + msg = MsgCE136WorkReport.from_jam_bytes(JamBytes(data)) + logger.info(f"CE136 received work report of length {len(msg.report)}") + stream.conn.send(stream.stream_id, b"", end_stream=True) + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE136 acceptor received request") + msg = MsgCE136HashRequest.from_jam_bytes(JamBytes(data)) + logger.info(f"CE136 received request for hash {msg.hash.hex()}") + report = MsgCE136WorkReport(report=b"") + stream.conn.send( + stream.stream_id, + stream.create_message(report.to_jam_bytes().to_bytes()), + end_stream=True, + ) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.debug("CE136 request successful with FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.debug("CE136 request successful with FIN") diff --git a/pyjamaz/transport/jamnp_s/streams/ce137.py b/pyjamaz/transport/jamnp_s/streams/ce137.py new file mode 100644 index 00000000..65af4471 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce137.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import ( + MsgCE137BundleShard, + MsgCE137Justification, + MsgCE137SegmentShard, + MsgCE137ShardRequest, +) +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class CE137Handler(ContextualStreamHandler): + kind = StreamKind.CE137_ShardDistribution + + def initiate_request(self, conn, req: MsgCE137ShardRequest) -> ManagedStream: + stream = self.open_outgoing(conn) + conn.send( + stream.stream_id, + stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + return stream + + @staticmethod + def _chunks(data: bytes, chunk_size: int): + for idx in range(0, len(data), chunk_size): + yield data[idx : idx + chunk_size] + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE137 initiator received response") + bundle = MsgCE137BundleShard.from_jam_bytes(JamBytes(data[: len(data) // 3])) + segments = [ + MsgCE137SegmentShard.from_jam_bytes(JamBytes(chunk)) + for chunk in self._chunks(data, max(1, len(data) // 3)) + ] + just = MsgCE137Justification.from_jam_bytes(JamBytes(data[-len(data) // 3 :])) + stream.conn.send(stream.stream_id, b"", end_stream=True) + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE137 acceptor received request") + MsgCE137ShardRequest.from_jam_bytes(JamBytes(data)) + bundle = MsgCE137BundleShard(bytes_=b"") + segments = [MsgCE137SegmentShard(bytes_=b"") for _ in range(10)] + just = MsgCE137Justification(bytes_=b"") + stream.conn.send( + stream.stream_id, + stream.create_message(bundle.to_jam_bytes().to_bytes()), + end_stream=False, + ) + for segment in segments: + stream.conn.send( + stream.stream_id, + stream.create_message(segment.to_jam_bytes().to_bytes()), + end_stream=False, + ) + stream.conn.send( + stream.stream_id, + stream.create_message(just.to_jam_bytes().to_bytes()), + end_stream=True, + ) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.info("CE137 success with FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.info("CE137 success with FIN") diff --git a/pyjamaz/transport/jamnp_s/streams/ce138.py b/pyjamaz/transport/jamnp_s/streams/ce138.py new file mode 100644 index 00000000..45f11b15 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce138.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import ( + MsgCE138BundleShard, + MsgCE138Justification, + MsgCE138ShardRequest, +) +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class CE138Handler(ContextualStreamHandler): + kind = StreamKind.CE138_AuditShardRequest + + def initiate_request(self, conn, req: MsgCE138ShardRequest) -> ManagedStream: + stream = self.open_outgoing(conn) + conn.send( + stream.stream_id, + stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE138 initiator received response") + bundle = MsgCE138BundleShard.from_jam_bytes(JamBytes(data[: len(data) // 2])) + just = MsgCE138Justification.from_jam_bytes(JamBytes(data[len(data) // 2 :])) + stream.conn.send(stream.stream_id, b"", end_stream=True) + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE138 acceptor received request") + MsgCE138ShardRequest.from_jam_bytes(JamBytes(data)) + bundle = MsgCE138BundleShard(bytes_=b"") + just = MsgCE138Justification(bytes_=b"") + stream.conn.send( + stream.stream_id, + stream.create_message(bundle.to_jam_bytes().to_bytes()), + end_stream=False, + ) + stream.conn.send( + stream.stream_id, + stream.create_message(just.to_jam_bytes().to_bytes()), + end_stream=True, + ) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.info("CE138 success FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.info("CE138 success FIN") diff --git a/pyjamaz/transport/jamnp_s/streams/ce139.py b/pyjamaz/transport/jamnp_s/streams/ce139.py new file mode 100644 index 00000000..53fbfe24 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce139.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import MsgCE139SegmentRequest, MsgCE139SegmentShard +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class CE139Handler(ContextualStreamHandler): + kind = StreamKind.CE139_SegmentShardRequest + + def initiate_request(self, conn, req: MsgCE139SegmentRequest) -> ManagedStream: + stream = self.open_outgoing(conn) + conn.send( + stream.stream_id, + stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE139 initiator received shards") + chunk_size = max(1, len(data)) + [ + MsgCE139SegmentShard.from_jam_bytes(JamBytes(chunk)) + for chunk in self._chunks(data, chunk_size) + ] + stream.conn.send(stream.stream_id, b"", end_stream=True) + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE139 acceptor received request") + msg = MsgCE139SegmentRequest.from_jam_bytes(JamBytes(data)) + shards = [MsgCE139SegmentShard(bytes_=b"") for _ in msg.segment_indices] + for shard in shards: + stream.conn.send( + stream.stream_id, + stream.create_message(shard.to_jam_bytes().to_bytes()), + end_stream=(shard == shards[-1]), + ) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.info("CE139 success with FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.info("CE139 success with FIN") + + @staticmethod + def _chunks(data: bytes, chunk_size: int): + for idx in range(0, len(data), chunk_size): + yield data[idx : idx + chunk_size] diff --git a/pyjamaz/transport/jamnp_s/streams/ce140.py b/pyjamaz/transport/jamnp_s/streams/ce140.py new file mode 100644 index 00000000..a0a4c2fe --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce140.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import ( + MsgCE140Justification, + MsgCE140SegmentRequest, + MsgCE140SegmentShard, +) +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class CE140Handler(ContextualStreamHandler): + kind = StreamKind.CE140_SegmentShardRequestJustification + + def initiate_request(self, conn, req: MsgCE140SegmentRequest) -> ManagedStream: + stream = self.open_outgoing(conn) + conn.send( + stream.stream_id, + stream.create_message(req.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE140 initiator received shards and justifications") + stream.conn.send(stream.stream_id, b"", end_stream=True) + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE140 acceptor received request") + msg = MsgCE140SegmentRequest.from_jam_bytes(JamBytes(data)) + for idx in msg.segment_indices: + shard = MsgCE140SegmentShard(bytes_=b"") + just = MsgCE140Justification(bytes_=b"") + stream.conn.send( + stream.stream_id, + stream.create_message(shard.to_jam_bytes().to_bytes()), + end_stream=False, + ) + stream.conn.send( + stream.stream_id, + stream.create_message(just.to_jam_bytes().to_bytes()), + end_stream=(idx == msg.segment_indices[-1]), + ) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.info("CE140 success with FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.info("CE140 success with FIN") diff --git a/pyjamaz/transport/jamnp_s/streams/ce141.py b/pyjamaz/transport/jamnp_s/streams/ce141.py new file mode 100644 index 00000000..2ee33f6e --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce141.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import MsgCE141Assurance +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class CE141Handler(ContextualStreamHandler): + kind = StreamKind.CE141_AssuranceDistribution + + def initiate_distribution(self, conn, msg: MsgCE141Assurance) -> ManagedStream: + stream = self.open_outgoing(conn) + logger.info(f"Initiating assurance distribution on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + conn.send( + stream.stream_id, + stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.warning(f"Unexpected data in CE141 initiator: {len(data)} bytes") + raise ValueError("Unexpected data in CE141 initiator") + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE141 acceptor received assurance") + MsgCE141Assurance.from_jam_bytes(JamBytes(data)) + logger.info("Received assurance") + stream.conn.send(stream.stream_id, b"", end_stream=True) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.info("Success with code with FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.info("Success with code with FIN") diff --git a/pyjamaz/transport/jamnp_s/streams/ce142.py b/pyjamaz/transport/jamnp_s/streams/ce142.py new file mode 100644 index 00000000..cbd95263 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce142.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import MsgCE142PreimageAnnouncement +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class CE142Handler(ContextualStreamHandler): + kind = StreamKind.CE142_PreimageAnnouncement + + def initiate_announcement(self, conn, msg: MsgCE142PreimageAnnouncement) -> ManagedStream: + stream = self.open_outgoing(conn) + logger.info(f"Initiating preimage announcement on stream id: {stream.stream_id} to {conn.host}:{conn.port}") + conn.send( + stream.stream_id, + stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + if len(data) == 0: + return + logger.warning(f"Unexpected data in CE142 initiator: {len(data)} bytes") + raise ValueError("Unexpected data in CE142 initiator") + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE142 acceptor received preimage announcement") + msg = MsgCE142PreimageAnnouncement.from_jam_bytes(JamBytes(data)) + logger.info(f"Received preimage announcement for hash {msg.hash.hex()}") + stream.conn.send(stream.stream_id, b"", end_stream=True) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.info("Success with code with FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.info("Success with code with FIN") diff --git a/pyjamaz/transport/jamnp_s/streams/ce143.py b/pyjamaz/transport/jamnp_s/streams/ce143.py new file mode 100644 index 00000000..d476eadb --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce143.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import MsgCE143HashRequest, MsgCE143Preimage +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class CE143Handler(ContextualStreamHandler): + kind = StreamKind.CE143_PreimageRequest + + def initiate_request(self, conn, msg: MsgCE143HashRequest) -> ManagedStream: + stream = self.open_outgoing(conn) + logger.info(f"CE143 initiating request on stream id: {stream.stream_id}") + conn.send( + stream.stream_id, + stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE143 initiator received preimage") + msg = MsgCE143Preimage.from_jam_bytes(JamBytes(data)) + logger.info(f"CE143 received preimage of length {len(msg.bytes_)}") + stream.conn.send(stream.stream_id, b"", end_stream=True) + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE143 acceptor received preimage request") + msg = MsgCE143HashRequest.from_jam_bytes(JamBytes(data)) + logger.info(f"CE143 received request for hash {msg.hash.hex()}") + preimage = MsgCE143Preimage(bytes_=b"") + stream.conn.send( + stream.stream_id, + stream.create_message(preimage.to_jam_bytes().to_bytes()), + end_stream=True, + ) + + def initiator_fin(self, stream: ManagedStream) -> None: + self._request_success(0) + + def acceptor_fin(self, stream: ManagedStream) -> None: + self._request_success(0) + + def initiator_reset(self, stream: ManagedStream, reset_code: int) -> None: + self._request_failure(reset_code) + + def acceptor_reset(self, stream: ManagedStream, reset_code: int) -> None: + self._request_failure(reset_code) + + @staticmethod + def _request_success(reset_code: int) -> None: + logger.info(f"CE143 request successful with code {reset_code}") + + @staticmethod + def _request_failure(reset_code: int) -> None: + logger.debug(f"CE143 received reset code: {reset_code}") + logger.error(f"CE143 request failed with code {reset_code}") diff --git a/pyjamaz/transport/jamnp_s/streams/ce144.py b/pyjamaz/transport/jamnp_s/streams/ce144.py new file mode 100644 index 00000000..ba8b5e32 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce144.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +import logging +from dataclasses import dataclass + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import MsgCE144Announcement, MsgCE144Evidence +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +@dataclass +class CE144StreamState: + received_announcement: bool = False + + +class CE144Handler(ContextualStreamHandler): + kind = StreamKind.CE144_AuditAnnouncement + + def __init__(self, context) -> None: + super().__init__(context) + self._streams = {} + + def init_stream(self, stream: ManagedStream) -> None: + self._streams[stream.stream_key] = CE144StreamState() + + def initiate_announcement( + self, + conn, + ann: MsgCE144Announcement, + evidence: MsgCE144Evidence, + ) -> ManagedStream: + stream = self.open_outgoing(conn) + conn.send( + stream.stream_id, + stream.create_message(ann.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=False, + ) + conn.send( + stream.stream_id, + stream.create_message(evidence.to_jam_bytes().to_bytes()), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.warning(f"Unexpected data in CE144 initiator: {len(data)} bytes") + raise ValueError("Unexpected data in CE144 initiator") + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + state = self._streams[stream.stream_key] + + if not state.received_announcement: + logger.debug("CE144 acceptor received announcement") + MsgCE144Announcement.from_jam_bytes(JamBytes(data)) + state.received_announcement = True + return + + logger.debug("CE144 acceptor received evidence") + MsgCE144Evidence.from_jam_bytes(JamBytes(data)) + stream.conn.send(stream.stream_id, b"", end_stream=True) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.info("CE144 success with FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.info("CE144 success with FIN") + + def on_close(self, stream: ManagedStream) -> None: + self._streams.pop(stream.stream_key, None) diff --git a/pyjamaz/transport/jamnp_s/streams/ce145.py b/pyjamaz/transport/jamnp_s/streams/ce145.py new file mode 100644 index 00000000..7631edb5 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/ce145.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +import logging + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import MsgCE145JudgmentPublication +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class CE145Handler(ContextualStreamHandler): + kind = StreamKind.CE145_JudgmentPublication + + def initiate_publication(self, conn, msg: MsgCE145JudgmentPublication) -> ManagedStream: + stream = self.open_outgoing(conn) + conn.send( + stream.stream_id, + stream.create_message(msg.to_jam_bytes().to_bytes(), add_stream_type=True), + end_stream=True, + ) + return stream + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + logger.warning(f"Unexpected data in CE145 initiator: {len(data)} bytes") + raise ValueError("Unexpected data in CE145 initiator") + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + logger.debug("CE145 acceptor received judgment") + MsgCE145JudgmentPublication.from_jam_bytes(JamBytes(data)) + stream.conn.send(stream.stream_id, b"", end_stream=True) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.info("CE145 success with FIN") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.info("CE145 success with FIN") diff --git a/pyjamaz/transport/jamnp_s/streams/context.py b/pyjamaz/transport/jamnp_s/streams/context.py new file mode 100644 index 00000000..e780fcda --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/context.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Dict + +from pyjamaz.transport.jamnp_s.types import StreamKind + +if TYPE_CHECKING: + from pyjamaz.app import PyjamazApp + from pyjamaz.transport.jamnp_s.peers import PeerRegistry + from pyjamaz.transport.jamnp_s.stream_manager import StreamManager + from pyjamaz.transport.jamnp_s.validator_manager import ValidatorConnectionManager + + +@dataclass +class ProtocolSharedState: + state_requesting_blocks: bool = False + state_warp_sync: bool = True + + +@dataclass +class ProtocolContext: + app: "PyjamazApp" + peer_registry: "PeerRegistry" + validator_manager: "ValidatorConnectionManager" + stream_manager: "StreamManager" + state: ProtocolSharedState + handlers: Dict[StreamKind, object] = field(default_factory=dict) + + @property + def connections(self): + return self.peer_registry.connections + + @property + def state_requesting_blocks(self) -> bool: + return self.state.state_requesting_blocks + + @state_requesting_blocks.setter + def state_requesting_blocks(self, value: bool) -> None: + self.state.state_requesting_blocks = value + + @property + def state_warp_sync(self) -> bool: + return self.state.state_warp_sync + + @state_warp_sync.setter + def state_warp_sync(self, value: bool) -> None: + self.state.state_warp_sync = value + + def register_handler(self, handler) -> None: + self.handlers[handler.kind] = handler + + def get_handler(self, kind: StreamKind): + return self.handlers[kind] diff --git a/pyjamaz/transport/jamnp_s/streams/stream_0.py b/pyjamaz/transport/jamnp_s/streams/stream_0.py deleted file mode 100644 index 576e7a62..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_0.py +++ /dev/null @@ -1,76 +0,0 @@ -import enum -import logging - -from jamcodec.base import JamBytes -from jamcodec.types import U32, VarInt64 - -from pyjamaz.transport.jamnp_s.message_types import MsgUP0Handshake, MsgUP0Leaf, MsgUP0Announcement -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class UPState(enum.Enum): - IN_PROGRESS = 0 - CONNECTED = 1 - - -class StreamUP(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.UP0_BlockAnnouncement.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - self.state = UPState.IN_PROGRESS - - - def initiator_message(self, data: bytes): - - match self.state: - - case UPState.IN_PROGRESS: - try: - self.state = UPState.CONNECTED - - msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) - self.protocol.up0_received_handshake(self.conn, msg) - - # Note: exception when received as an acceptor stream - if self.direction == StreamDirection.acceptor: - self.protocol.up0_send_handshake(self.conn) - - except Exception as e: - logger.error(f"Error processing handshake in initiator: {e}", exc_info=True) - raise - - case UPState.CONNECTED: - # After handshake is completed, we receive Announcement messages - msg = MsgUP0Announcement.from_jam_bytes(JamBytes(data)) - self.protocol.up0_received_announcement(self.conn, msg) - - case _: - raise RuntimeError(f"Unexpected state {self.state}") - - - def initiator_reset(self, reset_code: int): - # TODO: handle UP0 reset, e.g., reconnect - #self.protocol.up0_failure(reset_code, self.direction) - pass - - - def acceptor_reset(self, reset_code: int): - # TODO: handle UP0 reset, e.g., reconnect - #self.protocol.up0_failure(reset_code, self.direction) - pass - - - def acceptor_message(self, data: bytes): - # Note: in case of UP0, initiator and acceptor have symmetrical message flow - # (except for sending a handshake response, which is checked using the connection direction) - self.initiator_message(data) - - - def handle_fin(self): - logger.warning(f"Unexpected FIN on persistent UP0 stream {self.stream_id}") - # TODO: reset or ignore? diff --git a/pyjamaz/transport/jamnp_s/streams/stream_128.py b/pyjamaz/transport/jamnp_s/streams/stream_128.py deleted file mode 100644 index 16a6aa83..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_128.py +++ /dev/null @@ -1,40 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE128BlockRequest, MsgCE128BlockRequestResponse -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamBlockRequest(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE128_BlockRequest.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - self.protocol.ce128_finish_block_request(reset_code) - - - def acceptor_reset(self, reset_code: int): - self.protocol.ce128_finish_block_request(reset_code) - - - def initiator_message(self, data: bytes): - logger.debug(f"CE128 initiated stream {self.stream_id} received block request response: {len(data)} bytes") - req = MsgCE128BlockRequestResponse.from_jam_bytes(JamBytes(data)) - self.protocol.ce128_received_block_request(self, req) - - - def handle_fin(self): - super().handle_fin() - self.protocol.ce128_abort_block_request() # Or success callback - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE128 acceptor stream {self.stream_id} received block request") - self.protocol.ce128_send_block_request(self, MsgCE128BlockRequest.from_jam_bytes(JamBytes(data))) diff --git a/pyjamaz/transport/jamnp_s/streams/stream_129.py b/pyjamaz/transport/jamnp_s/streams/stream_129.py deleted file mode 100644 index b6c7c357..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_129.py +++ /dev/null @@ -1,28 +0,0 @@ -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - - -class StreamStateRequest(Stream): - - BOUNDARY_NODE = 0 - KEY_VALUE = 1 - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE129_StateRequest.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - self.state = self.BOUNDARY_NODE - - - def initiate_state_request(self, header_hash, direction, max_blocks): - pass - - - def initiator_message(self, data: bytes): - if self.state == self.BOUNDARY_NODE: - pass - elif self.state == self.KEY_VALUE: - pass - - - def acceptor_message(self, data: bytes): - pass \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_131.py b/pyjamaz/transport/jamnp_s/streams/stream_131.py deleted file mode 100644 index 3206d3d8..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_131.py +++ /dev/null @@ -1,40 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE131SafroleTicketDistribution -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamSafroleTicketDistributionStep1(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE131_SafroleTicketDistributionStep1.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - pass - - - def initiator_message(self, data: bytes): - logger.warning(f"Unexpected data in CE131 initiator: {len(data)} bytes") - self.handle_error(None) - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE131 acceptor stream {self.stream_id} received ticket") - msg = MsgCE131SafroleTicketDistribution.from_jam_bytes(JamBytes(data)) - self.protocol.ce131_received_ticket(self, msg) - - - def acceptor_reset(self, reset_code: int): - pass - - - def handle_fin(self): - super().handle_fin() - logger.info(f"Finished with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_132.py b/pyjamaz/transport/jamnp_s/streams/stream_132.py deleted file mode 100644 index 5dd055cb..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_132.py +++ /dev/null @@ -1,42 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE132SafroleTicketDistribution -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamSafroleTicketDistributionStep2(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE132_SafroleTicketDistributionStep2.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - logger.debug(f"CE132 received reset code: {reset_code}") - self.protocol.ce132_ticket_distribution_failure(reset_code) - - - def initiator_message(self, data: bytes): - logger.warning(f"Unexpected data in CE132 initiator: {len(data)} bytes") - self.handle_error("Unexpected data", 1) - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE132 acceptor stream {self.stream_id} received ticket") - msg = MsgCE132SafroleTicketDistribution.from_jam_bytes(JamBytes(data)) - self.protocol.ce132_received_ticket(self, msg) - - - def acceptor_reset(self, reset_code: int): - logger.debug(f"CE132 received reset code: {reset_code}") - self.protocol.ce132_ticket_distribution_failure(reset_code) - - - def handle_fin(self): - super().handle_fin() - self.protocol.ce132_ticket_distribution_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_133.py b/pyjamaz/transport/jamnp_s/streams/stream_133.py deleted file mode 100644 index 99e4625f..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_133.py +++ /dev/null @@ -1,65 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.hashing import blake2b_256_hash -from pyjamaz.transport.jamnp_s.message_types import MsgCE133WorkPackageSubmission, MsgCE133Extrinsic -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamWorkPackageSubmission(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE133_WorkPackageSubmission.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - self.received_wp = None - - - def initiator_reset(self, reset_code: int): - logger.debug(f"CE133 received reset code: {reset_code}") - #self.protocol.ce133_submission_failure(reset_code) - self.received_wp = None - - - def initiator_message(self, data: bytes): - logger.warning(f"Unexpected data in CE133 initiator: {len(data)} bytes") - self.handle_error("Unexpected data", 1) - - - def acceptor_reset(self, reset_code: int): - logger.debug(f"CE133 received reset code: {reset_code}") - #self.protocol.ce133_submission_failure(reset_code) - self.received_wp = None - - - def acceptor_message(self, data: bytes): - if self.received_wp is None: - logger.debug(f"CE133 acceptor received work package") - msg = MsgCE133WorkPackageSubmission.from_jam_bytes(JamBytes(data)) - self.received_wp = msg.work_package - #self.protocol.ce133_received_workpackage_submission(self, msg) #TODO: do we need this event at protocol level? - else: - logger.debug(f"CE133 acceptor received extrinsic data") - - extrinsics_data = JamBytes(data) - extriniscs_list = [] - - for item in self.received_wp.items: - for ext in item.extrinsic: - ext_data = extrinsics_data.get_next_bytes(ext.len) - if blake2b_256_hash(ext_data) != ext.hash: - #TODO: handle at protocol level - raise ValueError("Extrinsic hash mismatch") - - extriniscs_list.append(ext_data) - - self.protocol.ce133_received_extrinsic_submission(self, self.received_wp, extriniscs_list) - - - def handle_fin(self): - super().handle_fin() - self.received_wp = None - self.protocol.ce133_submission_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_134.py b/pyjamaz/transport/jamnp_s/streams/stream_134.py deleted file mode 100644 index 12ccbc6f..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_134.py +++ /dev/null @@ -1,53 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE134WorkPackageSharing, MsgCE134WorkPackageBundle, MsgCE134RefineResponse -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamWorkPackageSharing(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE134_WorkPackageSharing.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - self.received_mappings = False - - - def initiator_reset(self, reset_code: int): - pass - - - def initiator_message(self, data: bytes): - if not self.received_mappings: - logger.debug(f"CE134 initiator received refine response") - msg = MsgCE134RefineResponse.from_jam_bytes(JamBytes(data)) - self.protocol.ce134_received_refine_response(self, msg) - self.received_mappings = True - else: - logger.warning(f"Unexpected data in CE134 initiator after mappings: {len(data)} bytes") - self.handle_error("Unexpected data after mappings", 1) - - - def acceptor_reset(self, reset_code: int): - pass - - - def acceptor_message(self, data: bytes): - if not self.received_mappings: - logger.debug(f"CE134 acceptor received mappings") - msg = MsgCE134WorkPackageSharing.from_jam_bytes(JamBytes(data)) - self.protocol.ce134_received_workpackage_sharing(self, msg) - self.received_mappings = True - else: - logger.debug(f"CE134 acceptor received bundle") - msg = MsgCE134WorkPackageBundle.from_jam_bytes(JamBytes(data)) - self.protocol.ce134_received_bundle(self, msg) - - - def handle_fin(self): - super().handle_fin() - logger.info(f"Success with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_135.py b/pyjamaz/transport/jamnp_s/streams/stream_135.py deleted file mode 100644 index d01094c5..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_135.py +++ /dev/null @@ -1,40 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE135GuaranteedWorkReport -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamWorkReportDistribution(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE135_WorkReportDistribution.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - pass - - - def initiator_message(self, data: bytes): - logger.warning(f"Unexpected data in CE135 initiator: {len(data)} bytes") - self.handle_error("Unexpected data", 1) - - - def acceptor_reset(self, reset_code: int): - pass - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE135 acceptor received work report") - msg = MsgCE135GuaranteedWorkReport.from_jam_bytes(JamBytes(data)) - self.protocol.ce135_received_report(self, msg) - - - def handle_fin(self): - super().handle_fin() - logger.info(f"CE135 distribution successful with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_136.py b/pyjamaz/transport/jamnp_s/streams/stream_136.py deleted file mode 100644 index ae1c8258..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_136.py +++ /dev/null @@ -1,41 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE136HashRequest, MsgCE136WorkReport -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamWorkReportRequest(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE136_WorkReportRequest.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - pass - - - def initiator_message(self, data: bytes): - logger.debug(f"CE136 initiator received work report") - msg = MsgCE136WorkReport.from_jam_bytes(JamBytes(data)) - self.protocol.ce136_received_report(self, msg) - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE136 acceptor received request") - msg = MsgCE136HashRequest.from_jam_bytes(JamBytes(data)) - self.protocol.ce136_received_request(self, msg) - - - def acceptor_reset(self, reset_code: int): - pass - - - def handle_fin(self): - super().handle_fin() - logger.debug(f"CE136 request successful with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_137.py b/pyjamaz/transport/jamnp_s/streams/stream_137.py deleted file mode 100644 index 934f95fb..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_137.py +++ /dev/null @@ -1,50 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE137ShardRequest, MsgCE137BundleShard, MsgCE137SegmentShard, MsgCE137Justification -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamShardDistribution(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE137_ShardDistribution.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - pass - - - def _chunks(self, data: bytes, chunk_size: int): - """Yield successive n-sized chunks from data.""" - for i in range(0, len(data), chunk_size): - yield data[i:i + chunk_size] - - - def initiator_message(self, data: bytes): - logger.debug(f"CE137 initiator received response") - # Parse bundle shard, segment shards, justification - bundle = MsgCE137BundleShard.from_jam_bytes(JamBytes(data[:len(data)//3])) # Simplified parsing - segments = [MsgCE137SegmentShard.from_jam_bytes(JamBytes(d)) for d in self._chunks(data, len(data)//3)] - just = MsgCE137Justification.from_jam_bytes(JamBytes(data[-len(data)//3:])) - self.protocol.ce137_received_shard(self, bundle, segments, just) - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE137 acceptor received request") - msg = MsgCE137ShardRequest.from_jam_bytes(JamBytes(data)) - self.protocol.ce137_received_request(self, msg) - - - def acceptor_reset(self, reset_code: int): - pass - - - def handle_fin(self): - super().handle_fin() - logger.info(f"CE137 success with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_138.py b/pyjamaz/transport/jamnp_s/streams/stream_138.py deleted file mode 100644 index 54f94bd8..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_138.py +++ /dev/null @@ -1,42 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE138ShardRequest, MsgCE138BundleShard, MsgCE138Justification -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamAuditShardRequest(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE138_AuditShardRequest.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - pass - - - def initiator_message(self, data: bytes): - logger.debug(f"CE138 initiator received response") - bundle = MsgCE138BundleShard.from_jam_bytes(JamBytes(data[:len(data)//2])) - just = MsgCE138Justification.from_jam_bytes(JamBytes(data[len(data)//2:])) - self.protocol.ce138_received_shard(self, bundle, just) - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE138 acceptor received request") - msg = MsgCE138ShardRequest.from_jam_bytes(JamBytes(data)) - self.protocol.ce138_received_request(self, msg) - - - def acceptor_reset(self, reset_code: int): - pass - - - def handle_fin(self): - super().handle_fin() - logger.info(f"CE138 success FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_139.py b/pyjamaz/transport/jamnp_s/streams/stream_139.py deleted file mode 100644 index 946b4990..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_139.py +++ /dev/null @@ -1,41 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE139SegmentRequest, MsgCE139SegmentShard -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamSegmentShardRequest(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE139_SegmentShardRequest.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - pass - - - def initiator_message(self, data: bytes): - logger.debug(f"CE139 initiator received shards") - shards = [MsgCE139SegmentShard.from_jam_bytes(JamBytes(d)) for d in chunks(data, 4104 // R)] # R from spec - self.protocol.ce139_received_shards(self, shards) - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE139 acceptor received request") - msg = MsgCE139SegmentRequest.from_jam_bytes(JamBytes(data)) - self.protocol.ce139_received_request(self, msg) - - - def acceptor_reset(self, reset_code: int): - pass - - - def handle_fin(self): - super().handle_fin() - logger.info(f"CE139 success with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_140.py b/pyjamaz/transport/jamnp_s/streams/stream_140.py deleted file mode 100644 index 61822ba6..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_140.py +++ /dev/null @@ -1,41 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE140SegmentRequest, MsgCE140SegmentShard, MsgCE140Justification -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamSegmentShardRequestJustification(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE140_SegmentShardRequestJustification.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - pass - - - def initiator_message(self, data: bytes): - logger.debug(f"CE140 initiator received shards and justifications") - # Parse shards and per-shard justifications - self.protocol.ce140_received_shards_justified(self, data) # Simplified - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE140 acceptor received request") - msg = MsgCE140SegmentRequest.from_jam_bytes(JamBytes(data)) - self.protocol.ce140_received_request(self, msg) - - - def acceptor_reset(self, reset_code: int): - pass - - - def handle_fin(self): - super().handle_fin() - logger.info(f"CE140 success with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_141.py b/pyjamaz/transport/jamnp_s/streams/stream_141.py deleted file mode 100644 index 5ae78b23..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_141.py +++ /dev/null @@ -1,40 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE141Assurance -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamAssuranceDistribution(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE141_AssuranceDistribution.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - pass - - - def initiator_message(self, data: bytes): - logger.warning(f"Unexpected data in CE141 initiator: {len(data)} bytes") - self.handle_error("Unexpected data", 1) - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE141 acceptor received assurance") - msg = MsgCE141Assurance.from_jam_bytes(JamBytes(data)) - self.protocol.ce141_received_assurance(self, msg) - - - def acceptor_reset(self, reset_code: int): - pass - - - def handle_fin(self): - super().handle_fin() - logger.info(f"Success with code with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_142.py b/pyjamaz/transport/jamnp_s/streams/stream_142.py deleted file mode 100644 index 3b4524bc..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_142.py +++ /dev/null @@ -1,42 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE142PreimageAnnouncement -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamPreimageAnnouncement(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE142_PreimageAnnouncement.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - pass - - - def initiator_message(self, data: bytes): - if len(data) == 0: - return - logger.warning(f"Unexpected data in CE142 initiator: {len(data)} bytes") - self.handle_error("Unexpected data", 1) - - - def acceptor_reset(self, reset_code: int): - pass - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE142 acceptor received preimage announcement") - msg = MsgCE142PreimageAnnouncement.from_jam_bytes(JamBytes(data)) - self.protocol.ce142_received_announcement(self, msg) - - - def handle_fin(self): - super().handle_fin() - logger.info(f"Success with code with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_143.py b/pyjamaz/transport/jamnp_s/streams/stream_143.py deleted file mode 100644 index 948e5328..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_143.py +++ /dev/null @@ -1,43 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE143HashRequest, MsgCE143Preimage -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamPreimageRequest(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE143_PreimageRequest.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - logger.debug(f"CE143 received reset code: {reset_code}") - self.protocol.ce143_request_failure(reset_code) - - - def initiator_message(self, data: bytes): - logger.debug(f"CE143 initiator received preimage") - msg = MsgCE143Preimage.from_jam_bytes(JamBytes(data)) - self.protocol.ce143_received_preimage(self, msg) - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE143 acceptor received preimage request") - msg = MsgCE143HashRequest.from_jam_bytes(JamBytes(data)) - self.protocol.ce143_received_request(self, msg) - - - def acceptor_reset(self, reset_code: int): - logger.debug(f"CE143 received reset code: {reset_code}") - self.protocol.ce143_request_failure(reset_code) - - - def handle_fin(self): - super().handle_fin() - self.protocol.ce143_request_success(0) \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_144.py b/pyjamaz/transport/jamnp_s/streams/stream_144.py deleted file mode 100644 index dbcca941..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_144.py +++ /dev/null @@ -1,47 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE144Announcement, MsgCE144Evidence -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamAuditAnnouncement(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE144_AuditAnnouncement.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - self.received_announcement = False - - - def initiator_reset(self, reset_code: int): - pass - - - def initiator_message(self, data: bytes): - logger.warning(f"Unexpected data in CE144 initiator: {len(data)} bytes") - self.handle_error("Unexpected data", 1) - - - def acceptor_message(self, data: bytes): - if not self.received_announcement: - logger.debug(f"CE144 acceptor received announcement") - msg = MsgCE144Announcement.from_jam_bytes(JamBytes(data)) - self.protocol.ce144_received_announcement(self, msg) - self.received_announcement = True - else: - logger.debug(f"CE144 acceptor received evidence") - msg = MsgCE144Evidence.from_jam_bytes(JamBytes(data)) - self.protocol.ce144_received_evidence(self, msg) - - - def acceptor_reset(self, reset_code: int): - pass - - - def handle_fin(self): - super().handle_fin() - logger.info(f"CE144 success with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/stream_145.py b/pyjamaz/transport/jamnp_s/streams/stream_145.py deleted file mode 100644 index 2978bdfc..00000000 --- a/pyjamaz/transport/jamnp_s/streams/stream_145.py +++ /dev/null @@ -1,40 +0,0 @@ -import logging - -from jamcodec.base import JamBytes - -from pyjamaz.transport.jamnp_s.message_types import MsgCE145JudgmentPublication -from pyjamaz.transport.jamnp_s.stream_base import Stream, StreamType, StreamDirection - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class StreamJudgmentPublication(Stream): - - def __init__(self, stream_id: int, connection, direction: StreamDirection): - super().__init__(stream_id, connection, direction) - self.stream_type = StreamType.CE145_JudgmentPublication.value - self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder='little') - - - def initiator_reset(self, reset_code: int): - pass - - - def initiator_message(self, data: bytes): - logger.warning(f"Unexpected data in CE145 initiator: {len(data)} bytes") - self.handle_error("Unexpected data", 1) - - - def acceptor_message(self, data: bytes): - logger.debug(f"CE145 acceptor received judgment") - msg = MsgCE145JudgmentPublication.from_jam_bytes(JamBytes(data)) - self.protocol.ce145_received_judgment(self, msg) - - - def acceptor_reset(self, reset_code: int): - pass - - - def handle_fin(self): - super().handle_fin() - logger.info(f"CE145 success with FIN") \ No newline at end of file diff --git a/pyjamaz/transport/jamnp_s/streams/up0.py b/pyjamaz/transport/jamnp_s/streams/up0.py new file mode 100644 index 00000000..7d12007b --- /dev/null +++ b/pyjamaz/transport/jamnp_s/streams/up0.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +import enum +import logging +from dataclasses import dataclass + +from jamcodec.base import JamBytes + +from pyjamaz.transport.jamnp_s.connection import JAMConnectionDirection +from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.message_types import ( + MsgCE128BlockRequest, + MsgCE128BlockRequestDirection, + MsgUP0Announcement, + MsgUP0Handshake, +) +from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamDirection, StreamKind + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + + +class UPState(enum.Enum): + IN_PROGRESS = 0 + CONNECTED = 1 + + +@dataclass +class UP0StreamState: + phase: UPState = UPState.IN_PROGRESS + + +class UP0Handler(ContextualStreamHandler): + kind = StreamKind.UP0_BlockAnnouncement + + def __init__(self, context) -> None: + super().__init__(context) + self._streams = {} + + def init_stream(self, stream: ManagedStream) -> None: + self._streams[stream.stream_key] = UP0StreamState() + + def send_handshake(self, conn) -> None: + slot = self.context.app.working_state.timeslot.number + header_hash = self.context.app.retrieve_block_hash(slot) + handshake = MsgUP0Handshake( + header_hash=header_hash, + timeslot=slot, + leafs=[], + ) + logger.info( + f"Send handshake on stream {conn.stream_up.stream_id} to {conn.host}:{conn.port} with hash {header_hash}" + ) + + conn.send( + conn.stream_up.stream_id, + conn.stream_up.create_message( + handshake.to_jam_bytes().to_bytes(), + add_stream_type=conn.direction == JAMConnectionDirection.initiator, + ), + ) + + async def broadcast_block(self, block) -> None: + logger.info(f"Broadcast {block.header.hash.hex()} to {len(self.context.connections)} connections") + + payload = MsgUP0Announcement( + header=block.header, + header_hash=block.header.hash, + timeslot=block.header.timeslot, + ).to_jam_bytes().to_bytes() + + for conn in self.context.connections.values(): + if not conn.is_connected(): + logger.debug( + f"Skipping broadcast to {conn.host}:{conn.port} - connection is not fully established yet" + ) + continue + + logger.debug(f"Send block header to client {conn.host}:{conn.port} with hash {block.header.hash.hex()}") + conn.send( + conn.stream_up.stream_id, + conn.stream_up.create_message(payload), + end_stream=False, + ) + + def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + self._receive_message(stream, data) + + def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + self._receive_message(stream, data) + + def initiator_fin(self, stream: ManagedStream) -> None: + logger.warning(f"Unexpected FIN on persistent UP0 stream {stream.stream_id}") + + def acceptor_fin(self, stream: ManagedStream) -> None: + logger.warning(f"Unexpected FIN on persistent UP0 stream {stream.stream_id}") + + def on_close(self, stream: ManagedStream) -> None: + self._streams.pop(stream.stream_key, None) + + def _receive_message(self, stream: ManagedStream, data: bytes) -> None: + state = self._streams[stream.stream_key] + + if state.phase == UPState.IN_PROGRESS: + state.phase = UPState.CONNECTED + msg = MsgUP0Handshake.from_jam_bytes(JamBytes(data)) + self._handle_handshake(stream, msg) + return + + if state.phase == UPState.CONNECTED: + msg = MsgUP0Announcement.from_jam_bytes(JamBytes(data)) + self._handle_announcement(stream.conn, msg) + return + + raise RuntimeError(f"Unexpected state {state.phase}") + + def _handle_handshake(self, stream: ManagedStream, msg: MsgUP0Handshake) -> None: + if stream.direction == StreamDirection.acceptor: + self.send_handshake(stream.conn) + + if self.context.state_requesting_blocks: + logger.debug("Skipping handshake block header check, already importing blocks") + return + + block = self.context.app.retrieve_block_by_hash(msg.header_hash) + if block: + return + + logger.info(f"Received newer block from handshake: {msg.header_hash} -> initiate CE128RequestBlocks") + self._initiate_block_sync(stream.conn, msg.header_hash) + + def _handle_announcement(self, conn, msg: MsgUP0Announcement) -> None: + if self.context.state_requesting_blocks: + logger.debug("Skipping block header announcement check, already importing blocks") + return + + block = self.context.app.retrieve_block_by_hash(msg.header.hash) + if block: + return + + logger.info(f"Received new block announcement from up0: {msg.header.hash}") + self._initiate_block_sync(conn, announced_hash=msg.header.hash) + + def _initiate_block_sync(self, conn, announced_hash: bytes) -> None: + self.context.state_requesting_blocks = True + curr_hash = self.context.app.retrieve_block_hash(self.context.app.working_state.timeslot.number) + ce128_handler = self.context.get_handler(StreamKind.CE128_BlockRequest) + ce128_handler.initiate_block_request( + conn, + MsgCE128BlockRequest( + curr_hash, + MsgCE128BlockRequestDirection.ASC.value, + 10, + ), + ) diff --git a/pyjamaz/transport/jamnp_s/types.py b/pyjamaz/transport/jamnp_s/types.py new file mode 100644 index 00000000..9652026c --- /dev/null +++ b/pyjamaz/transport/jamnp_s/types.py @@ -0,0 +1,115 @@ +from __future__ import annotations + +import logging +from dataclasses import dataclass, field +from enum import Enum, IntEnum +from typing import Optional, Protocol, TYPE_CHECKING + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + +if TYPE_CHECKING: + from pyjamaz.transport.jamnp_s.connection import JAMConnection + + +class StreamKind(IntEnum): + UP0_BlockAnnouncement = 0 + CE128_BlockRequest = 128 + CE129_StateRequest = 129 + CE131_SafroleTicketDistributionStep1 = 131 + CE132_SafroleTicketDistributionStep2 = 132 + CE133_WorkPackageSubmission = 133 + CE134_WorkPackageSharing = 134 + CE135_WorkReportDistribution = 135 + CE136_WorkReportRequest = 136 + CE137_ShardDistribution = 137 + CE138_AuditShardRequest = 138 + CE139_SegmentShardRequest = 139 + CE140_SegmentShardRequestJustification = 140 + CE141_AssuranceDistribution = 141 + CE142_PreimageAnnouncement = 142 + CE143_PreimageRequest = 143 + CE144_AuditAnnouncement = 144 + CE145_JudgmentPublication = 145 + + +class StreamDirection(Enum): + initiator = 0 + acceptor = 1 + + +class StreamHandler(Protocol): + kind: StreamKind + + def init_stream(self, stream: "ManagedStream") -> None: + ... + + def on_message(self, stream: "ManagedStream", data: bytes) -> None: + ... + + def on_fin(self, stream: "ManagedStream") -> None: + ... + + def on_reset(self, stream: "ManagedStream", reset_code: int) -> None: + ... + + def on_close(self, stream: "ManagedStream") -> None: + ... + + +@dataclass +class ManagedStream: + stream_id: int + stream_kind: StreamKind + conn: "JAMConnection" + direction: StreamDirection + handler: Optional[StreamHandler] = field(default=None, repr=False) + closed: bool = False + recv_buffer: bytearray = field(default_factory=bytearray, repr=False) + expected_len: Optional[int] = field(default=None, repr=False) + stream_type: int = field(init=False) + stream_type_byte: bytes = field(init=False) + + ERROR_GENERAL = 1 + ERROR_INVALID_MESSAGE = 2 + ERROR_INVALID_SIZE = 3 + ERROR_TOO_LARGE = 4 + ERROR_VALIDATION_FAILED = 5 + + def __post_init__(self) -> None: + self.stream_type = int(self.stream_kind) + self.stream_type_byte = self.stream_type.to_bytes(length=1, byteorder="little") + + @property + def protocol(self): + return self.conn.protocol + + @property + def stream_key(self): + return self.conn.jam_connection_ulid, self.stream_id + + def create_message(self, payload: bytes, add_stream_type: bool = False) -> bytes: + msg = len(payload).to_bytes(length=4, byteorder="little") + payload + if add_stream_type: + return self.stream_type_byte + msg + return msg + + def close(self, reason: int = 0, clean_close: bool = True) -> None: + if self.closed: + return + self.conn.stream_manager.close_stream(self, reason=reason, clean_close=clean_close) + + def send_reset(self, reset_code: int) -> None: + if self.closed: + return + self.conn.stream_manager.send_reset(self, reset_code) + + def handle_error(self, exc: Exception) -> None: + if isinstance(exc, ValueError): + code = self.ERROR_INVALID_MESSAGE + elif isinstance(exc, MemoryError): + code = self.ERROR_TOO_LARGE + else: + code = self.ERROR_GENERAL + + logger.error(f"Stream {self.stream_id} error: {exc}") + self.send_reset(code) diff --git a/pyjamaz/transport/jamnp_s/validator_manager.py b/pyjamaz/transport/jamnp_s/validator_manager.py new file mode 100644 index 00000000..5d8cc711 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/validator_manager.py @@ -0,0 +1,233 @@ +from __future__ import annotations + +import logging +import math +import time +from dataclasses import dataclass +from typing import Awaitable, Callable, Dict, Optional, TYPE_CHECKING + +from aioquic.quic.connection import QuicConnectionState + +from pyjamaz.models.common import ValidatorData + +logger = logging.getLogger("pyjamaz.transport.jamnp_s") + +if TYPE_CHECKING: + from pyjamaz.app import PyjamazApp + from pyjamaz.transport.jamnp_s.connection import JAMConnection + +ConnectCallback = Callable[[str, int, Optional[bytes]], Awaitable[None]] +DisconnectCallback = Callable[["JAMConnection", Optional[bytes]], None] + + +@dataclass +class ValidatorConnection: + validator: ValidatorData + ip: str + port: int + connection: Optional["JAMConnection"] = None + last_try: Optional[float] = None + initiator: bool = False + in_grid: bool = False + + +class ValidatorConnectionManager: + def __init__( + self, + app: "PyjamazApp", + connect_callback: ConnectCallback, + disconnect_callback: DisconnectCallback, + ) -> None: + self.app = app + self.connect_callback = connect_callback + self.disconnect_callback = disconnect_callback + self.connections: Dict[bytes, ValidatorConnection] = {} + + self.validator = None + self.validator_dns = None + self.validator_port = None + self.validator_address = None + self.refresh_local_validator() + + def refresh_local_validator(self) -> None: + self.validator = None + self.validator_dns = None + self.validator_port = None + self.validator_address = None + + if self.app.config.keys is None: + return + + for validator in self.app.working_state.safrole.validators: + if validator.ed25519 == self.app.config.keys.ed25519.public_key: + self.validator = validator + self.validator_dns = validator.get_connection_dns() + self.validator_port = validator.get_metadata_port() + self.validator_address = validator.get_metadata_ipaddress() + return + + @staticmethod + def should_initiate_connection(validator_a: bytes, validator_b: bytes) -> bool: + connect_a = 1 if validator_a[31] > 127 else 0 + connect_b = 1 if validator_b[31] > 127 else 0 + a_less = 1 if validator_a < validator_b else 0 + return (connect_a ^ connect_b ^ a_less) == 1 + + def _add_grid_connections( + self, + validator_idx: int, + validator_queue, + same_epoch: bool, + initiate_conns: Dict[bytes, ValidatorData], + expect_conns: Dict[bytes, ValidatorData], + ) -> None: + width = max(1, math.floor(math.sqrt(len(validator_queue)))) + + for v_idx, validator in validator_queue: + if self.validator.ed25519 == validator.ed25519: + continue + + should_connect = False + if same_epoch: + same_row = (v_idx // width) == (validator_idx // width) + same_column = (validator_idx % width) == (v_idx % width) + should_connect = same_row or same_column + else: + should_connect = v_idx == validator_idx + + if not should_connect: + continue + + if self.should_initiate_connection(self.validator.ed25519, validator.ed25519): + initiate_conns[validator.ed25519] = validator + else: + expect_conns[validator.ed25519] = validator + + async def update_connections(self) -> None: + self.refresh_local_validator() + + if not self.validator: + raise Exception("This node is not a validator") + + prev_validators = list(enumerate(self.app.working_state.validator_archive.validators)) + next_validators = list(enumerate(self.app.working_state.validator_queue.validators)) + active_validators = list(enumerate(self.app.working_state.safrole.validators)) + + validator_idx = None + for v_idx, validator in active_validators: + if validator.ed25519 == self.validator.ed25519: + validator_idx = v_idx + break + + if validator_idx is None: + logger.debug( + f"Current validator {self.validator.ed25519.hex()} is not present in the validator queue" + ) + return + + initiate_grid_connections: Dict[bytes, ValidatorData] = {} + expected_grid_connections: Dict[bytes, ValidatorData] = {} + self._add_grid_connections( + validator_idx, + prev_validators, + same_epoch=False, + initiate_conns=initiate_grid_connections, + expect_conns=expected_grid_connections, + ) + self._add_grid_connections( + validator_idx, + active_validators, + same_epoch=True, + initiate_conns=initiate_grid_connections, + expect_conns=expected_grid_connections, + ) + self._add_grid_connections( + validator_idx, + next_validators, + same_epoch=False, + initiate_conns=initiate_grid_connections, + expect_conns=expected_grid_connections, + ) + + initiate_connections: Dict[bytes, ValidatorData] = {} + expected_connections: Dict[bytes, ValidatorData] = {} + for _, validator in active_validators: + if validator.ed25519 == self.validator.ed25519: + continue + + if self.should_initiate_connection(self.validator.ed25519, validator.ed25519): + initiate_connections[validator.ed25519] = validator + else: + expected_connections[validator.ed25519] = validator + + new_keys = ( + set(initiate_connections) + | set(expected_connections) + | set(initiate_grid_connections) + | set(expected_grid_connections) + ) + current_keys = set(self.connections) + + for ed25519 in current_keys - new_keys: + state = self.connections.pop(ed25519, None) + if state and state.connection is not None: + self.disconnect_callback(state.connection, ed25519) + + for ed25519 in new_keys: + validator = ( + initiate_connections.get(ed25519) + or expected_connections.get(ed25519) + or initiate_grid_connections.get(ed25519) + or expected_grid_connections.get(ed25519) + ) + if validator is None: + continue + + is_initiator = ed25519 in initiate_connections or ed25519 in initiate_grid_connections + in_grid = ed25519 in initiate_grid_connections or ed25519 in expected_grid_connections + + state = self.connections.get(ed25519) + if state is None: + state = ValidatorConnection( + validator=validator, + ip=validator.get_metadata_ipaddress(), + port=validator.get_metadata_port(), + initiator=is_initiator, + in_grid=in_grid, + ) + self.connections[ed25519] = state + else: + state.validator = validator + state.ip = validator.get_metadata_ipaddress() + state.port = validator.get_metadata_port() + state.initiator = is_initiator + state.in_grid = in_grid + + if not (state.in_grid and state.initiator): + continue + + if state.connection is None or state.connection._quic._state > QuicConnectionState.CONNECTED: + await self.connect_callback(state.ip, state.port, ed25519) + + def bind_connection(self, validator_key: bytes, connection: "JAMConnection") -> None: + state = self.connections.get(validator_key) + if state is None: + return + + if state.connection is not None and state.connection is not connection: + self.disconnect_callback(state.connection, validator_key) + + state.connection = connection + state.last_try = time.time() + + def on_disconnect(self, connection: "JAMConnection", validator_key: Optional[bytes] = None) -> None: + validator_key = validator_key or getattr(connection, "validator_key", None) + if validator_key is None: + return + + state = self.connections.get(validator_key) + if state is None: + return + + if state.connection is connection: + state.connection = None From 0a9ff36cd8cd234480b232bb28caaf2c3a954d52 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Tue, 7 Apr 2026 22:30:34 +0200 Subject: [PATCH 45/51] - refactoring DONE (logical / function seperation of concerns) --- pyjamaz/cli.py | 2 +- pyjamaz/transport/jamnp_s/connection.py | 34 ++- pyjamaz/transport/jamnp_s/message_types.py | 285 ------------------ pyjamaz/transport/jamnp_s/network.py | 173 ++++++++++- pyjamaz/transport/jamnp_s/peers.py | 2 +- pyjamaz/transport/jamnp_s/protocol.py | 147 --------- .../transport/jamnp_s/protocol/__init__.py | 55 ++++ .../jamnp_s/{streams => protocol}/base.py | 6 +- .../jamnp_s/{streams => protocol}/ce128.py | 6 +- .../jamnp_s/{streams => protocol}/ce129.py | 4 +- .../jamnp_s/{streams => protocol}/ce131.py | 6 +- .../jamnp_s/{streams => protocol}/ce132.py | 6 +- .../jamnp_s/{streams => protocol}/ce133.py | 9 +- .../jamnp_s/{streams => protocol}/ce134.py | 6 +- .../jamnp_s/{streams => protocol}/ce135.py | 6 +- .../jamnp_s/{streams => protocol}/ce136.py | 6 +- .../jamnp_s/{streams => protocol}/ce137.py | 6 +- .../jamnp_s/{streams => protocol}/ce138.py | 6 +- .../jamnp_s/{streams => protocol}/ce139.py | 9 +- .../jamnp_s/{streams => protocol}/ce140.py | 6 +- .../jamnp_s/{streams => protocol}/ce141.py | 6 +- .../jamnp_s/{streams => protocol}/ce142.py | 6 +- .../jamnp_s/{streams => protocol}/ce143.py | 6 +- .../jamnp_s/{streams => protocol}/ce144.py | 9 +- .../jamnp_s/{streams => protocol}/ce145.py | 6 +- .../jamnp_s/{streams => protocol}/context.py | 0 .../jamnp_s/protocol/messages/__init__.py | 1 + .../jamnp_s/protocol/messages/ce128.py | 28 ++ .../jamnp_s/protocol/messages/ce129.py | 14 + .../jamnp_s/protocol/messages/ce131.py | 18 ++ .../jamnp_s/protocol/messages/ce132.py | 18 ++ .../jamnp_s/protocol/messages/ce133.py | 19 ++ .../jamnp_s/protocol/messages/ce134.py | 26 ++ .../jamnp_s/protocol/messages/ce135.py | 10 + .../jamnp_s/protocol/messages/ce136.py | 16 + .../jamnp_s/protocol/messages/ce137.py | 30 ++ .../jamnp_s/protocol/messages/ce138.py | 23 ++ .../jamnp_s/protocol/messages/ce139.py | 21 ++ .../jamnp_s/protocol/messages/ce140.py | 26 ++ .../jamnp_s/protocol/messages/ce141.py | 13 + .../jamnp_s/protocol/messages/ce142.py | 13 + .../jamnp_s/protocol/messages/ce143.py | 16 + .../jamnp_s/protocol/messages/ce144.py | 43 +++ .../jamnp_s/protocol/messages/ce145.py | 21 ++ .../jamnp_s/protocol/messages/common.py | 38 +++ .../jamnp_s/protocol/messages/up0.py | 29 ++ .../jamnp_s/{streams => protocol}/up0.py | 8 +- pyjamaz/transport/jamnp_s/stream_manager.py | 6 +- pyjamaz/transport/jamnp_s/streams/__init__.py | 54 ---- pyjamaz/transport/jamnp_s/types.py | 4 +- 50 files changed, 743 insertions(+), 565 deletions(-) delete mode 100644 pyjamaz/transport/jamnp_s/message_types.py delete mode 100644 pyjamaz/transport/jamnp_s/protocol.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/__init__.py rename pyjamaz/transport/jamnp_s/{streams => protocol}/base.py (90%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce128.py (96%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce129.py (73%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce131.py (96%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce132.py (95%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce133.py (93%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce134.py (95%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce135.py (88%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce136.py (89%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce137.py (93%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce138.py (91%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce139.py (89%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce140.py (91%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce141.py (88%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce142.py (88%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce143.py (91%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce144.py (91%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/ce145.py (86%) rename pyjamaz/transport/jamnp_s/{streams => protocol}/context.py (100%) create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/__init__.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce128.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce129.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce131.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce132.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce133.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce134.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce135.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce136.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce137.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce138.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce139.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce140.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce141.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce142.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce143.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce144.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/ce145.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/common.py create mode 100644 pyjamaz/transport/jamnp_s/protocol/messages/up0.py rename pyjamaz/transport/jamnp_s/{streams => protocol}/up0.py (95%) delete mode 100644 pyjamaz/transport/jamnp_s/streams/__init__.py diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index ec511e8f..bb93b208 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -34,7 +34,7 @@ from pyjamaz.models.block import Block, Header, Extrinsic from pyjamaz.fuzzer import FuzzerMessage, InitializeMessage, FuzzerTarget, FuzzerSession, AncestryItem from pyjamaz.transport.cert import generate_cert, write_cert -from pyjamaz.transport.jamnp_s.protocol import JAMNPS +from pyjamaz.transport.jamnp_s.network import JAMNPS from pyjamaz.transport.pubsub import PubSub, PubSubSignal from pyjamaz.utils import format_hash, quic_peer_id diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index deee2a0d..2443f235 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -2,8 +2,36 @@ import logging from enum import Enum -from aioquic.asyncio import QuicConnectionProtocol -from aioquic.quic.events import QuicEvent, HandshakeCompleted, StreamReset, StreamDataReceived, ConnectionTerminated +try: + from aioquic.asyncio import QuicConnectionProtocol + from aioquic.quic.events import ( + ConnectionTerminated, + HandshakeCompleted, + QuicEvent, + StreamDataReceived, + StreamReset, + ) +except ModuleNotFoundError as exc: + _AIOQUIC_IMPORT_ERROR = exc + + class QuicConnectionProtocol: # type: ignore[override] + def __init__(self, *args, **kwargs): + raise ModuleNotFoundError("aioquic is required to instantiate JAMConnection") from _AIOQUIC_IMPORT_ERROR + + class QuicEvent: + pass + + class HandshakeCompleted(QuicEvent): + pass + + class StreamReset(QuicEvent): + pass + + class StreamDataReceived(QuicEvent): + pass + + class ConnectionTerminated(QuicEvent): + pass from pyjamaz.transport.jamnp_s.types import StreamKind @@ -30,7 +58,7 @@ def __init__(self, *args, **kwargs): self.addr = None self.stream_up = None - self.streams = {} + self.streams = {} #TODO: typings self._keepalive_task = asyncio.create_task(self._keepalive()) diff --git a/pyjamaz/transport/jamnp_s/message_types.py b/pyjamaz/transport/jamnp_s/message_types.py deleted file mode 100644 index 59012afb..00000000 --- a/pyjamaz/transport/jamnp_s/message_types.py +++ /dev/null @@ -1,285 +0,0 @@ -from dataclasses import dataclass, field -from enum import Enum -from typing import List, Optional, Dict - -from jamcodec.base import JamCodecTypeDef, JamBytes, JamCodecType -from jamcodec.mixins import Serializable -from jamcodec.types import U32, H256, U8, VarInt64, Vec, Option, Array, Map, H512, VecType, U16, Bytes - -from pyjamaz.graypaper_constants import VALIDATOR_COUNT -from pyjamaz.models.block import Header, Guarantee, Block -from pyjamaz.models.common import WorkPackage, RefinementContext, WorkItem - - -class ImplicitVec(Vec): - - def encode(self, value: list) -> JamBytes: - # Encode length of Vec - data = JamBytes(bytes()) - - for idx, item in enumerate(value): - if type(item) is JamBytes: - data += item - else: - data += self.type_def.encode(item) - if item and issubclass(item.__class__, JamCodecType): - value[idx] = item.serialize() - - return data - - def decode(self, data: JamBytes) -> list: - value = [] - - while True: - obj = self.type_def.new() - obj.decode(data) - value.append(obj) - - if data.get_remaining_length() == 0: - break - - return value - - -def calculate_r(): - return (VALIDATOR_COUNT // 3) + 1 # Per spec - - -@dataclass -class MsgUP0Leaf(Serializable): - header_hash: bytes = field(metadata={'codec': H256}) - timeslot: int = field(metadata={'codec': U32}) - -@dataclass -class MsgUP0Handshake(Serializable): - header_hash: bytes = field(metadata={'codec': H256}) - timeslot: int = field(metadata={'codec': U32}) - leafs: List[MsgUP0Leaf] = field(metadata={'codec': Vec(MsgUP0Leaf.to_codec_def())}) - -@dataclass -class MsgUP0Announcement(Serializable): - header: Header = field(metadata={'codec': Header.to_codec_def()}) - header_hash: bytes = field(metadata={'codec': H256}) - timeslot: int = field(metadata={'codec': U32}) - - -class MsgCE128BlockRequestDirection(Enum): - ASC = 0 - DESC = 1 - -@dataclass -class MsgCE128BlockRequest(Serializable): - header_hash: bytes = field(metadata={'codec': H256}) - direction: int = field(metadata={'codec': U8}) - max_blocks: int = field(metadata={'codec': U32}) - -@dataclass -class MsgCE128BlockRequestResponse(Serializable): - blocks: List[Block] = field(metadata={'codec': ImplicitVec(Block.to_codec_def())}) - - -@dataclass -class MsgCE129KeyRangeRequest(Serializable): - header_hash: bytes = field(metadata={'codec': H256}) - key_start: bytes = field(metadata={'codec': Array(U8, 31)}) - key_end: bytes = field(metadata={'codec': Array(U8, 31)}) - max_size: int = field(metadata={'codec': U32}) - - -@dataclass -class MsgCE131SafroleTicket(Serializable): - attempt: int = field(metadata={'codec': U8}) # 0|1 - proof: bytes = field(metadata={'codec': Array(U8, 784)}) - - -@dataclass -class MsgCE131SafroleTicketDistribution(Serializable): - # Note: CE 131 & 132 - epoch_index: int = field(metadata={'codec': U32}) - ticket: MsgCE131SafroleTicket = field(metadata={'codec': MsgCE131SafroleTicket.to_codec_def()}) - - -@dataclass -class MsgCE132SafroleTicket(MsgCE131SafroleTicket): - pass - -@dataclass -class MsgCE132SafroleTicketDistribution(MsgCE131SafroleTicketDistribution): - pass - - -@dataclass -class MsgCE133WorkPackageSubmission(Serializable): - core_index: int = field(metadata={'codec': U16}) - work_package: WorkPackage = field(metadata={'codec': WorkPackage.to_codec_def()}) - -@dataclass -class MsgCE133WorkPackageSubmission(Serializable): - core_index: int = field(metadata={'codec': U16}) - work_package: WorkPackage = field(metadata={'codec': WorkPackage.to_codec_def()}) - -@dataclass -class MsgCE133Extrinsic(Serializable): - bytes_: bytes = field(metadata={'codec': Vec(U8)}) - - -@dataclass -class MsgCE134WorkPackageSharing(Serializable): - core_index: int = field(metadata={'codec': U16}) - segment_root_map: Dict[bytes, bytes] = field(metadata={'codec': Map(H256, H256)}) - - -@dataclass -class MsgCE134WorkPackageBundle(Serializable): - work_package: WorkPackage = field(metadata={'codec': WorkPackage.to_codec_def()}) - - -@dataclass -class MsgCE134RefineResponse(Serializable): - report_hash: bytes = field(metadata={'codec': H256}) - signature: bytes = field(metadata={'codec': H512}) - - -@dataclass -class MsgCE135GuaranteedWorkReport(Guarantee): - pass - - -@dataclass -class MsgCE135GuaranteedWorkReport(Guarantee): - pass - - -@dataclass -class MsgCE136HashRequest(Serializable): - hash: bytes = field(metadata={'codec': H256}) - - -@dataclass -class MsgCE136WorkReport(Serializable): - report: bytes = field(metadata={'codec': Vec(U8)}) - - -@dataclass -class MsgCE137ShardRequest(Serializable): - erasure_root: bytes = field(metadata={'codec': H256}) - shard_index: int = field(metadata={'codec': U16}) - -@dataclass -class MsgCE137BundleShard(Serializable): - bytes_: bytes = field(metadata={'codec': Vec(U8)}) - -@dataclass -class MsgCE137SegmentShard(Serializable): - bytes_: bytes = field(metadata={'codec': Array(U8, 4104 // calculate_r())}) - -@dataclass -class MsgCE137Justification(Serializable): - nodes: List[bytes] = field(metadata={'codec': Vec(Vec(U8))}) # Co-path - - -@dataclass -class MsgCE138ShardRequest(Serializable): - erasure_root: bytes = field(metadata={'codec': H256}) - shard_index: int = field(metadata={'codec': U16}) - -@dataclass -class MsgCE138BundleShard(Serializable): - bytes_: bytes = field(metadata={'codec': Vec(U8)}) - -@dataclass -class MsgCE138Justification(Serializable): - nodes: List[bytes] = field(metadata={'codec': Vec(Vec(U8))}) - - -@dataclass -class MsgCE139SegmentRequest(Serializable): - erasure_root: bytes = field(metadata={'codec': H256}) - shard_index: int = field(metadata={'codec': U16}) - segment_indices: List[int] = field(metadata={'codec': Vec(U16)}) - -@dataclass -class MsgCE139SegmentShard(Serializable): - bytes_: bytes = field(metadata={'codec': Array(U8, 4104 // calculate_r())}) - - -@dataclass -class MsgCE140SegmentRequest(Serializable): - erasure_root: bytes = field(metadata={'codec': H256}) - shard_index: int = field(metadata={'codec': U16}) - segment_indices: List[int] = field(metadata={'codec': Vec(U16)}) - -@dataclass -class MsgCE140SegmentShard(Serializable): - bytes_: bytes = field(metadata={'codec': Array(U8, 4104 // calculate_r())}) - -@dataclass -class MsgCE140Justification(Serializable): - nodes: List[bytes] = field(metadata={'codec': Vec(Vec(U8))}) - - -@dataclass -class MsgCE141Assurance(Serializable): - header_hash: bytes = field(metadata={'codec': H256}) # anchor - bitfield: bytes = field(metadata={'codec': Vec(U8)}) # len = ceil(C/8) - signature: bytes = field(metadata={'codec': H512}) - - -@dataclass -class MsgCE142PreimageAnnouncement(Serializable): - service_id: int = field(metadata={'codec': U32}) - hash: bytes = field(metadata={'codec': H256}) - preimage_length: int = field(metadata={'codec': U32}) - - -@dataclass -class MsgCE143HashRequest(Serializable): - hash: bytes = field(metadata={'codec': H256}) - -@dataclass -class MsgCE143Preimage(Serializable): - bytes_: bytes = field(metadata={'codec': Vec(U8)}) - - -@dataclass -class MsgCE144CoreWRPair(Serializable): - core_index: int = field(metadata={'codec': U16}) - wr_hash: bytes = field(metadata={'codec': H256}) - -@dataclass -class MsgCE144Announcement(Serializable): - header_hash: bytes = field(metadata={'codec': H256}) - tranche: int = field(metadata={'codec': U8}) - announcement: List[MsgCE144CoreWRPair] = field(metadata={'codec': Vec(MsgCE144CoreWRPair.to_codec_def())}) - signature: bytes = field(metadata={'codec': H512}) - -@dataclass -class MsgCE144NoShow(Serializable): - validator_index: int = field(metadata={'codec': U32}) - announcement: bytes = field(metadata={'codec': Vec(U8)}) - -@dataclass -class MsgCE144TrancheEvidenceFirst(Serializable): - signature: bytes = field(metadata={'codec': Array(U8, 96)}) - -@dataclass -class MsgCE144TrancheEvidenceSubsequent(Serializable): - signature: bytes = field(metadata={'codec': Array(U8, 96)}) - no_shows: List[MsgCE144NoShow] = field(metadata={'codec': Vec(MsgCE144NoShow.to_codec_def())}) - -@dataclass -class MsgCE144Evidence(Serializable): - data: bytes = field(metadata={'codec': Vec(U8)}) - - -class JudgmentValidity(Enum): - INVALID = 0 - VALID = 1 - -@dataclass -class MsgCE145JudgmentPublication(Serializable): - epoch_index: int = field(metadata={'codec': U32}) - validator_index: int = field(metadata={'codec': U32}) - validity: int = field(metadata={'codec': U8}) - wr_hash: bytes = field(metadata={'codec': H256}) - signature: bytes = field(metadata={'codec': H512}) diff --git a/pyjamaz/transport/jamnp_s/network.py b/pyjamaz/transport/jamnp_s/network.py index ece81481..dce2826a 100644 --- a/pyjamaz/transport/jamnp_s/network.py +++ b/pyjamaz/transport/jamnp_s/network.py @@ -1,18 +1,42 @@ from __future__ import annotations +import logging import ssl -from typing import Optional, TYPE_CHECKING +import uuid +from typing import Optional, cast -from aioquic.quic.configuration import QuicConfiguration -from ulid import ULID +try: + from aioquic.asyncio import serve + from aioquic.asyncio.client import connect + from aioquic.quic.configuration import QuicConfiguration +except ModuleNotFoundError as exc: + _AIOQUIC_IMPORT_ERROR = exc + serve = None + connect = None + class QuicConfiguration: # type: ignore[override] + def __init__(self, *args, **kwargs): + raise ModuleNotFoundError("aioquic is required to use JAMNPS networking") from _AIOQUIC_IMPORT_ERROR + +try: + from ulid import ULID +except ModuleNotFoundError: + def ULID(): + return uuid.uuid4().hex + +from pyjamaz.constants import MESSAGE_TYPES from pyjamaz.transport.jamnp_s.connection import JAMConnection, JAMConnectionDirection +from pyjamaz.transport.jamnp_s.peers import PeerRegistry +from pyjamaz.transport.jamnp_s.stream_manager import StreamManager +from pyjamaz.transport.jamnp_s.protocol import ProtocolContext, ProtocolSharedState, register_handlers +from pyjamaz.transport.jamnp_s.types import StreamKind +from pyjamaz.transport.jamnp_s.validator_manager import ValidatorConnectionManager +from pyjamaz.transport.types import ProtocolType -if TYPE_CHECKING: - from pyjamaz.transport.jamnp_s.protocol import JAMNPS +logger = logging.getLogger("pyjamaz.transport.jamnp_s") -def build_client_configuration(protocol_name: str, certificate: str, private_key: str) -> QuicConfiguration: +def _build_client_configuration(protocol_name: str, certificate: str, private_key: str) -> QuicConfiguration: configuration = QuicConfiguration( alpn_protocols=[protocol_name], is_client=True, @@ -22,7 +46,7 @@ def build_client_configuration(protocol_name: str, certificate: str, private_key return configuration -def build_server_configuration(protocol_name: str, certificate: str, private_key: str) -> QuicConfiguration: +def _build_server_configuration(protocol_name: str, certificate: str, private_key: str) -> QuicConfiguration: configuration = QuicConfiguration( alpn_protocols=[protocol_name], is_client=False, @@ -31,8 +55,8 @@ def build_server_configuration(protocol_name: str, certificate: str, private_key return configuration -def create_connection_factory( - protocol: "JAMNPS", +def _create_connection_factory( + network: "JAMNPS", direction: JAMConnectionDirection, host: str, port: int, @@ -40,9 +64,9 @@ def create_connection_factory( ): def create_connection(*args, **kwargs): conn = JAMConnection(*args, **kwargs) - conn.protocol = protocol + conn.protocol = network conn.direction = direction - conn.stream_manager = protocol.stream_manager + conn.stream_manager = network.stream_manager conn.jam_connection_ulid = ULID() conn.validator_key = validator_key @@ -50,10 +74,133 @@ def create_connection(*args, **kwargs): conn.host = host conn.port = port - protocol.peer_registry.register(conn) + network.peer_registry.register(conn) if validator_key is not None: - protocol.validator_manager.bind_connection(validator_key, conn) + network.validator_manager.bind_connection(validator_key, conn) return conn return create_connection + + +class JAMNPS(ProtocolType): + MAX_MESSAGE_SIZE = 10000000 + PROTOCOL_NAME = "jamnp-s/0/{}" + + def __init__(self, host, port, certificate, private_key, app): + self.host = host + self.port = port + self.pubsub = app.pubsub + self.app = app + self.cert = certificate + self.pk = private_key + + bl_hash = app.retrieve_block_hash(0).hex() + if bl_hash.startswith("0x"): + bl_hash = bl_hash[2:] + bl_hash = bl_hash[:8] + + self.protocol_name = JAMNPS.PROTOCOL_NAME.format(bl_hash) + self.configuration = _build_client_configuration(self.protocol_name, certificate, private_key) + self.conn_out = None + + self.peer_registry = PeerRegistry(self) + self.stream_manager = StreamManager(self.MAX_MESSAGE_SIZE) + self.validator_manager = ValidatorConnectionManager( + app=self.app, + connect_callback=self.connect, + disconnect_callback=self.disconnect, + ) + self.context = ProtocolContext( + app=self.app, + peer_registry=self.peer_registry, + validator_manager=self.validator_manager, + stream_manager=self.stream_manager, + state=ProtocolSharedState(), + ) + self.protocol_handlers = register_handlers(self.stream_manager, self.context) + + up0_handler = self.handler(StreamKind.UP0_BlockAnnouncement) + ce128_handler = self.handler(StreamKind.CE128_BlockRequest) + ce131_handler = self.handler(StreamKind.CE131_SafroleTicketDistributionStep1) + + self.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, up0_handler.broadcast_block) + self.pubsub.subscribe(MESSAGE_TYPES.CE128_SUCCESS, ce128_handler.finish_block_request) + self.pubsub.subscribe(MESSAGE_TYPES.CE128_FAILURE, ce128_handler.finish_block_request) + self.pubsub.subscribe(MESSAGE_TYPES.TICKET_ADD, ce131_handler.broadcast_own_ticket) + + @property + def validator(self): + return self.validator_manager.validator + + @property + def validator_dns(self): + return self.validator_manager.validator_dns + + @property + def validator_port(self): + return self.validator_manager.validator_port + + @property + def validator_address(self): + return self.validator_manager.validator_address + + def handler(self, kind: StreamKind): + return self.protocol_handlers[kind] + + async def listen(self): + logger.info(f"Listening on {self.host}:{self.port}") + + server_conf = _build_server_configuration(self.protocol_name, self.cert, self.pk) + await serve( + self.host, + self.port, + configuration=server_conf, + create_protocol=_create_connection_factory( + self, + JAMConnectionDirection.acceptor, + self.host, + self.port, + None, + ), + retry=True, + ) + + def should_initiate_connection(self, validator_a: bytes, validator_b: bytes) -> bool: + return self.validator_manager.should_initiate_connection(validator_a, validator_b) + + async def update_validator_connections(self): + await self.validator_manager.update_connections() + + async def connect(self, host: str, port: int, validator_key: Optional[bytes]): + configuration = _build_client_configuration(self.protocol_name, self.cert, self.pk) + + addr = f"{host}:{port}" + if addr in self.peer_registry.conn_addr: + logger.warning(f"Ignoring duplicate connection to {addr}") + return + + logger.info(f"Connecting to {host}:{port}") + try: + async with connect( + host, + port, + configuration=configuration, + create_protocol=_create_connection_factory( + self, + JAMConnectionDirection.initiator, + host, + port, + validator_key, + ), + ) as client: + client = cast(JAMConnection, client) + await client.wait_closed() + self.disconnect(client) + except ConnectionError as exc: + logger.warning(f"💩 Cannot connect to {host}:{port} {exc}") + + def disconnect(self, connection: JAMConnection, validator_key: Optional[bytes] = None): + self.stream_manager.cleanup_connection(connection) + self.peer_registry.unregister(connection) + self.validator_manager.on_disconnect(connection, validator_key) diff --git a/pyjamaz/transport/jamnp_s/peers.py b/pyjamaz/transport/jamnp_s/peers.py index 47335ecb..3a284fe6 100644 --- a/pyjamaz/transport/jamnp_s/peers.py +++ b/pyjamaz/transport/jamnp_s/peers.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from pyjamaz.transport.jamnp_s.connection import JAMConnection - from pyjamaz.transport.jamnp_s.protocol import JAMNPS + from pyjamaz.transport.jamnp_s.network import JAMNPS class PeerRegistry: diff --git a/pyjamaz/transport/jamnp_s/protocol.py b/pyjamaz/transport/jamnp_s/protocol.py deleted file mode 100644 index 23ac2bcc..00000000 --- a/pyjamaz/transport/jamnp_s/protocol.py +++ /dev/null @@ -1,147 +0,0 @@ -from __future__ import annotations - -import logging -from typing import Optional, cast - -from aioquic.asyncio import serve -from aioquic.asyncio.client import connect - -from pyjamaz.constants import MESSAGE_TYPES -from pyjamaz.transport.jamnp_s.connection import JAMConnection, JAMConnectionDirection -from pyjamaz.transport.jamnp_s.streams import ProtocolContext, ProtocolSharedState, register_handlers -from pyjamaz.transport.jamnp_s.network import ( - build_client_configuration, - build_server_configuration, - create_connection_factory, -) -from pyjamaz.transport.jamnp_s.peers import PeerRegistry -from pyjamaz.transport.jamnp_s.stream_manager import StreamManager -from pyjamaz.transport.jamnp_s.types import StreamKind -from pyjamaz.transport.jamnp_s.validator_manager import ValidatorConnectionManager -from pyjamaz.transport.types import ProtocolType - -logger = logging.getLogger("pyjamaz.transport.jamnp_s") - - -class JAMNPS(ProtocolType): - MAX_MESSAGE_SIZE = 10000000 - PROTOCOL_NAME = "jamnp-s/0/{}" - - def __init__(self, host, port, certificate, private_key, app): - self.host = host - self.port = port - self.pubsub = app.pubsub - self.app = app - self.cert = certificate - self.pk = private_key - - bl_hash = app.retrieve_block_hash(0).hex() - if bl_hash.startswith("0x"): - bl_hash = bl_hash[2:] - bl_hash = bl_hash[:8] - - self.protocol_name = JAMNPS.PROTOCOL_NAME.format(bl_hash) - self.configuration = build_client_configuration(self.protocol_name, certificate, private_key) - self.conn_out = None - - self.peer_registry = PeerRegistry(self) - self.stream_manager = StreamManager(self.MAX_MESSAGE_SIZE) - self.validator_manager = ValidatorConnectionManager( - app=self.app, - connect_callback=self.connect, - disconnect_callback=self.disconnect, - ) - self.context = ProtocolContext( - app=self.app, - peer_registry=self.peer_registry, - validator_manager=self.validator_manager, - stream_manager=self.stream_manager, - state=ProtocolSharedState(), - ) - self.protocol_handlers = register_handlers(self.stream_manager, self.context) - - # Hook up signals that require protocol handling - up0_handler = self.handler(StreamKind.UP0_BlockAnnouncement) - ce128_handler = self.handler(StreamKind.CE128_BlockRequest) - ce131_handler = self.handler(StreamKind.CE131_SafroleTicketDistributionStep1) - - self.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, up0_handler.broadcast_block) - self.pubsub.subscribe(MESSAGE_TYPES.CE128_SUCCESS, ce128_handler.finish_block_request) - self.pubsub.subscribe(MESSAGE_TYPES.CE128_FAILURE, ce128_handler.finish_block_request) - self.pubsub.subscribe(MESSAGE_TYPES.TICKET_ADD, ce131_handler.broadcast_own_ticket) - - @property - def validator(self): - return self.validator_manager.validator - - @property - def validator_dns(self): - return self.validator_manager.validator_dns - - @property - def validator_port(self): - return self.validator_manager.validator_port - - @property - def validator_address(self): - return self.validator_manager.validator_address - - def handler(self, kind: StreamKind): - return self.protocol_handlers[kind] - - async def listen(self): - logger.info(f"Listening on {self.host}:{self.port}") - - server_conf = build_server_configuration(self.protocol_name, self.cert, self.pk) - await serve( - self.host, - self.port, - configuration=server_conf, - create_protocol=create_connection_factory( - self, - JAMConnectionDirection.acceptor, - self.host, - self.port, - None, - ), - retry=True, - ) - - def should_initiate_connection(self, validator_a: bytes, validator_b: bytes) -> bool: - return self.validator_manager.should_initiate_connection(validator_a, validator_b) - - async def update_validator_connections(self): - await self.validator_manager.update_connections() - - async def connect(self, host: str, port: int, validator_key: Optional[bytes]): - configuration = build_client_configuration(self.protocol_name, self.cert, self.pk) - - addr = f"{host}:{port}" - if addr in self.peer_registry.conn_addr: - logger.warning(f"Ignoring duplicate connection to {addr}") - return - - logger.info(f"Connecting to {host}:{port}") - try: - async with connect( - host, - port, - configuration=configuration, - create_protocol=create_connection_factory( - self, - JAMConnectionDirection.initiator, - host, - port, - validator_key, - ), - ) as client: - client = cast(JAMConnection, client) - await client.wait_closed() - self.disconnect(client) - except ConnectionError as exc: - logger.warning(f"💩 Cannot connect to {host}:{port} {exc}") - - def disconnect(self, connection: JAMConnection, validator_key: Optional[bytes] = None): - self.stream_manager.cleanup_connection(connection) - self.peer_registry.unregister(connection) - self.validator_manager.on_disconnect(connection, validator_key) diff --git a/pyjamaz/transport/jamnp_s/protocol/__init__.py b/pyjamaz/transport/jamnp_s/protocol/__init__.py new file mode 100644 index 00000000..c0c57398 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/__init__.py @@ -0,0 +1,55 @@ +from pyjamaz.transport.jamnp_s.protocol.context import ProtocolContext, ProtocolSharedState + + +def register_handlers(stream_manager, context: ProtocolContext): + from pyjamaz.transport.jamnp_s.protocol.ce128 import CE128Handler + from pyjamaz.transport.jamnp_s.protocol.ce129 import CE129Handler + from pyjamaz.transport.jamnp_s.protocol.ce131 import CE131Handler + from pyjamaz.transport.jamnp_s.protocol.ce132 import CE132Handler + from pyjamaz.transport.jamnp_s.protocol.ce133 import CE133Handler + from pyjamaz.transport.jamnp_s.protocol.ce134 import CE134Handler + from pyjamaz.transport.jamnp_s.protocol.ce135 import CE135Handler + from pyjamaz.transport.jamnp_s.protocol.ce136 import CE136Handler + from pyjamaz.transport.jamnp_s.protocol.ce137 import CE137Handler + from pyjamaz.transport.jamnp_s.protocol.ce138 import CE138Handler + from pyjamaz.transport.jamnp_s.protocol.ce139 import CE139Handler + from pyjamaz.transport.jamnp_s.protocol.ce140 import CE140Handler + from pyjamaz.transport.jamnp_s.protocol.ce141 import CE141Handler + from pyjamaz.transport.jamnp_s.protocol.ce142 import CE142Handler + from pyjamaz.transport.jamnp_s.protocol.ce143 import CE143Handler + from pyjamaz.transport.jamnp_s.protocol.ce144 import CE144Handler + from pyjamaz.transport.jamnp_s.protocol.ce145 import CE145Handler + from pyjamaz.transport.jamnp_s.protocol.up0 import UP0Handler + + handlers = [ + UP0Handler(context), + CE128Handler(context), + CE129Handler(context), + CE131Handler(context), + CE132Handler(context), + CE133Handler(context), + CE134Handler(context), + CE135Handler(context), + CE136Handler(context), + CE137Handler(context), + CE138Handler(context), + CE139Handler(context), + CE140Handler(context), + CE141Handler(context), + CE142Handler(context), + CE143Handler(context), + CE144Handler(context), + CE145Handler(context), + ] + + for handler in handlers: + context.register_handler(handler) + stream_manager.register_handler(handler) + return context.handlers + + +__all__ = [ + "ProtocolContext", + "ProtocolSharedState", + "register_handlers", +] diff --git a/pyjamaz/transport/jamnp_s/streams/base.py b/pyjamaz/transport/jamnp_s/protocol/base.py similarity index 90% rename from pyjamaz/transport/jamnp_s/streams/base.py rename to pyjamaz/transport/jamnp_s/protocol/base.py index 8a86dc59..620fbf34 100644 --- a/pyjamaz/transport/jamnp_s/streams/base.py +++ b/pyjamaz/transport/jamnp_s/protocol/base.py @@ -2,15 +2,15 @@ from typing import TYPE_CHECKING -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamDirection +from pyjamaz.transport.jamnp_s.types import IStreamHandler, ManagedStream, StreamDirection if TYPE_CHECKING: from pyjamaz.transport.jamnp_s.connection import JAMConnection - from pyjamaz.transport.jamnp_s.streams.context import ProtocolContext + from pyjamaz.transport.jamnp_s.protocol.context import ProtocolContext from pyjamaz.transport.jamnp_s.types import StreamKind -class ContextualStreamHandler: +class StreamHandler(IStreamHandler): kind: "StreamKind" def __init__(self, context: "ProtocolContext") -> None: diff --git a/pyjamaz/transport/jamnp_s/streams/ce128.py b/pyjamaz/transport/jamnp_s/protocol/ce128.py similarity index 96% rename from pyjamaz/transport/jamnp_s/streams/ce128.py rename to pyjamaz/transport/jamnp_s/protocol/ce128.py index 962551ed..53bd84f3 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce128.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce128.py @@ -9,8 +9,8 @@ from pyjamaz.constants import MESSAGE_TYPES from pyjamaz.models.block import Block, Header -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import ( +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce128 import ( MsgCE128BlockRequest, MsgCE128BlockRequestDirection, MsgCE128BlockRequestResponse, @@ -25,7 +25,7 @@ class CE128StreamState: active: bool = True -class CE128Handler(ContextualStreamHandler): +class CE128Handler(StreamHandler): kind = StreamKind.CE128_BlockRequest def __init__(self, context) -> None: diff --git a/pyjamaz/transport/jamnp_s/streams/ce129.py b/pyjamaz/transport/jamnp_s/protocol/ce129.py similarity index 73% rename from pyjamaz/transport/jamnp_s/streams/ce129.py rename to pyjamaz/transport/jamnp_s/protocol/ce129.py index 580430cf..f33e58ac 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce129.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce129.py @@ -1,10 +1,10 @@ from __future__ import annotations -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind -class CE129Handler(ContextualStreamHandler): +class CE129Handler(StreamHandler): kind = StreamKind.CE129_StateRequest def initiator_message(self, stream: ManagedStream, data: bytes) -> None: diff --git a/pyjamaz/transport/jamnp_s/streams/ce131.py b/pyjamaz/transport/jamnp_s/protocol/ce131.py similarity index 96% rename from pyjamaz/transport/jamnp_s/streams/ce131.py rename to pyjamaz/transport/jamnp_s/protocol/ce131.py index c2076231..af504b3e 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce131.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce131.py @@ -8,8 +8,8 @@ from pyjamaz.graypaper_constants import EPOCH_TIMESLOTS from pyjamaz.models.block import TicketEnvelope -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import ( +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce131 import ( MsgCE131SafroleTicket, MsgCE131SafroleTicketDistribution, ) @@ -23,7 +23,7 @@ class CE131StreamState: active: bool = True -class CE131Handler(ContextualStreamHandler): +class CE131Handler(StreamHandler): kind = StreamKind.CE131_SafroleTicketDistributionStep1 def __init__(self, context) -> None: diff --git a/pyjamaz/transport/jamnp_s/streams/ce132.py b/pyjamaz/transport/jamnp_s/protocol/ce132.py similarity index 95% rename from pyjamaz/transport/jamnp_s/streams/ce132.py rename to pyjamaz/transport/jamnp_s/protocol/ce132.py index 4c747de1..a3561fd1 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce132.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce132.py @@ -8,8 +8,8 @@ from pyjamaz.graypaper_constants import EPOCH_TIMESLOTS from pyjamaz.models.block import TicketEnvelope -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import MsgCE132SafroleTicketDistribution +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce132 import MsgCE132SafroleTicketDistribution from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -20,7 +20,7 @@ class CE132StreamState: active: bool = True -class CE132Handler(ContextualStreamHandler): +class CE132Handler(StreamHandler): kind = StreamKind.CE132_SafroleTicketDistributionStep2 def __init__(self, context) -> None: diff --git a/pyjamaz/transport/jamnp_s/streams/ce133.py b/pyjamaz/transport/jamnp_s/protocol/ce133.py similarity index 93% rename from pyjamaz/transport/jamnp_s/streams/ce133.py rename to pyjamaz/transport/jamnp_s/protocol/ce133.py index b68fec4c..335c6ecd 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce133.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce133.py @@ -8,8 +8,11 @@ from pyjamaz.hashing import blake2b_256_hash from pyjamaz.models.common import WorkPackage -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import MsgCE133Extrinsic, MsgCE133WorkPackageSubmission +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce133 import ( + MsgCE133Extrinsic, + MsgCE133WorkPackageSubmission, +) from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -20,7 +23,7 @@ class CE133StreamState: work_package: Optional[WorkPackage] = None -class CE133Handler(ContextualStreamHandler): +class CE133Handler(StreamHandler): kind = StreamKind.CE133_WorkPackageSubmission def __init__(self, context) -> None: diff --git a/pyjamaz/transport/jamnp_s/streams/ce134.py b/pyjamaz/transport/jamnp_s/protocol/ce134.py similarity index 95% rename from pyjamaz/transport/jamnp_s/streams/ce134.py rename to pyjamaz/transport/jamnp_s/protocol/ce134.py index edc84020..f9ab64b3 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce134.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce134.py @@ -7,8 +7,8 @@ from jamcodec.base import JamBytes from pyjamaz.models.common import WorkPackage -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import ( +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce134 import ( MsgCE134RefineResponse, MsgCE134WorkPackageBundle, MsgCE134WorkPackageSharing, @@ -24,7 +24,7 @@ class CE134StreamState: sharing_msg: Optional[MsgCE134WorkPackageSharing] = None -class CE134Handler(ContextualStreamHandler): +class CE134Handler(StreamHandler): kind = StreamKind.CE134_WorkPackageSharing def __init__(self, context) -> None: diff --git a/pyjamaz/transport/jamnp_s/streams/ce135.py b/pyjamaz/transport/jamnp_s/protocol/ce135.py similarity index 88% rename from pyjamaz/transport/jamnp_s/streams/ce135.py rename to pyjamaz/transport/jamnp_s/protocol/ce135.py index aa19906c..a8b88b64 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce135.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce135.py @@ -4,14 +4,14 @@ from jamcodec.base import JamBytes -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import MsgCE135GuaranteedWorkReport +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce135 import MsgCE135GuaranteedWorkReport from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") -class CE135Handler(ContextualStreamHandler): +class CE135Handler(StreamHandler): kind = StreamKind.CE135_WorkReportDistribution def initiate_distribution(self, conn, msg: MsgCE135GuaranteedWorkReport) -> ManagedStream: diff --git a/pyjamaz/transport/jamnp_s/streams/ce136.py b/pyjamaz/transport/jamnp_s/protocol/ce136.py similarity index 89% rename from pyjamaz/transport/jamnp_s/streams/ce136.py rename to pyjamaz/transport/jamnp_s/protocol/ce136.py index e0bd5259..33a86673 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce136.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce136.py @@ -4,14 +4,14 @@ from jamcodec.base import JamBytes -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import MsgCE136HashRequest, MsgCE136WorkReport +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce136 import MsgCE136HashRequest, MsgCE136WorkReport from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") -class CE136Handler(ContextualStreamHandler): +class CE136Handler(StreamHandler): kind = StreamKind.CE136_WorkReportRequest def initiate_request(self, conn, msg: MsgCE136HashRequest) -> ManagedStream: diff --git a/pyjamaz/transport/jamnp_s/streams/ce137.py b/pyjamaz/transport/jamnp_s/protocol/ce137.py similarity index 93% rename from pyjamaz/transport/jamnp_s/streams/ce137.py rename to pyjamaz/transport/jamnp_s/protocol/ce137.py index 65af4471..8c76cdfd 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce137.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce137.py @@ -4,8 +4,8 @@ from jamcodec.base import JamBytes -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import ( +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce137 import ( MsgCE137BundleShard, MsgCE137Justification, MsgCE137SegmentShard, @@ -16,7 +16,7 @@ logger = logging.getLogger("pyjamaz.transport.jamnp_s") -class CE137Handler(ContextualStreamHandler): +class CE137Handler(StreamHandler): kind = StreamKind.CE137_ShardDistribution def initiate_request(self, conn, req: MsgCE137ShardRequest) -> ManagedStream: diff --git a/pyjamaz/transport/jamnp_s/streams/ce138.py b/pyjamaz/transport/jamnp_s/protocol/ce138.py similarity index 91% rename from pyjamaz/transport/jamnp_s/streams/ce138.py rename to pyjamaz/transport/jamnp_s/protocol/ce138.py index 45f11b15..6243ac41 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce138.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce138.py @@ -4,8 +4,8 @@ from jamcodec.base import JamBytes -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import ( +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce138 import ( MsgCE138BundleShard, MsgCE138Justification, MsgCE138ShardRequest, @@ -15,7 +15,7 @@ logger = logging.getLogger("pyjamaz.transport.jamnp_s") -class CE138Handler(ContextualStreamHandler): +class CE138Handler(StreamHandler): kind = StreamKind.CE138_AuditShardRequest def initiate_request(self, conn, req: MsgCE138ShardRequest) -> ManagedStream: diff --git a/pyjamaz/transport/jamnp_s/streams/ce139.py b/pyjamaz/transport/jamnp_s/protocol/ce139.py similarity index 89% rename from pyjamaz/transport/jamnp_s/streams/ce139.py rename to pyjamaz/transport/jamnp_s/protocol/ce139.py index 53fbfe24..16686386 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce139.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce139.py @@ -4,14 +4,17 @@ from jamcodec.base import JamBytes -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import MsgCE139SegmentRequest, MsgCE139SegmentShard +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce139 import ( + MsgCE139SegmentRequest, + MsgCE139SegmentShard, +) from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") -class CE139Handler(ContextualStreamHandler): +class CE139Handler(StreamHandler): kind = StreamKind.CE139_SegmentShardRequest def initiate_request(self, conn, req: MsgCE139SegmentRequest) -> ManagedStream: diff --git a/pyjamaz/transport/jamnp_s/streams/ce140.py b/pyjamaz/transport/jamnp_s/protocol/ce140.py similarity index 91% rename from pyjamaz/transport/jamnp_s/streams/ce140.py rename to pyjamaz/transport/jamnp_s/protocol/ce140.py index a0a4c2fe..930a5595 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce140.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce140.py @@ -4,8 +4,8 @@ from jamcodec.base import JamBytes -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import ( +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce140 import ( MsgCE140Justification, MsgCE140SegmentRequest, MsgCE140SegmentShard, @@ -15,7 +15,7 @@ logger = logging.getLogger("pyjamaz.transport.jamnp_s") -class CE140Handler(ContextualStreamHandler): +class CE140Handler(StreamHandler): kind = StreamKind.CE140_SegmentShardRequestJustification def initiate_request(self, conn, req: MsgCE140SegmentRequest) -> ManagedStream: diff --git a/pyjamaz/transport/jamnp_s/streams/ce141.py b/pyjamaz/transport/jamnp_s/protocol/ce141.py similarity index 88% rename from pyjamaz/transport/jamnp_s/streams/ce141.py rename to pyjamaz/transport/jamnp_s/protocol/ce141.py index 2ee33f6e..356f34f3 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce141.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce141.py @@ -4,14 +4,14 @@ from jamcodec.base import JamBytes -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import MsgCE141Assurance +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce141 import MsgCE141Assurance from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") -class CE141Handler(ContextualStreamHandler): +class CE141Handler(StreamHandler): kind = StreamKind.CE141_AssuranceDistribution def initiate_distribution(self, conn, msg: MsgCE141Assurance) -> ManagedStream: diff --git a/pyjamaz/transport/jamnp_s/streams/ce142.py b/pyjamaz/transport/jamnp_s/protocol/ce142.py similarity index 88% rename from pyjamaz/transport/jamnp_s/streams/ce142.py rename to pyjamaz/transport/jamnp_s/protocol/ce142.py index cbd95263..9288b2a0 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce142.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce142.py @@ -4,14 +4,14 @@ from jamcodec.base import JamBytes -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import MsgCE142PreimageAnnouncement +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce142 import MsgCE142PreimageAnnouncement from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") -class CE142Handler(ContextualStreamHandler): +class CE142Handler(StreamHandler): kind = StreamKind.CE142_PreimageAnnouncement def initiate_announcement(self, conn, msg: MsgCE142PreimageAnnouncement) -> ManagedStream: diff --git a/pyjamaz/transport/jamnp_s/streams/ce143.py b/pyjamaz/transport/jamnp_s/protocol/ce143.py similarity index 91% rename from pyjamaz/transport/jamnp_s/streams/ce143.py rename to pyjamaz/transport/jamnp_s/protocol/ce143.py index d476eadb..ae026bd9 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce143.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce143.py @@ -4,14 +4,14 @@ from jamcodec.base import JamBytes -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import MsgCE143HashRequest, MsgCE143Preimage +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce143 import MsgCE143HashRequest, MsgCE143Preimage from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") -class CE143Handler(ContextualStreamHandler): +class CE143Handler(StreamHandler): kind = StreamKind.CE143_PreimageRequest def initiate_request(self, conn, msg: MsgCE143HashRequest) -> ManagedStream: diff --git a/pyjamaz/transport/jamnp_s/streams/ce144.py b/pyjamaz/transport/jamnp_s/protocol/ce144.py similarity index 91% rename from pyjamaz/transport/jamnp_s/streams/ce144.py rename to pyjamaz/transport/jamnp_s/protocol/ce144.py index ba8b5e32..febe12e4 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce144.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce144.py @@ -5,8 +5,11 @@ from jamcodec.base import JamBytes -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import MsgCE144Announcement, MsgCE144Evidence +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce144 import ( + MsgCE144Announcement, + MsgCE144Evidence, +) from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -17,7 +20,7 @@ class CE144StreamState: received_announcement: bool = False -class CE144Handler(ContextualStreamHandler): +class CE144Handler(StreamHandler): kind = StreamKind.CE144_AuditAnnouncement def __init__(self, context) -> None: diff --git a/pyjamaz/transport/jamnp_s/streams/ce145.py b/pyjamaz/transport/jamnp_s/protocol/ce145.py similarity index 86% rename from pyjamaz/transport/jamnp_s/streams/ce145.py rename to pyjamaz/transport/jamnp_s/protocol/ce145.py index 7631edb5..ce7abee7 100644 --- a/pyjamaz/transport/jamnp_s/streams/ce145.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce145.py @@ -4,14 +4,14 @@ from jamcodec.base import JamBytes -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import MsgCE145JudgmentPublication +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce145 import MsgCE145JudgmentPublication from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") -class CE145Handler(ContextualStreamHandler): +class CE145Handler(StreamHandler): kind = StreamKind.CE145_JudgmentPublication def initiate_publication(self, conn, msg: MsgCE145JudgmentPublication) -> ManagedStream: diff --git a/pyjamaz/transport/jamnp_s/streams/context.py b/pyjamaz/transport/jamnp_s/protocol/context.py similarity index 100% rename from pyjamaz/transport/jamnp_s/streams/context.py rename to pyjamaz/transport/jamnp_s/protocol/context.py diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/__init__.py b/pyjamaz/transport/jamnp_s/protocol/messages/__init__.py new file mode 100644 index 00000000..31f3515c --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/__init__.py @@ -0,0 +1 @@ +"""Per-stream JAMNP-S wire schema modules.""" diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce128.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce128.py new file mode 100644 index 00000000..bcc27526 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce128.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum +from typing import List + +from jamcodec.mixins import Serializable +from jamcodec.types import H256, U8, U32 + +from pyjamaz.models.block import Block +from pyjamaz.transport.jamnp_s.protocol.messages.common import ImplicitVec + + +class MsgCE128BlockRequestDirection(Enum): + ASC = 0 + DESC = 1 + + +@dataclass +class MsgCE128BlockRequest(Serializable): + header_hash: bytes = field(metadata={"codec": H256}) + direction: int = field(metadata={"codec": U8}) + max_blocks: int = field(metadata={"codec": U32}) + + +@dataclass +class MsgCE128BlockRequestResponse(Serializable): + blocks: List[Block] = field(metadata={"codec": ImplicitVec(Block.to_codec_def())}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce129.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce129.py new file mode 100644 index 00000000..dc2e4285 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce129.py @@ -0,0 +1,14 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from jamcodec.mixins import Serializable +from jamcodec.types import Array, H256, U8, U32 + + +@dataclass +class MsgCE129KeyRangeRequest(Serializable): + header_hash: bytes = field(metadata={"codec": H256}) + key_start: bytes = field(metadata={"codec": Array(U8, 31)}) + key_end: bytes = field(metadata={"codec": Array(U8, 31)}) + max_size: int = field(metadata={"codec": U32}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce131.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce131.py new file mode 100644 index 00000000..456b4c83 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce131.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from jamcodec.mixins import Serializable +from jamcodec.types import Array, U8, U32 + + +@dataclass +class MsgCE131SafroleTicket(Serializable): + attempt: int = field(metadata={"codec": U8}) + proof: bytes = field(metadata={"codec": Array(U8, 784)}) + + +@dataclass +class MsgCE131SafroleTicketDistribution(Serializable): + epoch_index: int = field(metadata={"codec": U32}) + ticket: MsgCE131SafroleTicket = field(metadata={"codec": MsgCE131SafroleTicket.to_codec_def()}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce132.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce132.py new file mode 100644 index 00000000..30ce4b5d --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce132.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from pyjamaz.transport.jamnp_s.protocol.messages.ce131 import ( + MsgCE131SafroleTicket, + MsgCE131SafroleTicketDistribution, +) + + +@dataclass +class MsgCE132SafroleTicket(MsgCE131SafroleTicket): + pass + + +@dataclass +class MsgCE132SafroleTicketDistribution(MsgCE131SafroleTicketDistribution): + pass diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce133.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce133.py new file mode 100644 index 00000000..57c3f283 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce133.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from jamcodec.mixins import Serializable +from jamcodec.types import U16, U8, Vec + +from pyjamaz.models.common import WorkPackage + + +@dataclass +class MsgCE133WorkPackageSubmission(Serializable): + core_index: int = field(metadata={"codec": U16}) + work_package: WorkPackage = field(metadata={"codec": WorkPackage.to_codec_def()}) + + +@dataclass +class MsgCE133Extrinsic(Serializable): + bytes_: bytes = field(metadata={"codec": Vec(U8)}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce134.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce134.py new file mode 100644 index 00000000..43e313b3 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce134.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict + +from jamcodec.mixins import Serializable +from jamcodec.types import H256, H512, Map, U16 + +from pyjamaz.models.common import WorkPackage + + +@dataclass +class MsgCE134WorkPackageSharing(Serializable): + core_index: int = field(metadata={"codec": U16}) + segment_root_map: Dict[bytes, bytes] = field(metadata={"codec": Map(H256, H256)}) + + +@dataclass +class MsgCE134WorkPackageBundle(Serializable): + work_package: WorkPackage = field(metadata={"codec": WorkPackage.to_codec_def()}) + + +@dataclass +class MsgCE134RefineResponse(Serializable): + report_hash: bytes = field(metadata={"codec": H256}) + signature: bytes = field(metadata={"codec": H512}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce135.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce135.py new file mode 100644 index 00000000..c8cb385e --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce135.py @@ -0,0 +1,10 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from pyjamaz.models.block import Guarantee + + +@dataclass +class MsgCE135GuaranteedWorkReport(Guarantee): + pass diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce136.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce136.py new file mode 100644 index 00000000..b303ab46 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce136.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from jamcodec.mixins import Serializable +from jamcodec.types import H256, U8, Vec + + +@dataclass +class MsgCE136HashRequest(Serializable): + hash: bytes = field(metadata={"codec": H256}) + + +@dataclass +class MsgCE136WorkReport(Serializable): + report: bytes = field(metadata={"codec": Vec(U8)}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce137.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce137.py new file mode 100644 index 00000000..bd0381a0 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce137.py @@ -0,0 +1,30 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import List + +from jamcodec.mixins import Serializable +from jamcodec.types import Array, H256, U8, U16, Vec + +from pyjamaz.transport.jamnp_s.protocol.messages.common import calculate_r + + +@dataclass +class MsgCE137ShardRequest(Serializable): + erasure_root: bytes = field(metadata={"codec": H256}) + shard_index: int = field(metadata={"codec": U16}) + + +@dataclass +class MsgCE137BundleShard(Serializable): + bytes_: bytes = field(metadata={"codec": Vec(U8)}) + + +@dataclass +class MsgCE137SegmentShard(Serializable): + bytes_: bytes = field(metadata={"codec": Array(U8, 4104 // calculate_r())}) + + +@dataclass +class MsgCE137Justification(Serializable): + nodes: List[bytes] = field(metadata={"codec": Vec(Vec(U8))}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce138.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce138.py new file mode 100644 index 00000000..f22355b1 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce138.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import List + +from jamcodec.mixins import Serializable +from jamcodec.types import H256, U8, U16, Vec + + +@dataclass +class MsgCE138ShardRequest(Serializable): + erasure_root: bytes = field(metadata={"codec": H256}) + shard_index: int = field(metadata={"codec": U16}) + + +@dataclass +class MsgCE138BundleShard(Serializable): + bytes_: bytes = field(metadata={"codec": Vec(U8)}) + + +@dataclass +class MsgCE138Justification(Serializable): + nodes: List[bytes] = field(metadata={"codec": Vec(Vec(U8))}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce139.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce139.py new file mode 100644 index 00000000..65cf7385 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce139.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import List + +from jamcodec.mixins import Serializable +from jamcodec.types import Array, H256, U8, U16, Vec + +from pyjamaz.transport.jamnp_s.protocol.messages.common import calculate_r + + +@dataclass +class MsgCE139SegmentRequest(Serializable): + erasure_root: bytes = field(metadata={"codec": H256}) + shard_index: int = field(metadata={"codec": U16}) + segment_indices: List[int] = field(metadata={"codec": Vec(U16)}) + + +@dataclass +class MsgCE139SegmentShard(Serializable): + bytes_: bytes = field(metadata={"codec": Array(U8, 4104 // calculate_r())}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce140.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce140.py new file mode 100644 index 00000000..dc1056fd --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce140.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import List + +from jamcodec.mixins import Serializable +from jamcodec.types import Array, H256, U8, U16, Vec + +from pyjamaz.transport.jamnp_s.protocol.messages.common import calculate_r + + +@dataclass +class MsgCE140SegmentRequest(Serializable): + erasure_root: bytes = field(metadata={"codec": H256}) + shard_index: int = field(metadata={"codec": U16}) + segment_indices: List[int] = field(metadata={"codec": Vec(U16)}) + + +@dataclass +class MsgCE140SegmentShard(Serializable): + bytes_: bytes = field(metadata={"codec": Array(U8, 4104 // calculate_r())}) + + +@dataclass +class MsgCE140Justification(Serializable): + nodes: List[bytes] = field(metadata={"codec": Vec(Vec(U8))}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce141.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce141.py new file mode 100644 index 00000000..6cd3b392 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce141.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from jamcodec.mixins import Serializable +from jamcodec.types import H256, H512, U8, Vec + + +@dataclass +class MsgCE141Assurance(Serializable): + header_hash: bytes = field(metadata={"codec": H256}) + bitfield: bytes = field(metadata={"codec": Vec(U8)}) + signature: bytes = field(metadata={"codec": H512}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce142.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce142.py new file mode 100644 index 00000000..af31f5e2 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce142.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from jamcodec.mixins import Serializable +from jamcodec.types import H256, U32 + + +@dataclass +class MsgCE142PreimageAnnouncement(Serializable): + service_id: int = field(metadata={"codec": U32}) + hash: bytes = field(metadata={"codec": H256}) + preimage_length: int = field(metadata={"codec": U32}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce143.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce143.py new file mode 100644 index 00000000..91cf1ca9 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce143.py @@ -0,0 +1,16 @@ +from __future__ import annotations + +from dataclasses import dataclass, field + +from jamcodec.mixins import Serializable +from jamcodec.types import H256, U8, Vec + + +@dataclass +class MsgCE143HashRequest(Serializable): + hash: bytes = field(metadata={"codec": H256}) + + +@dataclass +class MsgCE143Preimage(Serializable): + bytes_: bytes = field(metadata={"codec": Vec(U8)}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce144.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce144.py new file mode 100644 index 00000000..0ef2c7b2 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce144.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import List + +from jamcodec.mixins import Serializable +from jamcodec.types import Array, H256, H512, U8, U16, U32, Vec + + +@dataclass +class MsgCE144CoreWRPair(Serializable): + core_index: int = field(metadata={"codec": U16}) + wr_hash: bytes = field(metadata={"codec": H256}) + + +@dataclass +class MsgCE144Announcement(Serializable): + header_hash: bytes = field(metadata={"codec": H256}) + tranche: int = field(metadata={"codec": U8}) + announcement: List[MsgCE144CoreWRPair] = field(metadata={"codec": Vec(MsgCE144CoreWRPair.to_codec_def())}) + signature: bytes = field(metadata={"codec": H512}) + + +@dataclass +class MsgCE144NoShow(Serializable): + validator_index: int = field(metadata={"codec": U32}) + announcement: bytes = field(metadata={"codec": Vec(U8)}) + + +@dataclass +class MsgCE144TrancheEvidenceFirst(Serializable): + signature: bytes = field(metadata={"codec": Array(U8, 96)}) + + +@dataclass +class MsgCE144TrancheEvidenceSubsequent(Serializable): + signature: bytes = field(metadata={"codec": Array(U8, 96)}) + no_shows: List[MsgCE144NoShow] = field(metadata={"codec": Vec(MsgCE144NoShow.to_codec_def())}) + + +@dataclass +class MsgCE144Evidence(Serializable): + data: bytes = field(metadata={"codec": Vec(U8)}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/ce145.py b/pyjamaz/transport/jamnp_s/protocol/messages/ce145.py new file mode 100644 index 00000000..f23d05f5 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/ce145.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from enum import Enum + +from jamcodec.mixins import Serializable +from jamcodec.types import H256, H512, U8, U32 + + +class JudgmentValidity(Enum): + INVALID = 0 + VALID = 1 + + +@dataclass +class MsgCE145JudgmentPublication(Serializable): + epoch_index: int = field(metadata={"codec": U32}) + validator_index: int = field(metadata={"codec": U32}) + validity: int = field(metadata={"codec": U8}) + wr_hash: bytes = field(metadata={"codec": H256}) + signature: bytes = field(metadata={"codec": H512}) diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/common.py b/pyjamaz/transport/jamnp_s/protocol/messages/common.py new file mode 100644 index 00000000..aec620b9 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/common.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from jamcodec.base import JamBytes, JamCodecType +from jamcodec.types import Vec + +from pyjamaz.graypaper_constants import VALIDATOR_COUNT + + +class ImplicitVec(Vec): + def encode(self, value: list) -> JamBytes: + data = JamBytes(bytes()) + + for idx, item in enumerate(value): + if type(item) is JamBytes: + data += item + else: + data += self.type_def.encode(item) + if item and issubclass(item.__class__, JamCodecType): + value[idx] = item.serialize() + + return data + + def decode(self, data: JamBytes) -> list: + value = [] + + while True: + obj = self.type_def.new() + obj.decode(data) + value.append(obj) + + if data.get_remaining_length() == 0: + break + + return value + + +def calculate_r() -> int: + return (VALIDATOR_COUNT // 3) + 1 diff --git a/pyjamaz/transport/jamnp_s/protocol/messages/up0.py b/pyjamaz/transport/jamnp_s/protocol/messages/up0.py new file mode 100644 index 00000000..8cc458d1 --- /dev/null +++ b/pyjamaz/transport/jamnp_s/protocol/messages/up0.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import List + +from jamcodec.mixins import Serializable +from jamcodec.types import H256, U32, Vec + +from pyjamaz.models.block import Header + + +@dataclass +class MsgUP0Leaf(Serializable): + header_hash: bytes = field(metadata={"codec": H256}) + timeslot: int = field(metadata={"codec": U32}) + + +@dataclass +class MsgUP0Handshake(Serializable): + header_hash: bytes = field(metadata={"codec": H256}) + timeslot: int = field(metadata={"codec": U32}) + leafs: List[MsgUP0Leaf] = field(metadata={"codec": Vec(MsgUP0Leaf.to_codec_def())}) + + +@dataclass +class MsgUP0Announcement(Serializable): + header: Header = field(metadata={"codec": Header.to_codec_def()}) + header_hash: bytes = field(metadata={"codec": H256}) + timeslot: int = field(metadata={"codec": U32}) diff --git a/pyjamaz/transport/jamnp_s/streams/up0.py b/pyjamaz/transport/jamnp_s/protocol/up0.py similarity index 95% rename from pyjamaz/transport/jamnp_s/streams/up0.py rename to pyjamaz/transport/jamnp_s/protocol/up0.py index 7d12007b..92296cc4 100644 --- a/pyjamaz/transport/jamnp_s/streams/up0.py +++ b/pyjamaz/transport/jamnp_s/protocol/up0.py @@ -7,10 +7,12 @@ from jamcodec.base import JamBytes from pyjamaz.transport.jamnp_s.connection import JAMConnectionDirection -from pyjamaz.transport.jamnp_s.streams.base import ContextualStreamHandler -from pyjamaz.transport.jamnp_s.message_types import ( +from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler +from pyjamaz.transport.jamnp_s.protocol.messages.ce128 import ( MsgCE128BlockRequest, MsgCE128BlockRequestDirection, +) +from pyjamaz.transport.jamnp_s.protocol.messages.up0 import ( MsgUP0Announcement, MsgUP0Handshake, ) @@ -29,7 +31,7 @@ class UP0StreamState: phase: UPState = UPState.IN_PROGRESS -class UP0Handler(ContextualStreamHandler): +class UP0Handler(StreamHandler): kind = StreamKind.UP0_BlockAnnouncement def __init__(self, context) -> None: diff --git a/pyjamaz/transport/jamnp_s/stream_manager.py b/pyjamaz/transport/jamnp_s/stream_manager.py index 0e1cde03..1c512014 100644 --- a/pyjamaz/transport/jamnp_s/stream_manager.py +++ b/pyjamaz/transport/jamnp_s/stream_manager.py @@ -3,7 +3,7 @@ import logging from typing import Dict, Tuple, TYPE_CHECKING -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamDirection, StreamHandler, StreamKind +from pyjamaz.transport.jamnp_s.types import IStreamHandler, ManagedStream, StreamDirection, StreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -14,11 +14,11 @@ class StreamManager: def __init__(self, max_message_size: int) -> None: self.max_message_size = max_message_size - self.handlers: Dict[StreamKind, StreamHandler] = {} + self.handlers: Dict[StreamKind, IStreamHandler] = {} self._streams: Dict[Tuple[int, int], ManagedStream] = {} self._pending_kind_buffers: Dict[Tuple[int, int], bytearray] = {} - def register_handler(self, handler: StreamHandler) -> None: + def register_handler(self, handler: IStreamHandler) -> None: self.handlers[handler.kind] = handler def open_outgoing(self, connection: "JAMConnection", kind: StreamKind) -> ManagedStream: diff --git a/pyjamaz/transport/jamnp_s/streams/__init__.py b/pyjamaz/transport/jamnp_s/streams/__init__.py deleted file mode 100644 index 48d3788d..00000000 --- a/pyjamaz/transport/jamnp_s/streams/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -from pyjamaz.transport.jamnp_s.streams.ce129 import CE129Handler -from pyjamaz.transport.jamnp_s.streams.ce128 import CE128Handler -from pyjamaz.transport.jamnp_s.streams.ce131 import CE131Handler -from pyjamaz.transport.jamnp_s.streams.ce132 import CE132Handler -from pyjamaz.transport.jamnp_s.streams.ce133 import CE133Handler -from pyjamaz.transport.jamnp_s.streams.ce134 import CE134Handler -from pyjamaz.transport.jamnp_s.streams.ce135 import CE135Handler -from pyjamaz.transport.jamnp_s.streams.ce136 import CE136Handler -from pyjamaz.transport.jamnp_s.streams.ce137 import CE137Handler -from pyjamaz.transport.jamnp_s.streams.ce138 import CE138Handler -from pyjamaz.transport.jamnp_s.streams.ce139 import CE139Handler -from pyjamaz.transport.jamnp_s.streams.ce140 import CE140Handler -from pyjamaz.transport.jamnp_s.streams.ce141 import CE141Handler -from pyjamaz.transport.jamnp_s.streams.ce142 import CE142Handler -from pyjamaz.transport.jamnp_s.streams.ce143 import CE143Handler -from pyjamaz.transport.jamnp_s.streams.ce144 import CE144Handler -from pyjamaz.transport.jamnp_s.streams.ce145 import CE145Handler -from pyjamaz.transport.jamnp_s.streams.context import ProtocolContext, ProtocolSharedState -from pyjamaz.transport.jamnp_s.streams.up0 import UP0Handler - - -def register_handlers(stream_manager, context: ProtocolContext): - handlers = [ - UP0Handler(context), - CE128Handler(context), - CE129Handler(context), - CE131Handler(context), - CE132Handler(context), - CE133Handler(context), - CE134Handler(context), - CE135Handler(context), - CE136Handler(context), - CE137Handler(context), - CE138Handler(context), - CE139Handler(context), - CE140Handler(context), - CE141Handler(context), - CE142Handler(context), - CE143Handler(context), - CE144Handler(context), - CE145Handler(context), - ] - - for handler in handlers: - context.register_handler(handler) - stream_manager.register_handler(handler) - return context.handlers - - -__all__ = [ - "ProtocolContext", - "ProtocolSharedState", - "register_handlers", -] diff --git a/pyjamaz/transport/jamnp_s/types.py b/pyjamaz/transport/jamnp_s/types.py index 9652026c..a092dc66 100644 --- a/pyjamaz/transport/jamnp_s/types.py +++ b/pyjamaz/transport/jamnp_s/types.py @@ -37,7 +37,7 @@ class StreamDirection(Enum): acceptor = 1 -class StreamHandler(Protocol): +class IStreamHandler(Protocol): kind: StreamKind def init_stream(self, stream: "ManagedStream") -> None: @@ -62,7 +62,7 @@ class ManagedStream: stream_kind: StreamKind conn: "JAMConnection" direction: StreamDirection - handler: Optional[StreamHandler] = field(default=None, repr=False) + handler: Optional[IStreamHandler] = field(default=None, repr=False) closed: bool = False recv_buffer: bytearray = field(default_factory=bytearray, repr=False) expected_len: Optional[int] = field(default=None, repr=False) From 2ee5c8392f09ec2a8ae9fd26b98b3cc0fd65bc6d Mon Sep 17 00:00:00 2001 From: matthijsb Date: Wed, 8 Apr 2026 14:44:14 +0200 Subject: [PATCH 46/51] - seems to work! --- pyjamaz/cli.py | 36 ++- pyjamaz/settings.py | 1 + pyjamaz/transport/cert.py | 15 + pyjamaz/transport/jamnp_s/connection.py | 32 ++ pyjamaz/transport/jamnp_s/network.py | 45 ++- .../transport/jamnp_s/validator_manager.py | 279 +++++++++++------- scripts/init_nodes.sh | 7 +- 7 files changed, 301 insertions(+), 114 deletions(-) diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index bb93b208..e3c36e7d 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -33,7 +33,7 @@ from pyjamaz.storage import InMemoryStorageEngine, RocksDBStorageEngine from pyjamaz.models.block import Block, Header, Extrinsic from pyjamaz.fuzzer import FuzzerMessage, InitializeMessage, FuzzerTarget, FuzzerSession, AncestryItem -from pyjamaz.transport.cert import generate_cert, write_cert +from pyjamaz.transport.cert import generate_cert, read_cert_public_key, write_cert from pyjamaz.transport.jamnp_s.network import JAMNPS from pyjamaz.transport.pubsub import PubSub, PubSubSignal @@ -275,6 +275,14 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, logging.info(f'🌐 Peer ID: {quic_peer_id(app.config.keys.ed25519.public_key)}') logging.info(f'🔑 Bandersnatch public: {format_hash(app.config.keys.bandersnatch.public_key)}') logging.info(f'🔑 Ed25519 public: {format_hash(app.config.keys.ed25519.public_key)}') + #TODO: for now we assume we are either in the validator pool or are a listening node + validator_index = app.get_validator_index() + if validator_index is None: + logging.warning( + "⚠️ Local key not in validator pool; acting as non participating node" + ) + else: + logging.info(f"🧩 Validator pool index: #{validator_index}") logging.info(f'🗓️ Common Era: {app.config.common_era} ({common_era_time})') logging.info(f'🌲 State trie root: {format_hash(app.working_state.state_root)}') logging.info(f'📦 Finalized block: {format_hash(app.state_storage.finalized_block_hash)}') @@ -301,12 +309,11 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, certificate_file = os.path.join(db_path, "cert.pem") pk_file = os.path.join(db_path, "cert.key") - #nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app) - # (host, port, certificate, private_key, app, initial_slot_nr, initial_block_hash): - #initial_block_hash = app.retrieve_block_hash(0).hex() + await ensure_certificate_matches_seed(db_path, seed) nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app) app.protocol = nps_protocol tg.start_soon(nps_protocol.listen) + tg.start_soon(nps_protocol.maintain_validator_connections) if bootnode: # logging.debug(f'Connecting to node {validator_address}:{validator_port}') @@ -506,6 +513,27 @@ async def init_certificate(db_path, seed): write_cert(pk_pem, pk_file, cert_pem, pem_file) +async def ensure_certificate_matches_seed(db_path, seed): + cert_file = os.path.join(db_path, "cert.pem") + pk_file = os.path.join(db_path, "cert.key") + expected_public_key = Keys.from_seed(bytes.fromhex(seed[2:])).ed25519.public_key + + if not os.path.exists(cert_file) or not os.path.exists(pk_file): + logging.info("🔐 JAMNP-S certificate missing; generating a new certificate") + await init_certificate(db_path, seed) + return + + actual_public_key = read_cert_public_key(cert_file) + if actual_public_key == expected_public_key: + return + + logging.warning( + "🔐 JAMNP-S certificate key does not match the provided seed; regenerating certificate " + f"(cert={format_hash(actual_public_key)}, expected={format_hash(expected_public_key)})" + ) + await init_certificate(db_path, seed) + + @main.command() @click.option('--seed', 'seed', type=str, help="Seed to use for validator keys") @click.option('--chainspec', 'chainspec', type=click.Choice(['dev', 'docker']), help="Chainspec to use as genesis", default='dev', show_default=True) diff --git a/pyjamaz/settings.py b/pyjamaz/settings.py index d7c4f06a..6a1bc603 100644 --- a/pyjamaz/settings.py +++ b/pyjamaz/settings.py @@ -35,6 +35,7 @@ PVM_INTERPRETER = os.getenv("PVM_INTERPRETER", "NUMBA_AOT") RPC_SERVER_MAX_SIZE = 30 * 1024 * 1024 +VALIDATOR_ENDPOINT_OVERRIDES = {} try: from pyjamaz.local_settings import * diff --git a/pyjamaz/transport/cert.py b/pyjamaz/transport/cert.py index e54db302..71f07823 100644 --- a/pyjamaz/transport/cert.py +++ b/pyjamaz/transport/cert.py @@ -132,3 +132,18 @@ def write_cert(pk_pem, pk_file, cert_pem, cert_file): with open(cert_file, 'wb') as f: f.write(cert_pem) + + +def read_cert_public_key(cert_file: str) -> bytes: + with open(cert_file, "rb") as f: + cert_pem = f.read() + + cert = x509.load_pem_x509_certificate(cert_pem) + public_key = cert.public_key() + try: + return public_key.public_bytes_raw() + except AttributeError: + return public_key.public_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PublicFormat.Raw, + ) diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 2443f235..9a6ccb6a 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -2,6 +2,8 @@ import logging from enum import Enum +from cryptography.hazmat.primitives import serialization + try: from aioquic.asyncio import QuicConnectionProtocol from aioquic.quic.events import ( @@ -56,11 +58,26 @@ def __init__(self, *args, **kwargs): self.host = None self.port = None self.addr = None + self.validator_key = None self.stream_up = None self.streams = {} #TODO: typings self._keepalive_task = asyncio.create_task(self._keepalive()) + def _peer_validator_key(self) -> bytes | None: + peer_certificate = getattr(self._quic.tls, "_peer_certificate", None) + if peer_certificate is None: + return None + + public_key = peer_certificate.public_key() + try: + return public_key.public_bytes_raw() + except AttributeError: + return public_key.public_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PublicFormat.Raw, + ) + def close_stream(self, stream_id: int, clean_close: bool = True, reason: int = 0): logger.debug( @@ -145,6 +162,21 @@ def quic_event_received(self, event: QuicEvent) -> None: # Both nodes are validators, and are neighbours in the grid structure. # At least one of the nodes is not a validator. + peer_validator_key = self._peer_validator_key() + if peer_validator_key is not None: + if self.validator_key is not None and peer_validator_key != self.validator_key: + logger.warning( + "Connection %s peer certificate key mismatch: expected %s got %s", + self.jam_connection_ulid, + self.validator_key.hex(), + peer_validator_key.hex(), + ) + self.close(error_code=2, reason_phrase="validator key mismatch") + return + + if self.protocol.validator_manager.has_tracked_validator(peer_validator_key): + self.protocol.validator_manager.bind_connection(peer_validator_key, self) + if self.direction == JAMConnectionDirection.initiator: addr = f"{self.host}:{self.port}" if not self.protocol.peer_registry.activate(self, addr): diff --git a/pyjamaz/transport/jamnp_s/network.py b/pyjamaz/transport/jamnp_s/network.py index dce2826a..9409cc33 100644 --- a/pyjamaz/transport/jamnp_s/network.py +++ b/pyjamaz/transport/jamnp_s/network.py @@ -1,5 +1,6 @@ from __future__ import annotations +import asyncio import logging import ssl import uuid @@ -9,10 +10,12 @@ from aioquic.asyncio import serve from aioquic.asyncio.client import connect from aioquic.quic.configuration import QuicConfiguration + from aioquic.quic.connection import QuicConnection except ModuleNotFoundError as exc: _AIOQUIC_IMPORT_ERROR = exc serve = None connect = None + QuicConnection = None class QuicConfiguration: # type: ignore[override] def __init__(self, *args, **kwargs): @@ -35,6 +38,16 @@ def ULID(): logger = logging.getLogger("pyjamaz.transport.jamnp_s") +if QuicConnection is not None and not getattr(QuicConnection, "_pyjamaz_requests_client_cert", False): + _original_initialize = QuicConnection._initialize + + def _initialize_with_client_certificate(self, peer_cid: bytes) -> None: + _original_initialize(self, peer_cid) + self.tls._request_client_certificate = True + + QuicConnection._initialize = _initialize_with_client_certificate + QuicConnection._pyjamaz_requests_client_cert = True + def _build_client_configuration(protocol_name: str, certificate: str, private_key: str) -> QuicConfiguration: configuration = QuicConfiguration( @@ -86,6 +99,7 @@ def create_connection(*args, **kwargs): class JAMNPS(ProtocolType): MAX_MESSAGE_SIZE = 10000000 PROTOCOL_NAME = "jamnp-s/0/{}" + VALIDATOR_MAINTENANCE_INTERVAL = 3.0 def __init__(self, host, port, certificate, private_key, app): self.host = host @@ -103,6 +117,7 @@ def __init__(self, host, port, certificate, private_key, app): self.protocol_name = JAMNPS.PROTOCOL_NAME.format(bl_hash) self.configuration = _build_client_configuration(self.protocol_name, certificate, private_key) self.conn_out = None + self._dial_tasks: dict[str, asyncio.Task[None]] = {} self.peer_registry = PeerRegistry(self) self.stream_manager = StreamManager(self.MAX_MESSAGE_SIZE) @@ -110,6 +125,7 @@ def __init__(self, host, port, certificate, private_key, app): app=self.app, connect_callback=self.connect, disconnect_callback=self.disconnect, + local_port_override=self.port, ) self.context = ProtocolContext( app=self.app, @@ -173,13 +189,30 @@ async def update_validator_connections(self): await self.validator_manager.update_connections() async def connect(self, host: str, port: int, validator_key: Optional[bytes]): - configuration = _build_client_configuration(self.protocol_name, self.cert, self.pk) - addr = f"{host}:{port}" if addr in self.peer_registry.conn_addr: logger.warning(f"Ignoring duplicate connection to {addr}") return + if addr in self._dial_tasks: + logger.debug(f"Connection attempt already in progress for {addr}") + return + + task = asyncio.create_task(self._run_connection(host, port, validator_key)) + self._dial_tasks[addr] = task + + def _cleanup(finished: asyncio.Task[None]) -> None: + self._dial_tasks.pop(addr, None) + try: + finished.result() + except Exception as exc: + logger.warning(f"💩 Connection task for {addr} failed: {exc}") + + task.add_done_callback(_cleanup) + + async def _run_connection(self, host: str, port: int, validator_key: Optional[bytes]) -> None: + configuration = _build_client_configuration(self.protocol_name, self.cert, self.pk) + logger.info(f"Connecting to {host}:{port}") try: async with connect( @@ -200,6 +233,14 @@ async def connect(self, host: str, port: int, validator_key: Optional[bytes]): except ConnectionError as exc: logger.warning(f"💩 Cannot connect to {host}:{port} {exc}") + async def maintain_validator_connections(self) -> None: + while True: + try: + await self.update_validator_connections() + except Exception as exc: + logger.warning(f"Validator connectivity maintenance failed: {exc}") + await asyncio.sleep(self.VALIDATOR_MAINTENANCE_INTERVAL) + def disconnect(self, connection: JAMConnection, validator_key: Optional[bytes] = None): self.stream_manager.cleanup_connection(connection) self.peer_registry.unregister(connection) diff --git a/pyjamaz/transport/jamnp_s/validator_manager.py b/pyjamaz/transport/jamnp_s/validator_manager.py index 5d8cc711..537e610c 100644 --- a/pyjamaz/transport/jamnp_s/validator_manager.py +++ b/pyjamaz/transport/jamnp_s/validator_manager.py @@ -3,17 +3,28 @@ import logging import math import time +from base64 import b32encode from dataclasses import dataclass -from typing import Awaitable, Callable, Dict, Optional, TYPE_CHECKING +from enum import IntEnum +from typing import Awaitable, Callable, Dict, Iterable, Optional, TYPE_CHECKING -from aioquic.quic.connection import QuicConnectionState +try: + from aioquic.quic.connection import QuicConnectionState +except ModuleNotFoundError: + class QuicConnectionState(IntEnum): + FIRSTFLIGHT = 0 + CONNECTED = 1 + CLOSING = 2 + DRAINING = 3 + TERMINATED = 4 -from pyjamaz.models.common import ValidatorData +from pyjamaz import settings logger = logging.getLogger("pyjamaz.transport.jamnp_s") if TYPE_CHECKING: from pyjamaz.app import PyjamazApp + from pyjamaz.models.common import ValidatorData from pyjamaz.transport.jamnp_s.connection import JAMConnection ConnectCallback = Callable[[str, int, Optional[bytes]], Awaitable[None]] @@ -27,20 +38,26 @@ class ValidatorConnection: port: int connection: Optional["JAMConnection"] = None last_try: Optional[float] = None + desired_since: Optional[float] = None initiator: bool = False in_grid: bool = False class ValidatorConnectionManager: + FALLBACK_INITIATOR_TIMEOUT = 5.0 + RETRY_INTERVAL = 3.0 + def __init__( self, app: "PyjamazApp", connect_callback: ConnectCallback, disconnect_callback: DisconnectCallback, + local_port_override: Optional[int] = None, ) -> None: self.app = app self.connect_callback = connect_callback self.disconnect_callback = disconnect_callback + self.local_port_override = local_port_override self.connections: Dict[bytes, ValidatorConnection] = {} self.validator = None @@ -58,14 +75,19 @@ def refresh_local_validator(self) -> None: if self.app.config.keys is None: return - for validator in self.app.working_state.safrole.validators: + for validator in self._validator_sets(): if validator.ed25519 == self.app.config.keys.ed25519.public_key: self.validator = validator - self.validator_dns = validator.get_connection_dns() - self.validator_port = validator.get_metadata_port() - self.validator_address = validator.get_metadata_ipaddress() + self.validator_address, self.validator_port = self.resolve_validator_endpoint(validator) + peer_id = b32encode(validator.ed25519).decode("ascii").lower().rstrip("=") + self.validator_dns = f"e{peer_id}@{self.validator_address}:{self.validator_port}" return + def _validator_sets(self) -> Iterable[ValidatorData]: + yield from self.app.working_state.safrole.validators + yield from self.app.working_state.validator_queue.validators + yield from self.app.working_state.validator_archive.validators + @staticmethod def should_initiate_connection(validator_a: bytes, validator_b: bytes) -> bool: connect_a = 1 if validator_a[31] > 127 else 0 @@ -73,99 +95,131 @@ def should_initiate_connection(validator_a: bytes, validator_b: bytes) -> bool: a_less = 1 if validator_a < validator_b else 0 return (connect_a ^ connect_b ^ a_less) == 1 - def _add_grid_connections( + @staticmethod + def _override_key(validator: "ValidatorData") -> str: + return f"0x{validator.ed25519.hex()}" + + @staticmethod + def _parse_endpoint_override(value) -> Optional[tuple[str, int]]: + if isinstance(value, str): + host, sep, port_str = value.rpartition(":") + if not sep: + raise ValueError(f"Invalid validator endpoint override: {value!r}") + return host, int(port_str) + + if isinstance(value, (tuple, list)) and len(value) == 2: + host, port = value + return str(host), int(port) + + if value is None: + return None + + raise ValueError(f"Unsupported validator endpoint override: {value!r}") + + def _endpoint_override_for(self, validator: "ValidatorData") -> Optional[tuple[str, int]]: + overrides = getattr(settings, "VALIDATOR_ENDPOINT_OVERRIDES", {}) or {} + value = overrides.get(self._override_key(validator)) + if value is None: + value = overrides.get(validator.ed25519.hex()) + return self._parse_endpoint_override(value) + + def resolve_validator_endpoint(self, validator: ValidatorData) -> tuple[str, int]: + override = self._endpoint_override_for(validator) + if override is not None: + return override + + ip = validator.get_metadata_ipaddress() + port = validator.get_metadata_port() + if self.local_port_override is not None and self.validator is not None: + port_delta = self.local_port_override - self.validator.get_metadata_port() + port += port_delta + return ip, port + + def _build_neighbors( self, - validator_idx: int, - validator_queue, - same_epoch: bool, - initiate_conns: Dict[bytes, ValidatorData], - expect_conns: Dict[bytes, ValidatorData], - ) -> None: - width = max(1, math.floor(math.sqrt(len(validator_queue)))) + validator_set: list[ValidatorData], + local_key: bytes, + other_sets: Iterable[list[ValidatorData]], + ) -> Dict[bytes, ValidatorData]: + local_index = next( + (index for index, validator in enumerate(validator_set) if validator.ed25519 == local_key), + None, + ) + if local_index is None: + return {} - for v_idx, validator in validator_queue: - if self.validator.ed25519 == validator.ed25519: - continue + neighbors: Dict[bytes, ValidatorData] = {} + width = max(1, math.floor(math.sqrt(len(validator_set)))) + local_row = local_index // width + local_column = local_index % width - should_connect = False - if same_epoch: - same_row = (v_idx // width) == (validator_idx // width) - same_column = (validator_idx % width) == (v_idx % width) - should_connect = same_row or same_column - else: - should_connect = v_idx == validator_idx + for index, validator in enumerate(validator_set): + if (index // width) == local_row or (index % width) == local_column: + neighbors[validator.ed25519] = validator - if not should_connect: - continue + for other_set in other_sets: + if local_index < len(other_set): + validator = other_set[local_index] + neighbors[validator.ed25519] = validator - if self.should_initiate_connection(self.validator.ed25519, validator.ed25519): - initiate_conns[validator.ed25519] = validator - else: - expect_conns[validator.ed25519] = validator + neighbors.pop(local_key, None) + return neighbors + + def _collect_grid_neighbors(self) -> Dict[bytes, ValidatorData]: + if self.validator is None: + return {} + + previous = self.app.working_state.validator_archive.validators + current = self.app.working_state.safrole.validators + next_ = self.app.working_state.validator_queue.validators + + neighbors: Dict[bytes, ValidatorData] = {} + local_key = self.validator.ed25519 + + for validator in self._build_neighbors(previous, local_key, [current, next_]).values(): + neighbors[validator.ed25519] = validator + for validator in self._build_neighbors(current, local_key, []).values(): + neighbors[validator.ed25519] = validator + for validator in self._build_neighbors(next_, local_key, []).values(): + neighbors[validator.ed25519] = validator + + return neighbors + + @staticmethod + def _connection_is_active(connection: Optional["JAMConnection"]) -> bool: + if connection is None: + return False + + quic = getattr(connection, "_quic", None) + if quic is None: + return False + + if getattr(quic, "_close_pending", False): + return False + + state = getattr(quic, "_state", None) + if state is None: + return False + + terminal_states = { + QuicConnectionState.CLOSING, + QuicConnectionState.DRAINING, + QuicConnectionState.TERMINATED, + } + return state not in terminal_states + + def has_tracked_validator(self, validator_key: bytes) -> bool: + return validator_key in self.connections async def update_connections(self) -> None: self.refresh_local_validator() if not self.validator: - raise Exception("This node is not a validator") - - prev_validators = list(enumerate(self.app.working_state.validator_archive.validators)) - next_validators = list(enumerate(self.app.working_state.validator_queue.validators)) - active_validators = list(enumerate(self.app.working_state.safrole.validators)) - - validator_idx = None - for v_idx, validator in active_validators: - if validator.ed25519 == self.validator.ed25519: - validator_idx = v_idx - break - - if validator_idx is None: - logger.debug( - f"Current validator {self.validator.ed25519.hex()} is not present in the validator queue" - ) return - initiate_grid_connections: Dict[bytes, ValidatorData] = {} - expected_grid_connections: Dict[bytes, ValidatorData] = {} - self._add_grid_connections( - validator_idx, - prev_validators, - same_epoch=False, - initiate_conns=initiate_grid_connections, - expect_conns=expected_grid_connections, - ) - self._add_grid_connections( - validator_idx, - active_validators, - same_epoch=True, - initiate_conns=initiate_grid_connections, - expect_conns=expected_grid_connections, - ) - self._add_grid_connections( - validator_idx, - next_validators, - same_epoch=False, - initiate_conns=initiate_grid_connections, - expect_conns=expected_grid_connections, - ) - - initiate_connections: Dict[bytes, ValidatorData] = {} - expected_connections: Dict[bytes, ValidatorData] = {} - for _, validator in active_validators: - if validator.ed25519 == self.validator.ed25519: - continue - - if self.should_initiate_connection(self.validator.ed25519, validator.ed25519): - initiate_connections[validator.ed25519] = validator - else: - expected_connections[validator.ed25519] = validator - - new_keys = ( - set(initiate_connections) - | set(expected_connections) - | set(initiate_grid_connections) - | set(expected_grid_connections) - ) + now = time.time() + new_neighbors = self._collect_grid_neighbors() + new_keys = set(new_neighbors) current_keys = set(self.connections) for ed25519 in current_keys - new_keys: @@ -174,42 +228,55 @@ async def update_connections(self) -> None: self.disconnect_callback(state.connection, ed25519) for ed25519 in new_keys: - validator = ( - initiate_connections.get(ed25519) - or expected_connections.get(ed25519) - or initiate_grid_connections.get(ed25519) - or expected_grid_connections.get(ed25519) - ) - if validator is None: - continue - - is_initiator = ed25519 in initiate_connections or ed25519 in initiate_grid_connections - in_grid = ed25519 in initiate_grid_connections or ed25519 in expected_grid_connections + validator = new_neighbors[ed25519] + is_initiator = self.should_initiate_connection(self.validator.ed25519, validator.ed25519) + in_grid = True + ip, port = self.resolve_validator_endpoint(validator) state = self.connections.get(ed25519) if state is None: state = ValidatorConnection( validator=validator, - ip=validator.get_metadata_ipaddress(), - port=validator.get_metadata_port(), + ip=ip, + port=port, initiator=is_initiator, in_grid=in_grid, + desired_since=now, ) self.connections[ed25519] = state else: state.validator = validator - state.ip = validator.get_metadata_ipaddress() - state.port = validator.get_metadata_port() + state.ip = ip + state.port = port state.initiator = is_initiator state.in_grid = in_grid + if state.desired_since is None: + state.desired_since = now + + if self._connection_is_active(state.connection): + continue + + state.connection = None + + if state.last_try is not None and (now - state.last_try) < self.RETRY_INTERVAL: + continue + + should_attempt = state.initiator + if ( + not should_attempt + and state.desired_since is not None + and (now - state.desired_since) >= self.FALLBACK_INITIATOR_TIMEOUT + ): + should_attempt = True - if not (state.in_grid and state.initiator): + if not should_attempt: continue - if state.connection is None or state.connection._quic._state > QuicConnectionState.CONNECTED: - await self.connect_callback(state.ip, state.port, ed25519) + state.last_try = now + await self.connect_callback(state.ip, state.port, ed25519) def bind_connection(self, validator_key: bytes, connection: "JAMConnection") -> None: + connection.validator_key = validator_key state = self.connections.get(validator_key) if state is None: return diff --git a/scripts/init_nodes.sh b/scripts/init_nodes.sh index 4a86fbd7..2a400fa8 100644 --- a/scripts/init_nodes.sh +++ b/scripts/init_nodes.sh @@ -1,3 +1,6 @@ python pyjamaz/cli.py init --db-path ./pyjamaz/data/alice --seed 0x0000000000000000000000000000000000000000000000000000000000000000 --force-overwrite -python pyjamaz/cli.py init --db-path ./pyjamaz/data/bob --seed 0x0000000000000000000000000000000000000000000000000000000000000001 --force-overwrite -python pyjamaz/cli.py init --db-path ./pyjamaz/data/john --seed 0x0000000000000000000000000000000000000000000000000000000000000002 --force-overwrite +python pyjamaz/cli.py init --db-path ./pyjamaz/data/bob --seed 0x0100000001000000010000000100000001000000010000000100000001000000 --force-overwrite +python pyjamaz/cli.py init --db-path ./pyjamaz/data/charlie --seed 0x0200000002000000020000000200000002000000020000000200000002000000 --force-overwrite +python pyjamaz/cli.py init --db-path ./pyjamaz/data/dave --seed 0x0300000003000000030000000300000003000000030000000300000003000000 --force-overwrite +python pyjamaz/cli.py init --db-path ./pyjamaz/data/eve --seed 0x0400000004000000040000000400000004000000040000000400000004000000 --force-overwrite +python pyjamaz/cli.py init --db-path ./pyjamaz/data/ferdie --seed 0x0500000005000000050000000500000005000000050000000500000005000000 --force-overwrite From 74f377440baf5d609c5d00d7bb1d74a2cb88434a Mon Sep 17 00:00:00 2001 From: matthijsb Date: Tue, 14 Apr 2026 14:14:52 +0200 Subject: [PATCH 47/51] - NP refactor --- pyjamaz/cli.py | 2 +- pyjamaz/transport/jamnp_s/connection.py | 85 +++++--------- pyjamaz/transport/jamnp_s/network.py | 109 ++++++++++-------- pyjamaz/transport/jamnp_s/peers.py | 5 +- pyjamaz/transport/jamnp_s/protocol/base.py | 30 ++--- pyjamaz/transport/jamnp_s/protocol/ce128.py | 24 ++-- pyjamaz/transport/jamnp_s/protocol/ce129.py | 8 +- pyjamaz/transport/jamnp_s/protocol/ce131.py | 18 +-- pyjamaz/transport/jamnp_s/protocol/ce132.py | 24 ++-- pyjamaz/transport/jamnp_s/protocol/ce133.py | 22 ++-- pyjamaz/transport/jamnp_s/protocol/ce134.py | 22 ++-- pyjamaz/transport/jamnp_s/protocol/ce135.py | 14 +-- pyjamaz/transport/jamnp_s/protocol/ce136.py | 14 +-- pyjamaz/transport/jamnp_s/protocol/ce137.py | 14 +-- pyjamaz/transport/jamnp_s/protocol/ce138.py | 14 +-- pyjamaz/transport/jamnp_s/protocol/ce139.py | 14 +-- pyjamaz/transport/jamnp_s/protocol/ce140.py | 14 +-- pyjamaz/transport/jamnp_s/protocol/ce141.py | 14 +-- pyjamaz/transport/jamnp_s/protocol/ce142.py | 14 +-- pyjamaz/transport/jamnp_s/protocol/ce143.py | 18 +-- pyjamaz/transport/jamnp_s/protocol/ce144.py | 18 +-- pyjamaz/transport/jamnp_s/protocol/ce145.py | 14 +-- pyjamaz/transport/jamnp_s/protocol/context.py | 6 +- pyjamaz/transport/jamnp_s/protocol/up0.py | 22 ++-- pyjamaz/transport/jamnp_s/stream_manager.py | 83 +++++++------ pyjamaz/transport/jamnp_s/types.py | 18 +-- .../transport/jamnp_s/validator_manager.py | 74 +++++------- 27 files changed, 356 insertions(+), 358 deletions(-) diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index e3c36e7d..d14c73c2 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -313,7 +313,7 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, nps_protocol = JAMNPS(host, port, certificate_file, pk_file, app) app.protocol = nps_protocol tg.start_soon(nps_protocol.listen) - tg.start_soon(nps_protocol.maintain_validator_connections) + tg.start_soon(nps_protocol.check_connections) if bootnode: # logging.debug(f'Connecting to node {validator_address}:{validator_port}') diff --git a/pyjamaz/transport/jamnp_s/connection.py b/pyjamaz/transport/jamnp_s/connection.py index 9a6ccb6a..d76e9929 100644 --- a/pyjamaz/transport/jamnp_s/connection.py +++ b/pyjamaz/transport/jamnp_s/connection.py @@ -4,38 +4,16 @@ from cryptography.hazmat.primitives import serialization -try: - from aioquic.asyncio import QuicConnectionProtocol - from aioquic.quic.events import ( - ConnectionTerminated, - HandshakeCompleted, - QuicEvent, - StreamDataReceived, - StreamReset, - ) -except ModuleNotFoundError as exc: - _AIOQUIC_IMPORT_ERROR = exc +from aioquic.asyncio import QuicConnectionProtocol +from aioquic.quic.events import ( + ConnectionTerminated, + HandshakeCompleted, + QuicEvent, + StreamDataReceived, + StreamReset, +) - class QuicConnectionProtocol: # type: ignore[override] - def __init__(self, *args, **kwargs): - raise ModuleNotFoundError("aioquic is required to instantiate JAMConnection") from _AIOQUIC_IMPORT_ERROR - - class QuicEvent: - pass - - class HandshakeCompleted(QuicEvent): - pass - - class StreamReset(QuicEvent): - pass - - class StreamDataReceived(QuicEvent): - pass - - class ConnectionTerminated(QuicEvent): - pass - -from pyjamaz.transport.jamnp_s.types import StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -50,7 +28,7 @@ class JAMConnection(QuicConnectionProtocol): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # Note: should be set in wrap_protocol + # Note: is set in wrap_protocol call self.direction:JAMConnectionDirection = None self.protocol = None self.stream_manager = None @@ -62,21 +40,7 @@ def __init__(self, *args, **kwargs): self.stream_up = None self.streams = {} #TODO: typings - self._keepalive_task = asyncio.create_task(self._keepalive()) - - def _peer_validator_key(self) -> bytes | None: - peer_certificate = getattr(self._quic.tls, "_peer_certificate", None) - if peer_certificate is None: - return None - - public_key = peer_certificate.public_key() - try: - return public_key.public_bytes_raw() - except AttributeError: - return public_key.public_bytes( - encoding=serialization.Encoding.Raw, - format=serialization.PublicFormat.Raw, - ) + self._keepalive_task = asyncio.create_task(self.keepalive()) def close_stream(self, stream_id: int, clean_close: bool = True, reason: int = 0): @@ -139,7 +103,7 @@ def connection_lost(self, exc): super().connection_lost(exc) - async def _keepalive(self): + async def keepalive(self): try: while True: #TODO: what is a sane amount of time? Usefull for detecting disconnect (early)? @@ -150,6 +114,21 @@ async def _keepalive(self): pass + def get_peer_validator_key(self) -> bytes | None: + peer_certificate = getattr(self._quic.tls, "_peer_certificate", None) + if peer_certificate is None: + return None + + public_key = peer_certificate.public_key() + try: + return public_key.public_bytes_raw() + except AttributeError: + return public_key.public_bytes( + encoding=serialization.Encoding.Raw, + format=serialization.PublicFormat.Raw, + ) + + def quic_event_received(self, event: QuicEvent) -> None: if event and hasattr(event, "stream_id"): if event.stream_id != 0: @@ -160,9 +139,9 @@ def quic_event_received(self, event: QuicEvent) -> None: if isinstance(event, HandshakeCompleted): #TODO: enforce: # Both nodes are validators, and are neighbours in the grid structure. - # At least one of the nodes is not a validator. + # Or at least one of the nodes is not a validator. - peer_validator_key = self._peer_validator_key() + peer_validator_key = self.get_peer_validator_key() if peer_validator_key is not None: if self.validator_key is not None and peer_validator_key != self.validator_key: logger.warning( @@ -183,11 +162,11 @@ def quic_event_received(self, event: QuicEvent) -> None: return # Initiating side will send a JAM handshake message and set the stream id - stream_up = self.stream_manager.open_outgoing(self, StreamKind.UP0_BlockAnnouncement) + stream_up = self.stream_manager.open_outgoing(self, JAMStreamKind.UP0_BlockAnnouncement) self.stream_up = stream_up - self.protocol.handler(StreamKind.UP0_BlockAnnouncement).send_handshake(self) + self.protocol.handler(JAMStreamKind.UP0_BlockAnnouncement).send_handshake(self) else: - # Note: it seems only now we have this info available from the accepting side + # Note: it seems only now we have this info available from the accepting side (aioquick quirk) self.host, self.port = self._quic._network_paths[0].addr[0:2] addr = f"{self.host}:{self.port}" if not self.protocol.peer_registry.activate(self, addr): diff --git a/pyjamaz/transport/jamnp_s/network.py b/pyjamaz/transport/jamnp_s/network.py index 9409cc33..7797ecb5 100644 --- a/pyjamaz/transport/jamnp_s/network.py +++ b/pyjamaz/transport/jamnp_s/network.py @@ -3,42 +3,40 @@ import asyncio import logging import ssl -import uuid from typing import Optional, cast -try: - from aioquic.asyncio import serve - from aioquic.asyncio.client import connect - from aioquic.quic.configuration import QuicConfiguration - from aioquic.quic.connection import QuicConnection -except ModuleNotFoundError as exc: - _AIOQUIC_IMPORT_ERROR = exc - serve = None - connect = None - QuicConnection = None - - class QuicConfiguration: # type: ignore[override] - def __init__(self, *args, **kwargs): - raise ModuleNotFoundError("aioquic is required to use JAMNPS networking") from _AIOQUIC_IMPORT_ERROR - -try: - from ulid import ULID -except ModuleNotFoundError: - def ULID(): - return uuid.uuid4().hex +from ulid import ULID + +from aioquic.asyncio import serve +from aioquic.asyncio.client import connect +from aioquic.quic.configuration import QuicConfiguration +from aioquic.quic.connection import QuicConnection from pyjamaz.constants import MESSAGE_TYPES from pyjamaz.transport.jamnp_s.connection import JAMConnection, JAMConnectionDirection from pyjamaz.transport.jamnp_s.peers import PeerRegistry from pyjamaz.transport.jamnp_s.stream_manager import StreamManager from pyjamaz.transport.jamnp_s.protocol import ProtocolContext, ProtocolSharedState, register_handlers -from pyjamaz.transport.jamnp_s.types import StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStreamKind from pyjamaz.transport.jamnp_s.validator_manager import ValidatorConnectionManager from pyjamaz.transport.types import ProtocolType logger = logging.getLogger("pyjamaz.transport.jamnp_s") -if QuicConnection is not None and not getattr(QuicConnection, "_pyjamaz_requests_client_cert", False): +""" +Note: Monkeypatch :S + +we monkeypatch aioquic.quic.connection.QuicConnection._initialize +after aioquic creates its internal TLS context, it sets self.tls._request_client_certificate = True +that makes the server send a TLS CertificateRequest, so the client includes its certificate during the handshake +pyjamaz/transport/jamnp_s/connection.py reads the peer certificate on HandshakeCompleted to derive the remote validator key and bind the connection + +As of aioquic is 1.2.0 + - QuicConfiguration has no public request_client_certificate option in aioquic/quic/configuration.py + - aioquic TLS context keeps _request_client_certificate = False by default, explicitly marked "for test purposes only" in aioquic/tls.py:1276 + - the server only emits CertificateRequest when that private flag is true in aioquic/tls.py:2036 +""" +if QuicConnection is not None and not getattr(QuicConnection, "_pyjamaz_client_cert_patch", False): _original_initialize = QuicConnection._initialize def _initialize_with_client_certificate(self, peer_cid: bytes) -> None: @@ -46,10 +44,10 @@ def _initialize_with_client_certificate(self, peer_cid: bytes) -> None: self.tls._request_client_certificate = True QuicConnection._initialize = _initialize_with_client_certificate - QuicConnection._pyjamaz_requests_client_cert = True + QuicConnection._pyjamaz_client_cert_patch = True -def _build_client_configuration(protocol_name: str, certificate: str, private_key: str) -> QuicConfiguration: +def quick_client_config(protocol_name: str, certificate: str, private_key: str) -> QuicConfiguration: configuration = QuicConfiguration( alpn_protocols=[protocol_name], is_client=True, @@ -59,7 +57,7 @@ def _build_client_configuration(protocol_name: str, certificate: str, private_ke return configuration -def _build_server_configuration(protocol_name: str, certificate: str, private_key: str) -> QuicConfiguration: +def quick_server_config(protocol_name: str, certificate: str, private_key: str) -> QuicConfiguration: configuration = QuicConfiguration( alpn_protocols=[protocol_name], is_client=False, @@ -68,7 +66,7 @@ def _build_server_configuration(protocol_name: str, certificate: str, private_ke return configuration -def _create_connection_factory( +def _create_jam_connection( network: "JAMNPS", direction: JAMConnectionDirection, host: str, @@ -99,7 +97,7 @@ def create_connection(*args, **kwargs): class JAMNPS(ProtocolType): MAX_MESSAGE_SIZE = 10000000 PROTOCOL_NAME = "jamnp-s/0/{}" - VALIDATOR_MAINTENANCE_INTERVAL = 3.0 + VALIDATOR_CHECK_INTERVAL = 3.0 def __init__(self, host, port, certificate, private_key, app): self.host = host @@ -115,9 +113,9 @@ def __init__(self, host, port, certificate, private_key, app): bl_hash = bl_hash[:8] self.protocol_name = JAMNPS.PROTOCOL_NAME.format(bl_hash) - self.configuration = _build_client_configuration(self.protocol_name, certificate, private_key) + self.configuration = quick_client_config(self.protocol_name, certificate, private_key) self.conn_out = None - self._dial_tasks: dict[str, asyncio.Task[None]] = {} + self.connection_tasks: dict[str, asyncio.Task[None]] = {} self.peer_registry = PeerRegistry(self) self.stream_manager = StreamManager(self.MAX_MESSAGE_SIZE) @@ -125,7 +123,7 @@ def __init__(self, host, port, certificate, private_key, app): app=self.app, connect_callback=self.connect, disconnect_callback=self.disconnect, - local_port_override=self.port, + port_override=self.port, ) self.context = ProtocolContext( app=self.app, @@ -136,43 +134,50 @@ def __init__(self, host, port, certificate, private_key, app): ) self.protocol_handlers = register_handlers(self.stream_manager, self.context) - up0_handler = self.handler(StreamKind.UP0_BlockAnnouncement) - ce128_handler = self.handler(StreamKind.CE128_BlockRequest) - ce131_handler = self.handler(StreamKind.CE131_SafroleTicketDistributionStep1) + #TODO: add all handlers + up0_handler = self.handler(JAMStreamKind.UP0_BlockAnnouncement) + ce128_handler = self.handler(JAMStreamKind.CE128_BlockRequest) + ce131_handler = self.handler(JAMStreamKind.CE131_SafroleTicketDistributionStep1) self.pubsub.subscribe(MESSAGE_TYPES.PRODUCED_BLOCK, up0_handler.broadcast_block) self.pubsub.subscribe(MESSAGE_TYPES.CE128_SUCCESS, ce128_handler.finish_block_request) self.pubsub.subscribe(MESSAGE_TYPES.CE128_FAILURE, ce128_handler.finish_block_request) self.pubsub.subscribe(MESSAGE_TYPES.TICKET_ADD, ce131_handler.broadcast_own_ticket) + @property def validator(self): return self.validator_manager.validator + @property def validator_dns(self): return self.validator_manager.validator_dns + @property def validator_port(self): return self.validator_manager.validator_port + @property def validator_address(self): return self.validator_manager.validator_address - def handler(self, kind: StreamKind): + + def handler(self, kind: JAMStreamKind): return self.protocol_handlers[kind] + async def listen(self): logger.info(f"Listening on {self.host}:{self.port}") - server_conf = _build_server_configuration(self.protocol_name, self.cert, self.pk) + server_conf = quick_server_config(self.protocol_name, self.cert, self.pk) await serve( self.host, self.port, configuration=server_conf, - create_protocol=_create_connection_factory( + create_protocol=_create_jam_connection( self, JAMConnectionDirection.acceptor, self.host, @@ -182,11 +187,14 @@ async def listen(self): retry=True, ) + def should_initiate_connection(self, validator_a: bytes, validator_b: bytes) -> bool: return self.validator_manager.should_initiate_connection(validator_a, validator_b) - async def update_validator_connections(self): - await self.validator_manager.update_connections() + + async def update_connections(self): + await self.validator_manager.update_validator_connections() + async def connect(self, host: str, port: int, validator_key: Optional[bytes]): addr = f"{host}:{port}" @@ -194,15 +202,15 @@ async def connect(self, host: str, port: int, validator_key: Optional[bytes]): logger.warning(f"Ignoring duplicate connection to {addr}") return - if addr in self._dial_tasks: + if addr in self.connection_tasks: logger.debug(f"Connection attempt already in progress for {addr}") return - task = asyncio.create_task(self._run_connection(host, port, validator_key)) - self._dial_tasks[addr] = task + task = asyncio.create_task(self.connect_task(host, port, validator_key)) + self.connection_tasks[addr] = task def _cleanup(finished: asyncio.Task[None]) -> None: - self._dial_tasks.pop(addr, None) + self.connection_tasks.pop(addr, None) try: finished.result() except Exception as exc: @@ -210,8 +218,9 @@ def _cleanup(finished: asyncio.Task[None]) -> None: task.add_done_callback(_cleanup) - async def _run_connection(self, host: str, port: int, validator_key: Optional[bytes]) -> None: - configuration = _build_client_configuration(self.protocol_name, self.cert, self.pk) + + async def connect_task(self, host: str, port: int, validator_key: Optional[bytes]) -> None: + configuration = quick_client_config(self.protocol_name, self.cert, self.pk) logger.info(f"Connecting to {host}:{port}") try: @@ -219,7 +228,7 @@ async def _run_connection(self, host: str, port: int, validator_key: Optional[by host, port, configuration=configuration, - create_protocol=_create_connection_factory( + create_protocol=_create_jam_connection( self, JAMConnectionDirection.initiator, host, @@ -233,13 +242,15 @@ async def _run_connection(self, host: str, port: int, validator_key: Optional[by except ConnectionError as exc: logger.warning(f"💩 Cannot connect to {host}:{port} {exc}") - async def maintain_validator_connections(self) -> None: + + async def check_connections(self) -> None: while True: try: - await self.update_validator_connections() + await self.update_connections() except Exception as exc: logger.warning(f"Validator connectivity maintenance failed: {exc}") - await asyncio.sleep(self.VALIDATOR_MAINTENANCE_INTERVAL) + await asyncio.sleep(self.VALIDATOR_CHECK_INTERVAL) + def disconnect(self, connection: JAMConnection, validator_key: Optional[bytes] = None): self.stream_manager.cleanup_connection(connection) diff --git a/pyjamaz/transport/jamnp_s/peers.py b/pyjamaz/transport/jamnp_s/peers.py index 3a284fe6..22eff046 100644 --- a/pyjamaz/transport/jamnp_s/peers.py +++ b/pyjamaz/transport/jamnp_s/peers.py @@ -20,6 +20,7 @@ def __init__(self, protocol: "JAMNPS") -> None: self.conn_initiated = set() self.conn_addr = set() + def register(self, connection: "JAMConnection") -> None: self.connections[connection.jam_connection_ulid] = connection if connection.direction == JAMConnectionDirection.initiator: @@ -27,10 +28,11 @@ def register(self, connection: "JAMConnection") -> None: else: self.conn_accepted.add(connection.jam_connection_ulid) + def activate(self, connection: "JAMConnection", addr: str) -> bool: if addr in self.conn_addr and connection.addr != addr: logger.warning( - f"Connection {connection.jam_connection_ulid} direction: {connection.direction} is a duplicate {addr}" + f"Duplicate connection from {connection.jam_connection_ulid} direction: {connection.direction} : {addr}" ) connection.close(error_code=2, reason_phrase="duplicate") self.unregister(connection) @@ -50,6 +52,7 @@ def activate(self, connection: "JAMConnection", addr: str) -> bool: ) return True + def unregister(self, connection: "JAMConnection") -> None: self.conn_initiated.discard(connection.jam_connection_ulid) self.conn_accepted.discard(connection.jam_connection_ulid) diff --git a/pyjamaz/transport/jamnp_s/protocol/base.py b/pyjamaz/transport/jamnp_s/protocol/base.py index 620fbf34..4dd3c1a3 100644 --- a/pyjamaz/transport/jamnp_s/protocol/base.py +++ b/pyjamaz/transport/jamnp_s/protocol/base.py @@ -2,61 +2,61 @@ from typing import TYPE_CHECKING -from pyjamaz.transport.jamnp_s.types import IStreamHandler, ManagedStream, StreamDirection +from pyjamaz.transport.jamnp_s.types import IStreamHandler, JAMStream, StreamDirection if TYPE_CHECKING: from pyjamaz.transport.jamnp_s.connection import JAMConnection from pyjamaz.transport.jamnp_s.protocol.context import ProtocolContext - from pyjamaz.transport.jamnp_s.types import StreamKind + from pyjamaz.transport.jamnp_s.types import JAMStreamKind class StreamHandler(IStreamHandler): - kind: "StreamKind" + kind: "JAMStreamKind" def __init__(self, context: "ProtocolContext") -> None: self.context = context - def init_stream(self, stream: ManagedStream) -> None: + def init_stream(self, stream: JAMStream) -> None: pass - def on_message(self, stream: ManagedStream, data: bytes) -> None: + def on_message(self, stream: JAMStream, data: bytes) -> None: if stream.direction == StreamDirection.initiator: self.initiator_message(stream, data) else: self.acceptor_message(stream, data) - def on_fin(self, stream: ManagedStream) -> None: + def on_fin(self, stream: JAMStream) -> None: if stream.direction == StreamDirection.initiator: self.initiator_fin(stream) else: self.acceptor_fin(stream) - def on_reset(self, stream: ManagedStream, reset_code: int) -> None: + def on_reset(self, stream: JAMStream, reset_code: int) -> None: if stream.direction == StreamDirection.initiator: self.initiator_reset(stream, reset_code) else: self.acceptor_reset(stream, reset_code) - def on_close(self, stream: ManagedStream) -> None: + def on_close(self, stream: JAMStream) -> None: pass - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: raise NotImplementedError - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: raise NotImplementedError - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: pass - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: pass - def initiator_reset(self, stream: ManagedStream, reset_code: int) -> None: + def initiator_reset(self, stream: JAMStream, reset_code: int) -> None: pass - def acceptor_reset(self, stream: ManagedStream, reset_code: int) -> None: + def acceptor_reset(self, stream: JAMStream, reset_code: int) -> None: pass - def open_outgoing(self, conn: "JAMConnection") -> ManagedStream: + def open_outgoing(self, conn: "JAMConnection") -> JAMStream: return self.context.stream_manager.open_outgoing(conn, self.kind) diff --git a/pyjamaz/transport/jamnp_s/protocol/ce128.py b/pyjamaz/transport/jamnp_s/protocol/ce128.py index 53bd84f3..e5b68864 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce128.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce128.py @@ -15,7 +15,7 @@ MsgCE128BlockRequestDirection, MsgCE128BlockRequestResponse, ) -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -26,16 +26,16 @@ class CE128StreamState: class CE128Handler(StreamHandler): - kind = StreamKind.CE128_BlockRequest + kind = JAMStreamKind.CE128_BlockRequest def __init__(self, context) -> None: super().__init__(context) self._streams = {} - def init_stream(self, stream: ManagedStream) -> None: + def init_stream(self, stream: JAMStream) -> None: self._streams[stream.stream_key] = CE128StreamState() - def initiate_block_request(self, conn, req: MsgCE128BlockRequest) -> ManagedStream: + def initiate_block_request(self, conn, req: MsgCE128BlockRequest) -> JAMStream: stream = self.open_outgoing(conn) logger.info( f"Initiate block request on stream id: {stream.stream_id} direction: {req.direction}, " @@ -51,7 +51,7 @@ def initiate_block_request(self, conn, req: MsgCE128BlockRequest) -> ManagedStre async def finish_block_request(self, *args, **kwargs) -> None: self.context.state_requesting_blocks = False - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.debug( f"CE128 initiated stream {stream.stream_id} received block request response: {len(data)} bytes" ) @@ -66,29 +66,29 @@ def initiator_message(self, stream: ManagedStream, data: bytes) -> None: ) ) - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug(f"CE128 acceptor stream {stream.stream_id} received block request") self._send_block_request_response( stream, MsgCE128BlockRequest.from_jam_bytes(JamBytes(data)), ) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: self._abort_block_request() - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: self._abort_block_request() - def initiator_reset(self, stream: ManagedStream, reset_code: int) -> None: + def initiator_reset(self, stream: JAMStream, reset_code: int) -> None: asyncio.create_task(self.finish_block_request(reset_code)) - def acceptor_reset(self, stream: ManagedStream, reset_code: int) -> None: + def acceptor_reset(self, stream: JAMStream, reset_code: int) -> None: asyncio.create_task(self.finish_block_request(reset_code)) - def on_close(self, stream: ManagedStream) -> None: + def on_close(self, stream: JAMStream) -> None: self._streams.pop(stream.stream_key, None) - def _send_block_request_response(self, stream: ManagedStream, block_req: MsgCE128BlockRequest) -> None: + def _send_block_request_response(self, stream: JAMStream, block_req: MsgCE128BlockRequest) -> None: block: Block = None blocks: List[Block] = [] last_block_hash = self.context.app.retrieve_block_hash(self.context.app.working_state.timeslot.number) diff --git a/pyjamaz/transport/jamnp_s/protocol/ce129.py b/pyjamaz/transport/jamnp_s/protocol/ce129.py index f33e58ac..51408821 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce129.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce129.py @@ -1,14 +1,14 @@ from __future__ import annotations from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind class CE129Handler(StreamHandler): - kind = StreamKind.CE129_StateRequest + kind = JAMStreamKind.CE129_StateRequest - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: pass - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: pass diff --git a/pyjamaz/transport/jamnp_s/protocol/ce131.py b/pyjamaz/transport/jamnp_s/protocol/ce131.py index af504b3e..7b4f3798 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce131.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce131.py @@ -13,7 +13,7 @@ MsgCE131SafroleTicket, MsgCE131SafroleTicketDistribution, ) -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -24,13 +24,13 @@ class CE131StreamState: class CE131Handler(StreamHandler): - kind = StreamKind.CE131_SafroleTicketDistributionStep1 + kind = JAMStreamKind.CE131_SafroleTicketDistributionStep1 def __init__(self, context) -> None: super().__init__(context) self._streams = {} - def init_stream(self, stream: ManagedStream) -> None: + def init_stream(self, stream: JAMStream) -> None: self._streams[stream.stream_key] = CE131StreamState() async def broadcast_own_ticket(self, data) -> int: @@ -60,27 +60,27 @@ async def broadcast_own_ticket(self, data) -> int: logger.info(f"Distributed ticket to {distributed_count} peers") return distributed_count - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.warning(f"Unexpected data in CE131 initiator: {len(data)} bytes") raise ValueError("Unexpected data in CE131 initiator") - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug(f"CE131 acceptor stream {stream.stream_id} received ticket") self._handle_received_ticket( stream, MsgCE131SafroleTicketDistribution.from_jam_bytes(JamBytes(data)), ) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.info("Finished with FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.info("Finished with FIN") - def on_close(self, stream: ManagedStream) -> None: + def on_close(self, stream: JAMStream) -> None: self._streams.pop(stream.stream_key, None) - def _handle_received_ticket(self, stream: ManagedStream, msg: MsgCE131SafroleTicketDistribution) -> None: + def _handle_received_ticket(self, stream: JAMStream, msg: MsgCE131SafroleTicketDistribution) -> None: logger.info(f"Received ticket for epoch {msg.epoch_index}") current_epoch = self.context.app.working_state.timeslot.number // EPOCH_TIMESLOTS diff --git a/pyjamaz/transport/jamnp_s/protocol/ce132.py b/pyjamaz/transport/jamnp_s/protocol/ce132.py index a3561fd1..3e459d6e 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce132.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce132.py @@ -10,7 +10,7 @@ from pyjamaz.models.block import TicketEnvelope from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler from pyjamaz.transport.jamnp_s.protocol.messages.ce132 import MsgCE132SafroleTicketDistribution -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -21,16 +21,16 @@ class CE132StreamState: class CE132Handler(StreamHandler): - kind = StreamKind.CE132_SafroleTicketDistributionStep2 + kind = JAMStreamKind.CE132_SafroleTicketDistributionStep2 def __init__(self, context) -> None: super().__init__(context) self._streams = {} - def init_stream(self, stream: ManagedStream) -> None: + def init_stream(self, stream: JAMStream) -> None: self._streams[stream.stream_key] = CE132StreamState() - def initiate_ticket_distribution(self, conn, msg: MsgCE132SafroleTicketDistribution) -> ManagedStream: + def initiate_ticket_distribution(self, conn, msg: MsgCE132SafroleTicketDistribution) -> JAMStream: stream = self.open_outgoing(conn) logger.info(f"Distribute ticket on stream id: {stream.stream_id} to {conn.host}:{conn.port}") conn.send( @@ -40,33 +40,33 @@ def initiate_ticket_distribution(self, conn, msg: MsgCE132SafroleTicketDistribut ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.warning(f"Unexpected data in CE132 initiator: {len(data)} bytes") raise ValueError("Unexpected data in CE132 initiator") - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug(f"CE132 acceptor stream {stream.stream_id} received ticket") self._handle_received_ticket( stream, MsgCE132SafroleTicketDistribution.from_jam_bytes(JamBytes(data)), ) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.debug("Success with code 0") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.debug("Success with code 0") - def initiator_reset(self, stream: ManagedStream, reset_code: int) -> None: + def initiator_reset(self, stream: JAMStream, reset_code: int) -> None: logger.error(f"Failed with code {reset_code}") - def acceptor_reset(self, stream: ManagedStream, reset_code: int) -> None: + def acceptor_reset(self, stream: JAMStream, reset_code: int) -> None: logger.error(f"Failed with code {reset_code}") - def on_close(self, stream: ManagedStream) -> None: + def on_close(self, stream: JAMStream) -> None: self._streams.pop(stream.stream_key, None) - def _handle_received_ticket(self, stream: ManagedStream, msg: MsgCE132SafroleTicketDistribution) -> None: + def _handle_received_ticket(self, stream: JAMStream, msg: MsgCE132SafroleTicketDistribution) -> None: logger.info(f"Received ticket for epoch {msg.epoch_index}") current_epoch = self.context.app.working_state.timeslot.number // EPOCH_TIMESLOTS diff --git a/pyjamaz/transport/jamnp_s/protocol/ce133.py b/pyjamaz/transport/jamnp_s/protocol/ce133.py index 335c6ecd..83468a21 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce133.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce133.py @@ -13,7 +13,7 @@ MsgCE133Extrinsic, MsgCE133WorkPackageSubmission, ) -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -24,13 +24,13 @@ class CE133StreamState: class CE133Handler(StreamHandler): - kind = StreamKind.CE133_WorkPackageSubmission + kind = JAMStreamKind.CE133_WorkPackageSubmission def __init__(self, context) -> None: super().__init__(context) self._streams = {} - def init_stream(self, stream: ManagedStream) -> None: + def init_stream(self, stream: JAMStream) -> None: self._streams[stream.stream_key] = CE133StreamState() def initiate_workpackage_submission( @@ -38,7 +38,7 @@ def initiate_workpackage_submission( conn, wp: MsgCE133WorkPackageSubmission, extrinsic: MsgCE133Extrinsic, - ) -> ManagedStream: + ) -> JAMStream: stream = self.open_outgoing(conn) logger.info(f"Initiating workpackage submission on stream id: {stream.stream_id}") conn.send( @@ -53,11 +53,11 @@ def initiate_workpackage_submission( ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.warning(f"Unexpected data in CE133 initiator: {len(data)} bytes") raise ValueError("Unexpected data in CE133 initiator") - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: state = self._streams[stream.stream_key] if state.work_package is None: @@ -80,17 +80,17 @@ def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: stream.conn.send(stream.stream_id, b"", end_stream=True) self.context.app.add_work_package(state.work_package, extrinsics_list) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.debug("CE133 submission successful with FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.debug("CE133 submission successful with FIN") - def initiator_reset(self, stream: ManagedStream, reset_code: int) -> None: + def initiator_reset(self, stream: JAMStream, reset_code: int) -> None: logger.debug(f"CE133 received reset code: {reset_code}") - def acceptor_reset(self, stream: ManagedStream, reset_code: int) -> None: + def acceptor_reset(self, stream: JAMStream, reset_code: int) -> None: logger.debug(f"CE133 received reset code: {reset_code}") - def on_close(self, stream: ManagedStream) -> None: + def on_close(self, stream: JAMStream) -> None: self._streams.pop(stream.stream_key, None) diff --git a/pyjamaz/transport/jamnp_s/protocol/ce134.py b/pyjamaz/transport/jamnp_s/protocol/ce134.py index f9ab64b3..84c834fc 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce134.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce134.py @@ -13,7 +13,7 @@ MsgCE134WorkPackageBundle, MsgCE134WorkPackageSharing, ) -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -25,13 +25,13 @@ class CE134StreamState: class CE134Handler(StreamHandler): - kind = StreamKind.CE134_WorkPackageSharing + kind = JAMStreamKind.CE134_WorkPackageSharing def __init__(self, context) -> None: super().__init__(context) self._streams = {} - def init_stream(self, stream: ManagedStream) -> None: + def init_stream(self, stream: JAMStream) -> None: self._streams[stream.stream_key] = CE134StreamState() def initiate_workpackage_sharing( @@ -39,7 +39,7 @@ def initiate_workpackage_sharing( conn, sharing: MsgCE134WorkPackageSharing, bundle: MsgCE134WorkPackageBundle, - ) -> ManagedStream: + ) -> JAMStream: stream = self.open_outgoing(conn) logger.info(f"Initiate sharing workpage on stream id: {stream.stream_id} to {conn.host}:{conn.port}") conn.send( @@ -54,7 +54,7 @@ def initiate_workpackage_sharing( ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: state = self._streams[stream.stream_key] if state.received_mappings: logger.warning(f"Unexpected data in CE134 initiator after mappings: {len(data)} bytes") @@ -65,7 +65,7 @@ def initiator_message(self, stream: ManagedStream, data: bytes) -> None: state.received_mappings = True self._handle_refine_response(stream, msg) - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: state = self._streams[stream.stream_key] if not state.received_mappings: logger.debug("CE134 acceptor received mappings") @@ -79,16 +79,16 @@ def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: msg = MsgCE134WorkPackageBundle.from_jam_bytes(JamBytes(data)) self._handle_bundle(stream, msg) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.info("Success with FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.info("Success with FIN") - def on_close(self, stream: ManagedStream) -> None: + def on_close(self, stream: JAMStream) -> None: self._streams.pop(stream.stream_key, None) - def _handle_bundle(self, stream: ManagedStream, msg: MsgCE134WorkPackageBundle) -> None: + def _handle_bundle(self, stream: JAMStream, msg: MsgCE134WorkPackageBundle) -> None: logger.info("ce134_received_bundle") work_report = self.context.app.process_work_package(msg.work_package) @@ -101,7 +101,7 @@ def _handle_bundle(self, stream: ManagedStream, msg: MsgCE134WorkPackageBundle) end_stream=True, ) - def _handle_refine_response(self, stream: ManagedStream, msg: MsgCE134RefineResponse) -> None: + def _handle_refine_response(self, stream: JAMStream, msg: MsgCE134RefineResponse) -> None: logger.info("ce134_received_refine_response") work_report = None if work_report is not None: diff --git a/pyjamaz/transport/jamnp_s/protocol/ce135.py b/pyjamaz/transport/jamnp_s/protocol/ce135.py index a8b88b64..0f067ee2 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce135.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce135.py @@ -6,15 +6,15 @@ from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler from pyjamaz.transport.jamnp_s.protocol.messages.ce135 import MsgCE135GuaranteedWorkReport -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") class CE135Handler(StreamHandler): - kind = StreamKind.CE135_WorkReportDistribution + kind = JAMStreamKind.CE135_WorkReportDistribution - def initiate_distribution(self, conn, msg: MsgCE135GuaranteedWorkReport) -> ManagedStream: + def initiate_distribution(self, conn, msg: MsgCE135GuaranteedWorkReport) -> JAMStream: stream = self.open_outgoing(conn) logger.info(f"CE135 initiating distribution on stream id: {stream.stream_id}") conn.send( @@ -24,18 +24,18 @@ def initiate_distribution(self, conn, msg: MsgCE135GuaranteedWorkReport) -> Mana ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.warning(f"Unexpected data in CE135 initiator: {len(data)} bytes") raise ValueError("Unexpected data in CE135 initiator") - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE135 acceptor received work report") MsgCE135GuaranteedWorkReport.from_jam_bytes(JamBytes(data)) logger.info("CE135 received work report") stream.conn.send(stream.stream_id, b"", end_stream=True) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.info("CE135 distribution successful with FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.info("CE135 distribution successful with FIN") diff --git a/pyjamaz/transport/jamnp_s/protocol/ce136.py b/pyjamaz/transport/jamnp_s/protocol/ce136.py index 33a86673..596f05ee 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce136.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce136.py @@ -6,15 +6,15 @@ from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler from pyjamaz.transport.jamnp_s.protocol.messages.ce136 import MsgCE136HashRequest, MsgCE136WorkReport -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") class CE136Handler(StreamHandler): - kind = StreamKind.CE136_WorkReportRequest + kind = JAMStreamKind.CE136_WorkReportRequest - def initiate_request(self, conn, msg: MsgCE136HashRequest) -> ManagedStream: + def initiate_request(self, conn, msg: MsgCE136HashRequest) -> JAMStream: stream = self.open_outgoing(conn) logger.info(f"CE136 initiating request on stream id: {stream.stream_id}") conn.send( @@ -24,13 +24,13 @@ def initiate_request(self, conn, msg: MsgCE136HashRequest) -> ManagedStream: ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE136 initiator received work report") msg = MsgCE136WorkReport.from_jam_bytes(JamBytes(data)) logger.info(f"CE136 received work report of length {len(msg.report)}") stream.conn.send(stream.stream_id, b"", end_stream=True) - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE136 acceptor received request") msg = MsgCE136HashRequest.from_jam_bytes(JamBytes(data)) logger.info(f"CE136 received request for hash {msg.hash.hex()}") @@ -41,8 +41,8 @@ def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: end_stream=True, ) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.debug("CE136 request successful with FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.debug("CE136 request successful with FIN") diff --git a/pyjamaz/transport/jamnp_s/protocol/ce137.py b/pyjamaz/transport/jamnp_s/protocol/ce137.py index 8c76cdfd..27584aba 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce137.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce137.py @@ -11,15 +11,15 @@ MsgCE137SegmentShard, MsgCE137ShardRequest, ) -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") class CE137Handler(StreamHandler): - kind = StreamKind.CE137_ShardDistribution + kind = JAMStreamKind.CE137_ShardDistribution - def initiate_request(self, conn, req: MsgCE137ShardRequest) -> ManagedStream: + def initiate_request(self, conn, req: MsgCE137ShardRequest) -> JAMStream: stream = self.open_outgoing(conn) conn.send( stream.stream_id, @@ -33,7 +33,7 @@ def _chunks(data: bytes, chunk_size: int): for idx in range(0, len(data), chunk_size): yield data[idx : idx + chunk_size] - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE137 initiator received response") bundle = MsgCE137BundleShard.from_jam_bytes(JamBytes(data[: len(data) // 3])) segments = [ @@ -43,7 +43,7 @@ def initiator_message(self, stream: ManagedStream, data: bytes) -> None: just = MsgCE137Justification.from_jam_bytes(JamBytes(data[-len(data) // 3 :])) stream.conn.send(stream.stream_id, b"", end_stream=True) - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE137 acceptor received request") MsgCE137ShardRequest.from_jam_bytes(JamBytes(data)) bundle = MsgCE137BundleShard(bytes_=b"") @@ -66,8 +66,8 @@ def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: end_stream=True, ) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.info("CE137 success with FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.info("CE137 success with FIN") diff --git a/pyjamaz/transport/jamnp_s/protocol/ce138.py b/pyjamaz/transport/jamnp_s/protocol/ce138.py index 6243ac41..b0fc13e5 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce138.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce138.py @@ -10,15 +10,15 @@ MsgCE138Justification, MsgCE138ShardRequest, ) -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") class CE138Handler(StreamHandler): - kind = StreamKind.CE138_AuditShardRequest + kind = JAMStreamKind.CE138_AuditShardRequest - def initiate_request(self, conn, req: MsgCE138ShardRequest) -> ManagedStream: + def initiate_request(self, conn, req: MsgCE138ShardRequest) -> JAMStream: stream = self.open_outgoing(conn) conn.send( stream.stream_id, @@ -27,13 +27,13 @@ def initiate_request(self, conn, req: MsgCE138ShardRequest) -> ManagedStream: ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE138 initiator received response") bundle = MsgCE138BundleShard.from_jam_bytes(JamBytes(data[: len(data) // 2])) just = MsgCE138Justification.from_jam_bytes(JamBytes(data[len(data) // 2 :])) stream.conn.send(stream.stream_id, b"", end_stream=True) - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE138 acceptor received request") MsgCE138ShardRequest.from_jam_bytes(JamBytes(data)) bundle = MsgCE138BundleShard(bytes_=b"") @@ -49,8 +49,8 @@ def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: end_stream=True, ) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.info("CE138 success FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.info("CE138 success FIN") diff --git a/pyjamaz/transport/jamnp_s/protocol/ce139.py b/pyjamaz/transport/jamnp_s/protocol/ce139.py index 16686386..93931d4f 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce139.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce139.py @@ -9,15 +9,15 @@ MsgCE139SegmentRequest, MsgCE139SegmentShard, ) -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") class CE139Handler(StreamHandler): - kind = StreamKind.CE139_SegmentShardRequest + kind = JAMStreamKind.CE139_SegmentShardRequest - def initiate_request(self, conn, req: MsgCE139SegmentRequest) -> ManagedStream: + def initiate_request(self, conn, req: MsgCE139SegmentRequest) -> JAMStream: stream = self.open_outgoing(conn) conn.send( stream.stream_id, @@ -26,7 +26,7 @@ def initiate_request(self, conn, req: MsgCE139SegmentRequest) -> ManagedStream: ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE139 initiator received shards") chunk_size = max(1, len(data)) [ @@ -35,7 +35,7 @@ def initiator_message(self, stream: ManagedStream, data: bytes) -> None: ] stream.conn.send(stream.stream_id, b"", end_stream=True) - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE139 acceptor received request") msg = MsgCE139SegmentRequest.from_jam_bytes(JamBytes(data)) shards = [MsgCE139SegmentShard(bytes_=b"") for _ in msg.segment_indices] @@ -46,10 +46,10 @@ def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: end_stream=(shard == shards[-1]), ) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.info("CE139 success with FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.info("CE139 success with FIN") @staticmethod diff --git a/pyjamaz/transport/jamnp_s/protocol/ce140.py b/pyjamaz/transport/jamnp_s/protocol/ce140.py index 930a5595..4f2b9cc6 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce140.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce140.py @@ -10,15 +10,15 @@ MsgCE140SegmentRequest, MsgCE140SegmentShard, ) -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") class CE140Handler(StreamHandler): - kind = StreamKind.CE140_SegmentShardRequestJustification + kind = JAMStreamKind.CE140_SegmentShardRequestJustification - def initiate_request(self, conn, req: MsgCE140SegmentRequest) -> ManagedStream: + def initiate_request(self, conn, req: MsgCE140SegmentRequest) -> JAMStream: stream = self.open_outgoing(conn) conn.send( stream.stream_id, @@ -27,11 +27,11 @@ def initiate_request(self, conn, req: MsgCE140SegmentRequest) -> ManagedStream: ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE140 initiator received shards and justifications") stream.conn.send(stream.stream_id, b"", end_stream=True) - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE140 acceptor received request") msg = MsgCE140SegmentRequest.from_jam_bytes(JamBytes(data)) for idx in msg.segment_indices: @@ -48,8 +48,8 @@ def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: end_stream=(idx == msg.segment_indices[-1]), ) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.info("CE140 success with FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.info("CE140 success with FIN") diff --git a/pyjamaz/transport/jamnp_s/protocol/ce141.py b/pyjamaz/transport/jamnp_s/protocol/ce141.py index 356f34f3..0f168ff7 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce141.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce141.py @@ -6,15 +6,15 @@ from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler from pyjamaz.transport.jamnp_s.protocol.messages.ce141 import MsgCE141Assurance -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") class CE141Handler(StreamHandler): - kind = StreamKind.CE141_AssuranceDistribution + kind = JAMStreamKind.CE141_AssuranceDistribution - def initiate_distribution(self, conn, msg: MsgCE141Assurance) -> ManagedStream: + def initiate_distribution(self, conn, msg: MsgCE141Assurance) -> JAMStream: stream = self.open_outgoing(conn) logger.info(f"Initiating assurance distribution on stream id: {stream.stream_id} to {conn.host}:{conn.port}") conn.send( @@ -24,18 +24,18 @@ def initiate_distribution(self, conn, msg: MsgCE141Assurance) -> ManagedStream: ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.warning(f"Unexpected data in CE141 initiator: {len(data)} bytes") raise ValueError("Unexpected data in CE141 initiator") - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE141 acceptor received assurance") MsgCE141Assurance.from_jam_bytes(JamBytes(data)) logger.info("Received assurance") stream.conn.send(stream.stream_id, b"", end_stream=True) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.info("Success with code with FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.info("Success with code with FIN") diff --git a/pyjamaz/transport/jamnp_s/protocol/ce142.py b/pyjamaz/transport/jamnp_s/protocol/ce142.py index 9288b2a0..884eb39b 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce142.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce142.py @@ -6,15 +6,15 @@ from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler from pyjamaz.transport.jamnp_s.protocol.messages.ce142 import MsgCE142PreimageAnnouncement -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") class CE142Handler(StreamHandler): - kind = StreamKind.CE142_PreimageAnnouncement + kind = JAMStreamKind.CE142_PreimageAnnouncement - def initiate_announcement(self, conn, msg: MsgCE142PreimageAnnouncement) -> ManagedStream: + def initiate_announcement(self, conn, msg: MsgCE142PreimageAnnouncement) -> JAMStream: stream = self.open_outgoing(conn) logger.info(f"Initiating preimage announcement on stream id: {stream.stream_id} to {conn.host}:{conn.port}") conn.send( @@ -24,20 +24,20 @@ def initiate_announcement(self, conn, msg: MsgCE142PreimageAnnouncement) -> Mana ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: if len(data) == 0: return logger.warning(f"Unexpected data in CE142 initiator: {len(data)} bytes") raise ValueError("Unexpected data in CE142 initiator") - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE142 acceptor received preimage announcement") msg = MsgCE142PreimageAnnouncement.from_jam_bytes(JamBytes(data)) logger.info(f"Received preimage announcement for hash {msg.hash.hex()}") stream.conn.send(stream.stream_id, b"", end_stream=True) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.info("Success with code with FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.info("Success with code with FIN") diff --git a/pyjamaz/transport/jamnp_s/protocol/ce143.py b/pyjamaz/transport/jamnp_s/protocol/ce143.py index ae026bd9..092a6d94 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce143.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce143.py @@ -6,15 +6,15 @@ from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler from pyjamaz.transport.jamnp_s.protocol.messages.ce143 import MsgCE143HashRequest, MsgCE143Preimage -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") class CE143Handler(StreamHandler): - kind = StreamKind.CE143_PreimageRequest + kind = JAMStreamKind.CE143_PreimageRequest - def initiate_request(self, conn, msg: MsgCE143HashRequest) -> ManagedStream: + def initiate_request(self, conn, msg: MsgCE143HashRequest) -> JAMStream: stream = self.open_outgoing(conn) logger.info(f"CE143 initiating request on stream id: {stream.stream_id}") conn.send( @@ -24,13 +24,13 @@ def initiate_request(self, conn, msg: MsgCE143HashRequest) -> ManagedStream: ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE143 initiator received preimage") msg = MsgCE143Preimage.from_jam_bytes(JamBytes(data)) logger.info(f"CE143 received preimage of length {len(msg.bytes_)}") stream.conn.send(stream.stream_id, b"", end_stream=True) - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE143 acceptor received preimage request") msg = MsgCE143HashRequest.from_jam_bytes(JamBytes(data)) logger.info(f"CE143 received request for hash {msg.hash.hex()}") @@ -41,16 +41,16 @@ def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: end_stream=True, ) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: self._request_success(0) - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: self._request_success(0) - def initiator_reset(self, stream: ManagedStream, reset_code: int) -> None: + def initiator_reset(self, stream: JAMStream, reset_code: int) -> None: self._request_failure(reset_code) - def acceptor_reset(self, stream: ManagedStream, reset_code: int) -> None: + def acceptor_reset(self, stream: JAMStream, reset_code: int) -> None: self._request_failure(reset_code) @staticmethod diff --git a/pyjamaz/transport/jamnp_s/protocol/ce144.py b/pyjamaz/transport/jamnp_s/protocol/ce144.py index febe12e4..0433c15c 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce144.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce144.py @@ -10,7 +10,7 @@ MsgCE144Announcement, MsgCE144Evidence, ) -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -21,13 +21,13 @@ class CE144StreamState: class CE144Handler(StreamHandler): - kind = StreamKind.CE144_AuditAnnouncement + kind = JAMStreamKind.CE144_AuditAnnouncement def __init__(self, context) -> None: super().__init__(context) self._streams = {} - def init_stream(self, stream: ManagedStream) -> None: + def init_stream(self, stream: JAMStream) -> None: self._streams[stream.stream_key] = CE144StreamState() def initiate_announcement( @@ -35,7 +35,7 @@ def initiate_announcement( conn, ann: MsgCE144Announcement, evidence: MsgCE144Evidence, - ) -> ManagedStream: + ) -> JAMStream: stream = self.open_outgoing(conn) conn.send( stream.stream_id, @@ -49,11 +49,11 @@ def initiate_announcement( ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.warning(f"Unexpected data in CE144 initiator: {len(data)} bytes") raise ValueError("Unexpected data in CE144 initiator") - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: state = self._streams[stream.stream_key] if not state.received_announcement: @@ -66,11 +66,11 @@ def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: MsgCE144Evidence.from_jam_bytes(JamBytes(data)) stream.conn.send(stream.stream_id, b"", end_stream=True) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.info("CE144 success with FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.info("CE144 success with FIN") - def on_close(self, stream: ManagedStream) -> None: + def on_close(self, stream: JAMStream) -> None: self._streams.pop(stream.stream_key, None) diff --git a/pyjamaz/transport/jamnp_s/protocol/ce145.py b/pyjamaz/transport/jamnp_s/protocol/ce145.py index ce7abee7..e4d85c9e 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce145.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce145.py @@ -6,15 +6,15 @@ from pyjamaz.transport.jamnp_s.protocol.base import StreamHandler from pyjamaz.transport.jamnp_s.protocol.messages.ce145 import MsgCE145JudgmentPublication -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") class CE145Handler(StreamHandler): - kind = StreamKind.CE145_JudgmentPublication + kind = JAMStreamKind.CE145_JudgmentPublication - def initiate_publication(self, conn, msg: MsgCE145JudgmentPublication) -> ManagedStream: + def initiate_publication(self, conn, msg: MsgCE145JudgmentPublication) -> JAMStream: stream = self.open_outgoing(conn) conn.send( stream.stream_id, @@ -23,17 +23,17 @@ def initiate_publication(self, conn, msg: MsgCE145JudgmentPublication) -> Manage ) return stream - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: logger.warning(f"Unexpected data in CE145 initiator: {len(data)} bytes") raise ValueError("Unexpected data in CE145 initiator") - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug("CE145 acceptor received judgment") MsgCE145JudgmentPublication.from_jam_bytes(JamBytes(data)) stream.conn.send(stream.stream_id, b"", end_stream=True) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.info("CE145 success with FIN") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.info("CE145 success with FIN") diff --git a/pyjamaz/transport/jamnp_s/protocol/context.py b/pyjamaz/transport/jamnp_s/protocol/context.py index e780fcda..6b908b1a 100644 --- a/pyjamaz/transport/jamnp_s/protocol/context.py +++ b/pyjamaz/transport/jamnp_s/protocol/context.py @@ -3,7 +3,7 @@ from dataclasses import dataclass, field from typing import TYPE_CHECKING, Dict -from pyjamaz.transport.jamnp_s.types import StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStreamKind if TYPE_CHECKING: from pyjamaz.app import PyjamazApp @@ -25,7 +25,7 @@ class ProtocolContext: validator_manager: "ValidatorConnectionManager" stream_manager: "StreamManager" state: ProtocolSharedState - handlers: Dict[StreamKind, object] = field(default_factory=dict) + handlers: Dict[JAMStreamKind, object] = field(default_factory=dict) @property def connections(self): @@ -50,5 +50,5 @@ def state_warp_sync(self, value: bool) -> None: def register_handler(self, handler) -> None: self.handlers[handler.kind] = handler - def get_handler(self, kind: StreamKind): + def get_handler(self, kind: JAMStreamKind): return self.handlers[kind] diff --git a/pyjamaz/transport/jamnp_s/protocol/up0.py b/pyjamaz/transport/jamnp_s/protocol/up0.py index 92296cc4..09fdb09a 100644 --- a/pyjamaz/transport/jamnp_s/protocol/up0.py +++ b/pyjamaz/transport/jamnp_s/protocol/up0.py @@ -16,7 +16,7 @@ MsgUP0Announcement, MsgUP0Handshake, ) -from pyjamaz.transport.jamnp_s.types import ManagedStream, StreamDirection, StreamKind +from pyjamaz.transport.jamnp_s.types import JAMStream, StreamDirection, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -32,13 +32,13 @@ class UP0StreamState: class UP0Handler(StreamHandler): - kind = StreamKind.UP0_BlockAnnouncement + kind = JAMStreamKind.UP0_BlockAnnouncement def __init__(self, context) -> None: super().__init__(context) self._streams = {} - def init_stream(self, stream: ManagedStream) -> None: + def init_stream(self, stream: JAMStream) -> None: self._streams[stream.stream_key] = UP0StreamState() def send_handshake(self, conn) -> None: @@ -84,22 +84,22 @@ async def broadcast_block(self, block) -> None: end_stream=False, ) - def initiator_message(self, stream: ManagedStream, data: bytes) -> None: + def initiator_message(self, stream: JAMStream, data: bytes) -> None: self._receive_message(stream, data) - def acceptor_message(self, stream: ManagedStream, data: bytes) -> None: + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: self._receive_message(stream, data) - def initiator_fin(self, stream: ManagedStream) -> None: + def initiator_fin(self, stream: JAMStream) -> None: logger.warning(f"Unexpected FIN on persistent UP0 stream {stream.stream_id}") - def acceptor_fin(self, stream: ManagedStream) -> None: + def acceptor_fin(self, stream: JAMStream) -> None: logger.warning(f"Unexpected FIN on persistent UP0 stream {stream.stream_id}") - def on_close(self, stream: ManagedStream) -> None: + def on_close(self, stream: JAMStream) -> None: self._streams.pop(stream.stream_key, None) - def _receive_message(self, stream: ManagedStream, data: bytes) -> None: + def _receive_message(self, stream: JAMStream, data: bytes) -> None: state = self._streams[stream.stream_key] if state.phase == UPState.IN_PROGRESS: @@ -115,7 +115,7 @@ def _receive_message(self, stream: ManagedStream, data: bytes) -> None: raise RuntimeError(f"Unexpected state {state.phase}") - def _handle_handshake(self, stream: ManagedStream, msg: MsgUP0Handshake) -> None: + def _handle_handshake(self, stream: JAMStream, msg: MsgUP0Handshake) -> None: if stream.direction == StreamDirection.acceptor: self.send_handshake(stream.conn) @@ -145,7 +145,7 @@ def _handle_announcement(self, conn, msg: MsgUP0Announcement) -> None: def _initiate_block_sync(self, conn, announced_hash: bytes) -> None: self.context.state_requesting_blocks = True curr_hash = self.context.app.retrieve_block_hash(self.context.app.working_state.timeslot.number) - ce128_handler = self.context.get_handler(StreamKind.CE128_BlockRequest) + ce128_handler = self.context.get_handler(JAMStreamKind.CE128_BlockRequest) ce128_handler.initiate_block_request( conn, MsgCE128BlockRequest( diff --git a/pyjamaz/transport/jamnp_s/stream_manager.py b/pyjamaz/transport/jamnp_s/stream_manager.py index 1c512014..2a8da80b 100644 --- a/pyjamaz/transport/jamnp_s/stream_manager.py +++ b/pyjamaz/transport/jamnp_s/stream_manager.py @@ -3,7 +3,7 @@ import logging from typing import Dict, Tuple, TYPE_CHECKING -from pyjamaz.transport.jamnp_s.types import IStreamHandler, ManagedStream, StreamDirection, StreamKind +from pyjamaz.transport.jamnp_s.types import IStreamHandler, JAMStream, StreamDirection, JAMStreamKind logger = logging.getLogger("pyjamaz.transport.jamnp_s") @@ -12,26 +12,30 @@ class StreamManager: + def __init__(self, max_message_size: int) -> None: self.max_message_size = max_message_size - self.handlers: Dict[StreamKind, IStreamHandler] = {} - self._streams: Dict[Tuple[int, int], ManagedStream] = {} - self._pending_kind_buffers: Dict[Tuple[int, int], bytearray] = {} + self.handlers: Dict[JAMStreamKind, IStreamHandler] = {} + self._streams: Dict[Tuple[int, int], JAMStream] = {} + self._pending_streams: Dict[Tuple[int, int], bytearray] = {} + def register_handler(self, handler: IStreamHandler) -> None: self.handlers[handler.kind] = handler - def open_outgoing(self, connection: "JAMConnection", kind: StreamKind) -> ManagedStream: + + def open_outgoing(self, connection: "JAMConnection", kind: JAMStreamKind) -> JAMStream: stream_id = connection._quic.get_next_available_stream_id() - stream = ManagedStream( + stream = JAMStream( stream_id=stream_id, stream_kind=kind, conn=connection, direction=StreamDirection.initiator, ) - self._register_stream(stream) + self.register_stream(stream) return stream + def receive_stream_data( self, connection: "JAMConnection", @@ -39,23 +43,25 @@ def receive_stream_data( data: bytes, end_stream: bool = False, ) -> None: - stream = self._streams.get(self._key(connection, stream_id)) + + stream = self._streams.get(self.get_stream_key(connection, stream_id)) if stream is None: - stream = self._resolve_incoming_stream(connection, stream_id, data, end_stream) + stream = self.determine_stream_kind(connection, stream_id, data, end_stream) if stream is None: return - pending = self._pending_kind_buffers.pop(self._key(connection, stream_id), bytearray()) + pending = self._pending_streams.pop(self.get_stream_key(connection, stream_id), bytearray()) data = bytes(pending[1:]) - self._process_stream_bytes(stream, data, end_stream) + self._parse_bytes(stream, data, end_stream) + def receive_reset(self, connection: "JAMConnection", stream_id: int, reset_code: int) -> None: - key = self._key(connection, stream_id) + key = self.get_stream_key(connection, stream_id) stream = self._streams.get(key) if stream is None: - self._pending_kind_buffers.pop(key, None) + self._pending_streams.pop(key, None) logger.debug(f"Connection {connection.jam_connection_ulid} QUIC stream {stream_id} already closed") return @@ -69,14 +75,17 @@ def receive_reset(self, connection: "JAMConnection", stream_id: int, reset_code: finally: self._cleanup_stream(stream) - def send_reset(self, stream: ManagedStream, reset_code: int) -> None: + + def send_reset(self, stream: JAMStream, reset_code: int) -> None: stream.conn.reset_stream(stream.stream_id, reset_code) self._cleanup_stream(stream) - def close_stream(self, stream: ManagedStream, reason: int = 0, clean_close: bool = True) -> None: + + def close_stream(self, stream: JAMStream, reason: int = 0, clean_close: bool = True) -> None: stream.conn.close_stream(stream.stream_id, clean_close=clean_close, reason=reason) self._cleanup_stream(stream) + def cleanup_connection(self, connection: "JAMConnection") -> None: conn_key = id(connection) @@ -85,52 +94,54 @@ def cleanup_connection(self, connection: "JAMConnection") -> None: continue self._cleanup_stream(stream) - for key in [key for key in self._pending_kind_buffers if key[0] == conn_key]: - self._pending_kind_buffers.pop(key, None) + for key in [key for key in self._pending_streams if key[0] == conn_key]: + self._pending_streams.pop(key, None) connection.streams.clear() connection.stream_up = None - def _resolve_incoming_stream( + + def determine_stream_kind( self, connection: "JAMConnection", stream_id: int, data: bytes, end_stream: bool, - ) -> ManagedStream | None: - key = self._key(connection, stream_id) - pending = self._pending_kind_buffers.setdefault(key, bytearray()) + ) -> JAMStream | None: + key = self.get_stream_key(connection, stream_id) + pending = self._pending_streams.setdefault(key, bytearray()) if data: pending.extend(data) if len(pending) < 1: if end_stream: self._reset_unknown_stream( - connection, stream_id, ManagedStream.ERROR_INVALID_MESSAGE, "Stream ended before stream kind was received" + connection, stream_id, JAMStream.ERROR_INVALID_MESSAGE, "Stream ended before stream kind was received" ) return None try: - kind = StreamKind(pending[0]) + kind = JAMStreamKind(pending[0]) except ValueError: self._reset_unknown_stream( connection, stream_id, - ManagedStream.ERROR_INVALID_MESSAGE, + JAMStream.ERROR_INVALID_MESSAGE, f"QUIC stream {stream_id} is not mapped (type {pending[0]})", ) return None - stream = ManagedStream( + stream = JAMStream( stream_id=stream_id, stream_kind=kind, conn=connection, direction=StreamDirection.acceptor, ) - self._register_stream(stream) + self.register_stream(stream) return stream - def _process_stream_bytes(self, stream: ManagedStream, data: bytes, end_stream: bool) -> None: + + def _parse_bytes(self, stream: JAMStream, data: bytes, end_stream: bool) -> None: if data: stream.recv_buffer.extend(data) @@ -181,20 +192,22 @@ def _process_stream_bytes(self, stream: ManagedStream, data: bytes, end_stream: self._cleanup_stream(stream) - def _register_stream(self, stream: ManagedStream) -> None: + + def register_stream(self, stream: JAMStream) -> None: handler = self.handlers.get(stream.stream_kind) if handler is None: raise ValueError(f"Unsupported stream kind: {stream.stream_kind}") stream.handler = handler - self._streams[self._key(stream.conn, stream.stream_id)] = stream + self._streams[self.get_stream_key(stream.conn, stream.stream_id)] = stream stream.conn.streams[stream.stream_id] = stream - if stream.stream_kind == StreamKind.UP0_BlockAnnouncement: + if stream.stream_kind == JAMStreamKind.UP0_BlockAnnouncement: stream.conn.stream_up = stream handler.init_stream(stream) - def _cleanup_stream(self, stream: ManagedStream) -> None: + + def _cleanup_stream(self, stream: JAMStream) -> None: if stream.closed: return @@ -205,12 +218,13 @@ def _cleanup_stream(self, stream: ManagedStream) -> None: if callable(on_close): on_close(stream) - self._streams.pop(self._key(stream.conn, stream.stream_id), None) + self._streams.pop(self.get_stream_key(stream.conn, stream.stream_id), None) stream.conn.streams.pop(stream.stream_id, None) if stream.conn.stream_up is stream: stream.conn.stream_up = None + def _reset_unknown_stream( self, connection: "JAMConnection", @@ -220,8 +234,9 @@ def _reset_unknown_stream( ) -> None: logger.error(reason) connection.reset_stream(stream_id, error_code) - self._pending_kind_buffers.pop(self._key(connection, stream_id), None) + self._pending_streams.pop(self.get_stream_key(connection, stream_id), None) + @staticmethod - def _key(connection: "JAMConnection", stream_id: int) -> Tuple[int, int]: + def get_stream_key(connection: "JAMConnection", stream_id: int) -> Tuple[int, int]: return id(connection), stream_id diff --git a/pyjamaz/transport/jamnp_s/types.py b/pyjamaz/transport/jamnp_s/types.py index a092dc66..338faf15 100644 --- a/pyjamaz/transport/jamnp_s/types.py +++ b/pyjamaz/transport/jamnp_s/types.py @@ -11,7 +11,7 @@ from pyjamaz.transport.jamnp_s.connection import JAMConnection -class StreamKind(IntEnum): +class JAMStreamKind(IntEnum): UP0_BlockAnnouncement = 0 CE128_BlockRequest = 128 CE129_StateRequest = 129 @@ -38,28 +38,28 @@ class StreamDirection(Enum): class IStreamHandler(Protocol): - kind: StreamKind + kind: JAMStreamKind - def init_stream(self, stream: "ManagedStream") -> None: + def init_stream(self, stream: "JAMStream") -> None: ... - def on_message(self, stream: "ManagedStream", data: bytes) -> None: + def on_message(self, stream: "JAMStream", data: bytes) -> None: ... - def on_fin(self, stream: "ManagedStream") -> None: + def on_fin(self, stream: "JAMStream") -> None: ... - def on_reset(self, stream: "ManagedStream", reset_code: int) -> None: + def on_reset(self, stream: "JAMStream", reset_code: int) -> None: ... - def on_close(self, stream: "ManagedStream") -> None: + def on_close(self, stream: "JAMStream") -> None: ... @dataclass -class ManagedStream: +class JAMStream: stream_id: int - stream_kind: StreamKind + stream_kind: JAMStreamKind conn: "JAMConnection" direction: StreamDirection handler: Optional[IStreamHandler] = field(default=None, repr=False) diff --git a/pyjamaz/transport/jamnp_s/validator_manager.py b/pyjamaz/transport/jamnp_s/validator_manager.py index 537e610c..52dd1a23 100644 --- a/pyjamaz/transport/jamnp_s/validator_manager.py +++ b/pyjamaz/transport/jamnp_s/validator_manager.py @@ -8,15 +8,7 @@ from enum import IntEnum from typing import Awaitable, Callable, Dict, Iterable, Optional, TYPE_CHECKING -try: - from aioquic.quic.connection import QuicConnectionState -except ModuleNotFoundError: - class QuicConnectionState(IntEnum): - FIRSTFLIGHT = 0 - CONNECTED = 1 - CLOSING = 2 - DRAINING = 3 - TERMINATED = 4 +from aioquic.quic.connection import QuicConnectionState from pyjamaz import settings @@ -27,6 +19,7 @@ class QuicConnectionState(IntEnum): from pyjamaz.models.common import ValidatorData from pyjamaz.transport.jamnp_s.connection import JAMConnection + ConnectCallback = Callable[[str, int, Optional[bytes]], Awaitable[None]] DisconnectCallback = Callable[["JAMConnection", Optional[bytes]], None] @@ -52,12 +45,12 @@ def __init__( app: "PyjamazApp", connect_callback: ConnectCallback, disconnect_callback: DisconnectCallback, - local_port_override: Optional[int] = None, + port_override: Optional[int] = None, ) -> None: self.app = app self.connect_callback = connect_callback self.disconnect_callback = disconnect_callback - self.local_port_override = local_port_override + self.port_override = port_override self.connections: Dict[bytes, ValidatorConnection] = {} self.validator = None @@ -66,6 +59,7 @@ def __init__( self.validator_address = None self.refresh_local_validator() + def refresh_local_validator(self) -> None: self.validator = None self.validator_dns = None @@ -78,16 +72,18 @@ def refresh_local_validator(self) -> None: for validator in self._validator_sets(): if validator.ed25519 == self.app.config.keys.ed25519.public_key: self.validator = validator - self.validator_address, self.validator_port = self.resolve_validator_endpoint(validator) + self.validator_address, self.validator_port = self.get_validator_endpoint(validator) peer_id = b32encode(validator.ed25519).decode("ascii").lower().rstrip("=") self.validator_dns = f"e{peer_id}@{self.validator_address}:{self.validator_port}" return + def _validator_sets(self) -> Iterable[ValidatorData]: - yield from self.app.working_state.safrole.validators yield from self.app.working_state.validator_queue.validators + yield from self.app.working_state.validator_pool.validators yield from self.app.working_state.validator_archive.validators + @staticmethod def should_initiate_connection(validator_a: bytes, validator_b: bytes) -> bool: connect_a = 1 if validator_a[31] > 127 else 0 @@ -95,12 +91,13 @@ def should_initiate_connection(validator_a: bytes, validator_b: bytes) -> bool: a_less = 1 if validator_a < validator_b else 0 return (connect_a ^ connect_b ^ a_less) == 1 - @staticmethod - def _override_key(validator: "ValidatorData") -> str: - return f"0x{validator.ed25519.hex()}" - @staticmethod - def _parse_endpoint_override(value) -> Optional[tuple[str, int]]: + def _validator_override(self, validator: "ValidatorData") -> Optional[tuple[str, int]]: + overrides = getattr(settings, "VALIDATOR_ENDPOINT_OVERRIDES", {}) or {} + value = overrides.get(f"0x{validator.ed25519.hex()}") + if value is None: + value = overrides.get(validator.ed25519.hex()) + if isinstance(value, str): host, sep, port_str = value.rpartition(":") if not sep: @@ -116,26 +113,19 @@ def _parse_endpoint_override(value) -> Optional[tuple[str, int]]: raise ValueError(f"Unsupported validator endpoint override: {value!r}") - def _endpoint_override_for(self, validator: "ValidatorData") -> Optional[tuple[str, int]]: - overrides = getattr(settings, "VALIDATOR_ENDPOINT_OVERRIDES", {}) or {} - value = overrides.get(self._override_key(validator)) - if value is None: - value = overrides.get(validator.ed25519.hex()) - return self._parse_endpoint_override(value) - - def resolve_validator_endpoint(self, validator: ValidatorData) -> tuple[str, int]: - override = self._endpoint_override_for(validator) + def get_validator_endpoint(self, validator: ValidatorData) -> tuple[str, int]: + override = self._validator_override(validator) if override is not None: return override ip = validator.get_metadata_ipaddress() port = validator.get_metadata_port() - if self.local_port_override is not None and self.validator is not None: - port_delta = self.local_port_override - self.validator.get_metadata_port() + if self.port_override is not None and self.validator is not None: + port_delta = self.port_override - self.validator.get_metadata_port() port += port_delta return ip, port - def _build_neighbors( + def _grid_neighbors( self, validator_set: list[ValidatorData], local_key: bytes, @@ -165,28 +155,28 @@ def _build_neighbors( neighbors.pop(local_key, None) return neighbors - def _collect_grid_neighbors(self) -> Dict[bytes, ValidatorData]: + def get_grid_neighbors(self) -> Dict[bytes, ValidatorData]: if self.validator is None: return {} - previous = self.app.working_state.validator_archive.validators - current = self.app.working_state.safrole.validators - next_ = self.app.working_state.validator_queue.validators + prev_validators = self.app.working_state.validator_archive.validators + current_validators = self.app.working_state.validator_pool.validators + next_validators = self.app.working_state.validator_queue.validators neighbors: Dict[bytes, ValidatorData] = {} local_key = self.validator.ed25519 - for validator in self._build_neighbors(previous, local_key, [current, next_]).values(): + for validator in self._grid_neighbors(prev_validators, local_key, [current_validators, next_validators]).values(): neighbors[validator.ed25519] = validator - for validator in self._build_neighbors(current, local_key, []).values(): + for validator in self._grid_neighbors(current_validators, local_key, []).values(): neighbors[validator.ed25519] = validator - for validator in self._build_neighbors(next_, local_key, []).values(): + for validator in self._grid_neighbors(next_validators, local_key, []).values(): neighbors[validator.ed25519] = validator return neighbors @staticmethod - def _connection_is_active(connection: Optional["JAMConnection"]) -> bool: + def is_connection_active(connection: Optional["JAMConnection"]) -> bool: if connection is None: return False @@ -211,14 +201,14 @@ def _connection_is_active(connection: Optional["JAMConnection"]) -> bool: def has_tracked_validator(self, validator_key: bytes) -> bool: return validator_key in self.connections - async def update_connections(self) -> None: + async def update_validator_connections(self) -> None: self.refresh_local_validator() if not self.validator: return now = time.time() - new_neighbors = self._collect_grid_neighbors() + new_neighbors = self.get_grid_neighbors() new_keys = set(new_neighbors) current_keys = set(self.connections) @@ -231,7 +221,7 @@ async def update_connections(self) -> None: validator = new_neighbors[ed25519] is_initiator = self.should_initiate_connection(self.validator.ed25519, validator.ed25519) in_grid = True - ip, port = self.resolve_validator_endpoint(validator) + ip, port = self.get_validator_endpoint(validator) state = self.connections.get(ed25519) if state is None: @@ -253,7 +243,7 @@ async def update_connections(self) -> None: if state.desired_since is None: state.desired_since = now - if self._connection_is_active(state.connection): + if self.is_connection_active(state.connection): continue state.connection = None From d7a9753e33c324fe04a92ee7d22411359aaefe3b Mon Sep 17 00:00:00 2001 From: matthijsb Date: Tue, 14 Apr 2026 14:31:54 +0200 Subject: [PATCH 48/51] - NP refactor --- .../transport/jamnp_s/validator_manager.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/pyjamaz/transport/jamnp_s/validator_manager.py b/pyjamaz/transport/jamnp_s/validator_manager.py index 52dd1a23..1709b9f7 100644 --- a/pyjamaz/transport/jamnp_s/validator_manager.py +++ b/pyjamaz/transport/jamnp_s/validator_manager.py @@ -92,7 +92,7 @@ def should_initiate_connection(validator_a: bytes, validator_b: bytes) -> bool: return (connect_a ^ connect_b ^ a_less) == 1 - def _validator_override(self, validator: "ValidatorData") -> Optional[tuple[str, int]]: + def _debug_validator_override(self, validator: "ValidatorData") -> Optional[tuple[str, int]]: overrides = getattr(settings, "VALIDATOR_ENDPOINT_OVERRIDES", {}) or {} value = overrides.get(f"0x{validator.ed25519.hex()}") if value is None: @@ -113,8 +113,9 @@ def _validator_override(self, validator: "ValidatorData") -> Optional[tuple[str, raise ValueError(f"Unsupported validator endpoint override: {value!r}") + def get_validator_endpoint(self, validator: ValidatorData) -> tuple[str, int]: - override = self._validator_override(validator) + override = self._debug_validator_override(validator) if override is not None: return override @@ -125,7 +126,8 @@ def get_validator_endpoint(self, validator: ValidatorData) -> tuple[str, int]: port += port_delta return ip, port - def _grid_neighbors( + + def get_neighbors( self, validator_set: list[ValidatorData], local_key: bytes, @@ -155,7 +157,8 @@ def _grid_neighbors( neighbors.pop(local_key, None) return neighbors - def get_grid_neighbors(self) -> Dict[bytes, ValidatorData]: + + def build_grid_neighbors(self) -> Dict[bytes, ValidatorData]: if self.validator is None: return {} @@ -166,15 +169,16 @@ def get_grid_neighbors(self) -> Dict[bytes, ValidatorData]: neighbors: Dict[bytes, ValidatorData] = {} local_key = self.validator.ed25519 - for validator in self._grid_neighbors(prev_validators, local_key, [current_validators, next_validators]).values(): + for validator in self.get_neighbors(prev_validators, local_key, [current_validators, next_validators]).values(): neighbors[validator.ed25519] = validator - for validator in self._grid_neighbors(current_validators, local_key, []).values(): + for validator in self.get_neighbors(current_validators, local_key, []).values(): neighbors[validator.ed25519] = validator - for validator in self._grid_neighbors(next_validators, local_key, []).values(): + for validator in self.get_neighbors(next_validators, local_key, []).values(): neighbors[validator.ed25519] = validator return neighbors + @staticmethod def is_connection_active(connection: Optional["JAMConnection"]) -> bool: if connection is None: @@ -198,9 +202,11 @@ def is_connection_active(connection: Optional["JAMConnection"]) -> bool: } return state not in terminal_states + def has_tracked_validator(self, validator_key: bytes) -> bool: return validator_key in self.connections + async def update_validator_connections(self) -> None: self.refresh_local_validator() @@ -208,7 +214,7 @@ async def update_validator_connections(self) -> None: return now = time.time() - new_neighbors = self.get_grid_neighbors() + new_neighbors = self.build_grid_neighbors() new_keys = set(new_neighbors) current_keys = set(self.connections) @@ -265,6 +271,7 @@ async def update_validator_connections(self) -> None: state.last_try = now await self.connect_callback(state.ip, state.port, ed25519) + def bind_connection(self, validator_key: bytes, connection: "JAMConnection") -> None: connection.validator_key = validator_key state = self.connections.get(validator_key) @@ -277,6 +284,7 @@ def bind_connection(self, validator_key: bytes, connection: "JAMConnection") -> state.connection = connection state.last_try = time.time() + def on_disconnect(self, connection: "JAMConnection", validator_key: Optional[bytes] = None) -> None: validator_key = validator_key or getattr(connection, "validator_key", None) if validator_key is None: From b3d86aa189268a84552c6793cc951295f8ce926a Mon Sep 17 00:00:00 2001 From: matthijsb Date: Tue, 14 Apr 2026 15:48:14 +0200 Subject: [PATCH 49/51] - NP refactor --- pyjamaz/transport/jamnp_s/validator_manager.py | 7 +++++++ scripts/init_nodes.sh | 2 ++ 2 files changed, 9 insertions(+) diff --git a/pyjamaz/transport/jamnp_s/validator_manager.py b/pyjamaz/transport/jamnp_s/validator_manager.py index 1709b9f7..7abe53a8 100644 --- a/pyjamaz/transport/jamnp_s/validator_manager.py +++ b/pyjamaz/transport/jamnp_s/validator_manager.py @@ -225,6 +225,13 @@ async def update_validator_connections(self) -> None: for ed25519 in new_keys: validator = new_neighbors[ed25519] + overrides = getattr(settings, "VALIDATOR_ENDPOINT_OVERRIDES", {}) or {} + override_key = f"0x{validator.ed25519.hex()}" + if ( + (override_key in overrides and overrides[override_key] is None) + or (validator.ed25519.hex() in overrides and overrides[validator.ed25519.hex()] is None) + ): + continue is_initiator = self.should_initiate_connection(self.validator.ed25519, validator.ed25519) in_grid = True ip, port = self.get_validator_endpoint(validator) diff --git a/scripts/init_nodes.sh b/scripts/init_nodes.sh index 2a400fa8..249166c2 100644 --- a/scripts/init_nodes.sh +++ b/scripts/init_nodes.sh @@ -4,3 +4,5 @@ python pyjamaz/cli.py init --db-path ./pyjamaz/data/charlie --seed 0x02000000020 python pyjamaz/cli.py init --db-path ./pyjamaz/data/dave --seed 0x0300000003000000030000000300000003000000030000000300000003000000 --force-overwrite python pyjamaz/cli.py init --db-path ./pyjamaz/data/eve --seed 0x0400000004000000040000000400000004000000040000000400000004000000 --force-overwrite python pyjamaz/cli.py init --db-path ./pyjamaz/data/ferdie --seed 0x0500000005000000050000000500000005000000050000000500000005000000 --force-overwrite + +python pyjamaz/cli.py init --db-path ./pyjamaz/data/karel --seed 0x1111111111111111111111111111111111111111111111111111111111111111 --chainspec dev --force-overwrite \ No newline at end of file From 9f4509c6a07d89e0bb86ae3b7568381d6c713a96 Mon Sep 17 00:00:00 2001 From: matthijsb Date: Tue, 14 Apr 2026 15:53:00 +0200 Subject: [PATCH 50/51] - JAMNP refactor --- pyjamaz/settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyjamaz/settings.py b/pyjamaz/settings.py index 6a1bc603..e68dd26c 100644 --- a/pyjamaz/settings.py +++ b/pyjamaz/settings.py @@ -35,6 +35,9 @@ PVM_INTERPRETER = os.getenv("PVM_INTERPRETER", "NUMBA_AOT") RPC_SERVER_MAX_SIZE = 30 * 1024 * 1024 +# validator overrides for JAMNP-S +# Keys are validator Ed25519 public key hex strings ( with or without 0x) +# Values can be either "host:port" or ("host", port), or None to ignore connections (for debugging purposes) VALIDATOR_ENDPOINT_OVERRIDES = {} try: From d0ee77ed3f141d6e2354b6008f6ac1e1bc2e691a Mon Sep 17 00:00:00 2001 From: matthijsb Date: Tue, 14 Apr 2026 17:05:23 +0200 Subject: [PATCH 51/51] - merged with main, testing ce128 --- pyjamaz/cli.py | 8 +-- pyjamaz/runtime/extrinsics.py | 59 +++++++++++++-------- pyjamaz/runtime/pipelines/accumulate.py | 4 +- pyjamaz/transport/jamnp_s/protocol/ce128.py | 17 +++--- 4 files changed, 55 insertions(+), 33 deletions(-) diff --git a/pyjamaz/cli.py b/pyjamaz/cli.py index 283b65ab..66ebc8c3 100644 --- a/pyjamaz/cli.py +++ b/pyjamaz/cli.py @@ -304,6 +304,10 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, try: async with anyio.create_task_group() as tg: + # Create and start runtime + app.runtime = NodeRuntime(app) + app.runtime.start(tg) + # TODO: we need to start this manually in all event loops, make an AppFactory that handles this in a generic way # Create a subscriber to process incoming messages (fx from a protocol) tg.start_soon(app.pubsub.process_messages) @@ -359,10 +363,6 @@ async def run(seed, port, ts, culprit, block_dir, record_traces, custom_db_path, await anyio.sleep(ts - time.time()) - # Create and start runtime - app.runtime = NodeRuntime(app) - app.runtime.start(tg) - except (KeyboardInterrupt, CancelledError): logging.info("Stopping node...") # stop_event.set() diff --git a/pyjamaz/runtime/extrinsics.py b/pyjamaz/runtime/extrinsics.py index 165ad119..cbe45095 100644 --- a/pyjamaz/runtime/extrinsics.py +++ b/pyjamaz/runtime/extrinsics.py @@ -1,16 +1,19 @@ +import asyncio import logging from typing import Dict, List, Optional from bandersnatch_vrfs import RingContext, vrf_output -from pyjamaz.graypaper_constants import TICKET_ENTRIES, EPOCH_TIMESLOTS, TICKET_SUBMISSION_END_SLOT, \ - MAXIMUM_EXTRINSIC_TICKETS +from pyjamaz.constants import MESSAGE_TYPES +from pyjamaz.graypaper_constants import TICKET_ENTRIES, MAXIMUM_EXTRINSIC_TICKETS, TICKET_SUBMISSION_END_SLOT, \ + EPOCH_TIMESLOTS from pyjamaz.hashing import blake2b_256_hash -from pyjamaz.models.block import TicketEnvelope, Guarantee, Assurance, Preimage +from pyjamaz.models.block import TicketEnvelope, Guarantee, Assurance, Preimage, Block from pyjamaz.models.common import TicketBody, WorkPackage from pyjamaz.models.state import ServicesState from pyjamaz.models.stf_output import SafroleErrorCode from pyjamaz.signing import BandersnatchKeypair +from pyjamaz.transport.pubsub import PubSub, PubSubSignal from pyjamaz.utils import vrf_input_ticket_seal, format_hash @@ -25,6 +28,8 @@ def __init__(self, ring_data: bytes): self.preimage_queue: List[Preimage] = [] self.ring_data = ring_data + self._ticket_queue_lock = asyncio.Lock() + def create_ticket_body(self, ticket_data: TicketEnvelope, ring_context: RingContext, entropy: bytes) -> TicketBody: if ticket_data.attempt >= TICKET_ENTRIES: raise ValueError(SafroleErrorCode.bad_ticket_attempt) @@ -42,16 +47,18 @@ def create_ticket_body(self, ticket_data: TicketEnvelope, ring_context: RingCont return TicketBody(id=ring_vrf_output, attempt=ticket_data.attempt) - def add_ticket(self, ticket_data: TicketEnvelope, ring_public_keys: List[bytes], entropy: bytes): + async def add_ticket(self, ticket_data: TicketEnvelope, ring_public_keys: List[bytes], entropy: bytes): ring_context = RingContext(self.ring_data, ring_public_keys) ticket_body = self.create_ticket_body(ticket_data, ring_context, entropy) - self.tickets_queue[ticket_body.id] = ticket_data + async with self._ticket_queue_lock: + self.tickets_queue[ticket_body.id] = ticket_data def can_add_own_ticket(self, timeslot: int) -> bool: return len(self.own_tickets_next) < TICKET_ENTRIES and timeslot % EPOCH_TIMESLOTS < TICKET_SUBMISSION_END_SLOT - def add_own_ticket( - self, ring_context: RingContext, entropy: bytes, keypair: BandersnatchKeypair, author_index: int + async def add_own_ticket( + self, ring_context: RingContext, entropy: bytes, keypair: BandersnatchKeypair, author_index: int, + epoch_index: int = None, pubsub: PubSub = None ): if len(self.tickets_queue) > TICKET_ENTRIES: @@ -75,10 +82,16 @@ def add_own_ticket( logging.info(f'🎫 Generated ticket: {format_hash(ticket_id)}') logging.DEBUG and logging.debug(f'Generated ticket: id = {format_hash(ticket_id)} with entropy {format_hash(entropy)}') - self.tickets_queue[ticket_id] = ticket + async with self._ticket_queue_lock: + self.tickets_queue[ticket_id] = ticket self.own_tickets_next.append(ticket_id) - def collect_tickets(self) -> List[TicketEnvelope]: + # Notify new ticket is added + if pubsub and epoch_index is not None: + await pubsub.publish(PubSubSignal(topic=MESSAGE_TYPES.TICKET_ADD, data=[epoch_index, attempt, signature])) + + + async def collect_tickets(self) -> List[TicketEnvelope]: """ Collect tickets to include in a block @@ -87,11 +100,12 @@ def collect_tickets(self) -> List[TicketEnvelope]: List[TicketEnvelope] """ - collected_tickets = sorted(self.tickets_queue.items())[:MAXIMUM_EXTRINSIC_TICKETS] + async with self._ticket_queue_lock: + collected_tickets = sorted(self.tickets_queue.items())[:MAXIMUM_EXTRINSIC_TICKETS] - # Remove from queue - for key in [key for key, _ in collected_tickets]: - self.tickets_queue.pop(key) + # Remove from queue + for key in [key for key, _ in collected_tickets]: + self.tickets_queue.pop(key) return [ticket for _, ticket in collected_tickets] @@ -101,24 +115,27 @@ def is_own_ticket(self, ticket_id: bytes) -> bool: def own_ticket_count(self) -> int: return len(self.own_tickets_next) - def clear_own_tickets(self): - for ticket_id in self.own_tickets_next: - del self.tickets_queue[ticket_id] + async def clear_own_tickets(self): + async with self._ticket_queue_lock: + for ticket_id in self.own_tickets_next: + del self.tickets_queue[ticket_id] self.own_tickets_next = [] - def clear_tickets(self): - self.tickets_queue = {} + async def clear_tickets(self): + async with self._ticket_queue_lock: + self.tickets_queue = {} self.own_tickets_next = [] - def process_epoch_change(self): + async def process_epoch_change(self): self.own_tickets_current = self.own_tickets_next self.own_tickets_next = [] - self.tickets_queue = {} + async with self._ticket_queue_lock: + self.tickets_queue = {} def add_guarantee(self, guarantee: Guarantee): self.guarentees_queue[guarantee.report.hash()] = guarantee - def collect_guarantees(self) -> List[Guarantee]: + async def collect_guarantees(self) -> List[Guarantee]: guarentees = list(self.guarentees_queue.values()) self.guarentees_queue = {} return guarentees diff --git a/pyjamaz/runtime/pipelines/accumulate.py b/pyjamaz/runtime/pipelines/accumulate.py index 88ec4d74..4ab74541 100644 --- a/pyjamaz/runtime/pipelines/accumulate.py +++ b/pyjamaz/runtime/pipelines/accumulate.py @@ -117,7 +117,7 @@ async def _timeslot_worker(self) -> None: ) # Process tickets - self.app.block_extrinsic.process_epoch_change() + await self.app.block_extrinsic.process_epoch_change() DEBUG and logging.debug( f"Current tickets {[i.hex() for i in self.app.block_extrinsic.own_tickets_current]}" ) @@ -161,7 +161,7 @@ async def _timeslot_worker(self) -> None: # Rollback state from DB self.app.working_state = self.app.retrieve_jam_state() # TODO Make transactional - self.app.block_extrinsic.clear_tickets() + await self.app.block_extrinsic.clear_tickets() else: logging.info(f'💤 Waiting for block #{timeslot} | epoch #{epoch} | phase #{phase}') diff --git a/pyjamaz/transport/jamnp_s/protocol/ce128.py b/pyjamaz/transport/jamnp_s/protocol/ce128.py index e5b68864..f8009d56 100644 --- a/pyjamaz/transport/jamnp_s/protocol/ce128.py +++ b/pyjamaz/transport/jamnp_s/protocol/ce128.py @@ -58,13 +58,18 @@ def initiator_message(self, stream: JAMStream, data: bytes) -> None: req = MsgCE128BlockRequestResponse.from_jam_bytes(JamBytes(data)) blocks = req.blocks logger.info(f"Parsed {len(blocks)} blocks") - asyncio.create_task( - self.context.app.import_queue_add_blocks( - blocks, - on_success=MESSAGE_TYPES.CE128_SUCCESS, - on_failure=MESSAGE_TYPES.CE128_FAILURE, + # asyncio.create_task( + # self.context.app.import_queue_add_blocks( + # blocks, + # on_success=MESSAGE_TYPES.CE128_SUCCESS, + # on_failure=MESSAGE_TYPES.CE128_FAILURE, + # ) + # ) + for block in blocks: + asyncio.create_task( + self.context.app.runtime.accumulate_pipeline.add_block(block) ) - ) + def acceptor_message(self, stream: JAMStream, data: bytes) -> None: logger.debug(f"CE128 acceptor stream {stream.stream_id} received block request")