From 5374e7d37f414f8daa329706e56f54186b746f77 Mon Sep 17 00:00:00 2001 From: KonradStaniec Date: Fri, 18 Nov 2022 10:00:06 +0100 Subject: [PATCH] Initial implementation of getting range of lc updates (#1301) * Initial implementation of getting lc updates --- fluffy/content_db.nim | 2 +- .../beacon_light_client.nim | 14 +- .../beacon_light_client_manager.nim | 53 +++-- .../light_client_content.nim | 58 +++-- .../beacon_light_client/light_client_db.nim | 201 ++++++++++++++++++ .../light_client_network.nim | 77 ++++--- .../light_client_test_data.nim | 3 + .../light_client_test_helpers.nim | 11 +- .../test_light_client_network.nim | 79 +++++-- 9 files changed, 408 insertions(+), 90 deletions(-) create mode 100644 fluffy/network/beacon_light_client/light_client_db.nim diff --git a/fluffy/content_db.nim b/fluffy/content_db.nim index 9e0f04178..8753eb78c 100644 --- a/fluffy/content_db.nim +++ b/fluffy/content_db.nim @@ -8,7 +8,7 @@ {.push raises: [Defect].} import - std/[options, heapqueue], + std/[options], chronicles, metrics, eth/db/kvstore, diff --git a/fluffy/network/beacon_light_client/beacon_light_client.nim b/fluffy/network/beacon_light_client/beacon_light_client.nim index b8d450a4a..e23500e28 100644 --- a/fluffy/network/beacon_light_client/beacon_light_client.nim +++ b/fluffy/network/beacon_light_client/beacon_light_client.nim @@ -112,23 +112,23 @@ proc new*( else: false - func getFinalizedPeriod(): SyncCommitteePeriod = + func getFinalizedSlot(): Slot = if lightClient.store[].isSome: - lightClient.store[].get.finalized_header.slot.sync_committee_period + lightClient.store[].get.finalized_header.slot else: - GENESIS_SLOT.sync_committee_period + GENESIS_SLOT - func getOptimisticPeriod(): SyncCommitteePeriod = + func getOptimistiSlot(): Slot = if lightClient.store[].isSome: - lightClient.store[].get.optimistic_header.slot.sync_committee_period + lightClient.store[].get.optimistic_header.slot else: - GENESIS_SLOT.sync_committee_period + GENESIS_SLOT lightClient.manager = LightClientManager.init( lightClient.network, rng, getTrustedBlockRoot, bootstrapVerifier, updateVerifier, finalityVerifier, optimisticVerifier, isLightClientStoreInitialized, isNextSyncCommitteeKnown, - getFinalizedPeriod, getOptimisticPeriod, getBeaconTime) + getFinalizedSlot, getOptimistiSlot, getBeaconTime) lightClient diff --git a/fluffy/network/beacon_light_client/beacon_light_client_manager.nim b/fluffy/network/beacon_light_client/beacon_light_client_manager.nim index 1146fbe42..f8de6c548 100644 --- a/fluffy/network/beacon_light_client/beacon_light_client_manager.nim +++ b/fluffy/network/beacon_light_client/beacon_light_client_manager.nim @@ -23,6 +23,10 @@ logScope: type Nothing = object + SlotInfo = object + finalSlot: Slot + optimisticSlot: Slot + NetRes*[T] = Result[T, void] Endpoint[K, V] = (K, V) # https://github.com/nim-lang/Nim/issues/19531 @@ -31,9 +35,9 @@ type UpdatesByRange = Endpoint[Slice[SyncCommitteePeriod], altair.LightClientUpdate] FinalityUpdate = - Endpoint[Nothing, altair.LightClientFinalityUpdate] + Endpoint[SlotInfo, altair.LightClientFinalityUpdate] OptimisticUpdate = - Endpoint[Nothing, altair.LightClientOptimisticUpdate] + Endpoint[Slot, altair.LightClientOptimisticUpdate] ValueVerifier[V] = proc(v: V): Future[Result[void, BlockError]] {.gcsafe, raises: [Defect].} @@ -50,8 +54,9 @@ type proc(): Option[Eth2Digest] {.gcsafe, raises: [Defect].} GetBoolCallback* = proc(): bool {.gcsafe, raises: [Defect].} - GetSyncCommitteePeriodCallback* = - proc(): SyncCommitteePeriod {.gcsafe, raises: [Defect].} + + GetSlotCallback* = + proc(): Slot {.gcsafe, raises: [Defect].} LightClientManager* = object network: LightClientNetwork @@ -63,8 +68,8 @@ type optimisticUpdateVerifier: OptimisticUpdateVerifier isLightClientStoreInitialized: GetBoolCallback isNextSyncCommitteeKnown: GetBoolCallback - getFinalizedPeriod: GetSyncCommitteePeriodCallback - getOptimisticPeriod: GetSyncCommitteePeriodCallback + getFinalizedSlot: GetSlotCallback + getOptimisticSlot: GetSlotCallback getBeaconTime: GetBeaconTimeFn loopFuture: Future[void] @@ -79,8 +84,8 @@ func init*( optimisticUpdateVerifier: OptimisticUpdateVerifier, isLightClientStoreInitialized: GetBoolCallback, isNextSyncCommitteeKnown: GetBoolCallback, - getFinalizedPeriod: GetSyncCommitteePeriodCallback, - getOptimisticPeriod: GetSyncCommitteePeriodCallback, + getFinalizedSlot: GetSlotCallback, + getOptimisticSlot: GetSlotCallback, getBeaconTime: GetBeaconTimeFn ): LightClientManager = ## Initialize light client manager. @@ -94,8 +99,8 @@ func init*( optimisticUpdateVerifier: optimisticUpdateVerifier, isLightClientStoreInitialized: isLightClientStoreInitialized, isNextSyncCommitteeKnown: isNextSyncCommitteeKnown, - getFinalizedPeriod: getFinalizedPeriod, - getOptimisticPeriod: getOptimisticPeriod, + getFinalizedSlot: getFinalizedSlot, + getOptimisticSlot: getOptimisticSlot, getBeaconTime: getBeaconTime ) @@ -108,7 +113,7 @@ proc isGossipSupported*( return false let - finalizedPeriod = self.getFinalizedPeriod() + finalizedPeriod = self.getFinalizedSlot().sync_committee_period isNextSyncCommitteeKnown = self.isNextSyncCommitteeKnown() if isNextSyncCommitteeKnown: period <= finalizedPeriod + 1 @@ -142,16 +147,21 @@ proc doRequest( # https://github.com/ethereum/consensus-specs/blob/v1.2.0/specs/altair/light-client/p2p-interface.md#getlightclientfinalityupdate proc doRequest( e: typedesc[FinalityUpdate], - n: LightClientNetwork + n: LightClientNetwork, + slotInfo: SlotInfo ): Future[NetRes[altair.LightClientFinalityUpdate]] = - n.getLightClientFinalityUpdate() + n.getLightClientFinalityUpdate( + distinctBase(slotInfo.finalSlot), + distinctBase(slotInfo.optimisticSlot) + ) # https://github.com/ethereum/consensus-specs/blob/v1.2.0/specs/altair/light-client/p2p-interface.md#getlightclientoptimisticupdate proc doRequest( e: typedesc[OptimisticUpdate], - n: LightClientNetwork + n: LightClientNetwork, + optimisticSlot: Slot ): Future[NetRes[altair.LightClientOptimisticUpdate]] = - n.getLightClientOptimisticUpdate() + n.getLightClientOptimisticUpdate(distinctBase(optimisticSlot)) template valueVerifier[E]( self: LightClientManager, @@ -296,8 +306,10 @@ proc loop(self: LightClientManager) {.async.} = # Fetch updates var allowWaitNextPeriod = false let - finalized = self.getFinalizedPeriod() - optimistic = self.getOptimisticPeriod() + finalSlot = self.getFinalizedSlot() + optimisticSlot = self.getOptimisticSlot() + finalized = finalSlot.sync_committee_period + optimistic = optimisticSlot.sync_committee_period current = wallTime.slotOrZero().sync_committee_period isNextSyncCommitteeKnown = self.isNextSyncCommitteeKnown() @@ -310,10 +322,13 @@ proc loop(self: LightClientManager) {.async.} = elif finalized + 1 < current: await self.query(UpdatesByRange, finalized + 1 ..< current) elif finalized != optimistic: - await self.query(FinalityUpdate) + await self.query(FinalityUpdate, SlotInfo( + finalSlot: finalSlot, + optimisticSlot: optimisticSlot + )) else: allowWaitNextPeriod = true - await self.query(OptimisticUpdate) + await self.query(OptimisticUpdate, optimisticSlot) schedulingMode = if not didProgress or not self.isGossipSupported(current): diff --git a/fluffy/network/beacon_light_client/light_client_content.nim b/fluffy/network/beacon_light_client/light_client_content.nim index 5313b33ae..f2a1b5d7a 100644 --- a/fluffy/network/beacon_light_client/light_client_content.nim +++ b/fluffy/network/beacon_light_client/light_client_content.nim @@ -41,12 +41,20 @@ type LightClientBootstrapKey* = object blockHash*: Digest - #TODO Following types will need revision and improvements LightClientUpdateKey* = object + startPeriod*: uint64 + count*: uint64 + # TODO Following types are not yet included in spec + # optimisticSlot - slot of attested header of the update + # finalSlot - slot of finalized header of the update LightClientFinalityUpdateKey* = object + optimisticSlot: uint64 + finalSlot: uint64 + # optimisticSlot - slot of attested header of the update LightClientOptimisticUpdateKey* = object + optimisticSlot: uint64 ContentKey* = object case contentType*: ContentType @@ -59,8 +67,8 @@ type of lightClientOptimisticUpdate: lightClientOptimisticUpdateKey*: LightClientOptimisticUpdateKey - ForkedLightClientUpdateBytes = List[byte, MAX_LIGHT_CLIENT_UPDATE_SIZE] - LightClientUpdateList = List[ForkedLightClientUpdateBytes, MAX_REQUEST_LIGHT_CLIENT_UPDATES] + ForkedLightClientUpdateBytes* = List[byte, MAX_LIGHT_CLIENT_UPDATE_SIZE] + LightClientUpdateList* = List[ForkedLightClientUpdateBytes, MAX_REQUEST_LIGHT_CLIENT_UPDATES] func encode*(contentKey: ContentKey): ByteList = ByteList.init(SSZ.encode(contentKey)) @@ -194,21 +202,43 @@ proc encodeLightClientUpdatesForked*( return SSZ.encode(lu) -proc decodeLightClientUpdatesForked*( - forks: ForkDigests, - data: openArray[byte]): Result[seq[altair.LightClientUpdate], string] = +proc decodeLightClientUpdatesForkedAsList*( + data: openArray[byte]): Result[LightClientUpdateList, string] = try: let listDecoded = SSZ.decode( data, LightClientUpdateList ) - - var updates: seq[altair.LightClientUpdate] - - for enc in listDecoded: - let updateDecoded = ? decodeLightClientUpdateForked(forks, enc.asSeq()) - updates.add(updateDecoded) - - return ok(updates) + return ok(listDecoded) except SszError as exc: return err(exc.msg) + +proc decodeLightClientUpdatesForked*( + forks: ForkDigests, + data: openArray[byte]): Result[seq[altair.LightClientUpdate], string] = + let listDecoded = ? decodeLightClientUpdatesForkedAsList(data) + + var updates: seq[altair.LightClientUpdate] + + for enc in listDecoded: + let updateDecoded = ? decodeLightClientUpdateForked(forks, enc.asSeq()) + updates.add(updateDecoded) + + return ok(updates) + +func finalityUpdateContentKey*(finalSlot: uint64, optimisticSlot: uint64): ContentKey = + ContentKey( + contentType: lightClientFinalityUpdate, + lightClientFinalityUpdateKey: LightClientFinalityUpdateKey( + optimisticSlot: optimisticSlot, + finalSlot: finalSlot + ) + ) + +func optimisticUpdateContentKey*(optimisticSlot: uint64): ContentKey = + ContentKey( + contentType: lightClientOptimisticUpdate, + lightClientOptimisticUpdateKey: LightClientOptimisticUpdateKey( + optimisticSlot: optimisticSlot + ) + ) diff --git a/fluffy/network/beacon_light_client/light_client_db.nim b/fluffy/network/beacon_light_client/light_client_db.nim new file mode 100644 index 000000000..e4e2ea92f --- /dev/null +++ b/fluffy/network/beacon_light_client/light_client_db.nim @@ -0,0 +1,201 @@ +# Nimbus +# Copyright (c) 2022 Status Research & Development GmbH +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +{.push raises: [Defect].} + +import + std/[options], + chronicles, + metrics, + eth/db/kvstore, + eth/db/kvstore_sqlite3, + stint, + stew/[results, byteutils], + ssz_serialization, + ./light_client_content, + ../wire/portal_protocol + +export kvstore_sqlite3 + +# We only one best optimistic and one best final update +const + bestFinalUpdateKey = toContentId(ByteList.init(toBytes("bestFinal"))) + bestOptimisticUpdateKey = toContentId(ByteList.init(toBytes("bestOptimistic"))) + +type + BestLightClientUpdateStore = ref object + putStmt: SqliteStmt[(int64, seq[byte]), void] + getBulkStmt: SqliteStmt[(int64, int64), seq[byte]] + + LightClientDb* = ref object + kv: KvStoreRef + lcuStore: BestLightClientUpdateStore + +template expectDb(x: auto): untyped = + # There's no meaningful error handling implemented for a corrupt database or + # full disk - this requires manual intervention, so we'll panic for now + x.expect("working database (disk broken/full?)") + +proc initBestUpdatesStore( + backend: SqStoreRef, + name: string): KvResult[BestLightClientUpdateStore] = + ? backend.exec(""" + CREATE TABLE IF NOT EXISTS `""" & name & """` ( + `period` INTEGER PRIMARY KEY, -- `SyncCommitteePeriod` + `update` BLOB -- `altair.LightClientUpdate` (SSZ) + ); + """) + + let + putStmt = backend.prepareStmt(""" + REPLACE INTO `""" & name & """` ( + `period`, `update` + ) VALUES (?, ?); + """, (int64, seq[byte]), void, managed = false).expect("SQL query OK") + + getBulkStmt = backend.prepareStmt(""" + SELECT `update` + FROM `""" & name & """` + WHERE `period` >= ? AND `period` < ?; + """, (int64, int64), seq[byte], managed = false).expect("SQL query OK") + + ok BestLightClientUpdateStore( + putStmt: putStmt, + getBulkStmt: getBulkStmt + ) + +proc new*( + T: type LightClientDb, path: string, inMemory = false): + LightClientDb = + let db = + if inMemory: + SqStoreRef.init("", "lc-test", inMemory = true).expect( + "working database (out of memory?)") + else: + SqStoreRef.init(path, "lc").expectDb() + + let kvStore = kvStore db.openKvStore().expectDb() + let lcuStore = initBestUpdatesStore(db, "lcu").expectDb() + + LightClientDb( + kv: kvStore, + lcuStore: lcuStore + ) + +# TODO Add checks that uint64 can be safely casted to int64 +proc getLightClientUpdates( + db: LightClientDb, start: uint64, to: uint64 +): LightClientUpdateList = + var updates: LightClientUpdateList + var update: seq[byte] + for res in db.lcuStore.getBulkStmt.exec((start.int64, to.int64), update): + res.expect("SQL query OK") + let byteList = List[byte, MAX_LIGHT_CLIENT_UPDATE_SIZE].init(update) + discard updates.add(byteList) + return updates + +func putLightClientUpdate( + db: LightClientDb, period: uint64, update: seq[byte]) = + let res = db.lcuStore.putStmt.exec((period.int64, update)) + res.expect("SQL query OK") + +## Private KvStoreRef Calls + +proc get(kv: KvStoreRef, key: openArray[byte]): results.Opt[seq[byte]] = + var res: results.Opt[seq[byte]] = Opt.none(seq[byte]) + proc onData(data: openArray[byte]) = res = ok(@data) + + discard kv.get(key, onData).expectDb() + + return res + +## Private LightClientDb calls +proc get(db: LightClientDb, key: openArray[byte]): results.Opt[seq[byte]] = + db.kv.get(key) + +proc put(db: LightClientDb, key, value: openArray[byte]) = + db.kv.put(key, value).expectDb() + +proc get*(db: LightClientDb, key: ContentId): results.Opt[seq[byte]] = + # TODO: Here it is unfortunate that ContentId is a uint256 instead of Digest256. + db.get(key.toByteArrayBE()) + +proc put*(db: LightClientDb, key: ContentId, value: openArray[byte]) = + db.put(key.toByteArrayBE(), value) + +proc createGetHandler*(db: LightClientDb): DbGetHandler = + return ( + proc(contentKey: ByteList, contentId: ContentId): results.Opt[seq[byte]] = + let contentKeyResult = decode(contentKey) + # TODO: as this should not fail, maybe it is better to raiseAssert ? + if contentKeyResult.isNone(): + return Opt.none(seq[byte]) + + let ck = contentKeyResult.get() + + if ck.contentType == lightClientUpdate: + let + # TODO: add validation that startPeriod is not from the future, + # this requires db to be aware off the current beacon time + startPeriod = ck.lightClientUpdateKey.startPeriod + # get max 128 updates + numOfUpdates = min( + uint64(MAX_REQUEST_LIGHT_CLIENT_UPDATES), + ck.lightClientUpdateKey.count + ) + to = startPeriod + numOfUpdates + updates = db.getLightClientUpdates(startPeriod, to) + + if len(updates) == 0: + return Opt.none(seq[byte]) + else: + return ok(SSZ.encode(updates)) + elif ck.contentType == lightClientFinalityUpdate: + # TODO Return only when the update is better that requeste by contentKey + return db.get(bestFinalUpdateKey) + elif ck.contentType == lightClientOptimisticUpdate: + # TODO Return only when the update is better that requeste by contentKey + return db.get(bestOptimisticUpdateKey) + else: + return db.get(contentId) + ) + +proc createStoreHandler*(db: LightClientDb): DbStoreHandler = + return (proc( + contentKey: ByteList, + contentId: ContentId, + content: seq[byte]) {.raises: [Defect], gcsafe.} = + let contentKeyResult = decode(contentKey) + # TODO: as this should not fail, maybe it is better to raiseAssert ? + if contentKeyResult.isNone(): + return + + let ck = contentKeyResult.get() + + if ck.contentType == lightClientUpdate: + # Lot of assumptions here: + # - that updates are continious i.e there is no period gaps + # - that updates start from startPeriod of content key + var period = ck.lightClientUpdateKey.startPeriod + + let updatesResult = decodeLightClientUpdatesForkedAsList(content) + + if updatesResult.isErr: + return + + let updates = updatesResult.get() + + for update in updates.asSeq(): + db.putLightClientUpdate(period, update.asSeq()) + inc period + elif ck.contentType == lightClientFinalityUpdate: + db.put(bestFinalUpdateKey, content) + elif ck.contentType == lightClientOptimisticUpdate: + db.put(bestOptimisticUpdateKey, content) + else: + db.put(contentId, content) + ) diff --git a/fluffy/network/beacon_light_client/light_client_network.nim b/fluffy/network/beacon_light_client/light_client_network.nim index d6ac428b3..6ff4dffd6 100644 --- a/fluffy/network/beacon_light_client/light_client_network.nim +++ b/fluffy/network/beacon_light_client/light_client_network.nim @@ -13,10 +13,9 @@ import eth/p2p/discoveryv5/[protocol, enr], beacon_chain/spec/forks, beacon_chain/spec/datatypes/[phase0, altair, bellatrix], - ../../content_db, ../../../nimbus/constants, ../wire/[portal_protocol, portal_stream, portal_protocol_config], - "."/light_client_content + "."/[light_client_content, light_client_db] logScope: topics = "portal_lc" @@ -27,7 +26,7 @@ const type LightClientNetwork* = ref object portalProtocol*: PortalProtocol - contentDB*: ContentDB + lightClientDb*: LightClientDb contentQueue*: AsyncQueue[(ContentKeysList, seq[seq[byte]])] forkDigests*: ForkDigests processContentLoop: Future[void] @@ -35,14 +34,6 @@ type func toContentIdHandler(contentKey: ByteList): results.Opt[ContentId] = ok(toContentId(contentKey)) -func encodeKey(k: ContentKey): (ByteList, ContentId) = - let keyEncoded = encode(k) - return (keyEncoded, toContentId(keyEncoded)) - -proc dbGetHandler(db: ContentDB, contentId: ContentId): - Option[seq[byte]] {.raises: [Defect], gcsafe.} = - db.get(contentId) - proc getLightClientBootstrap*( l: LightClientNetwork, trustedRoot: Digest): Future[results.Opt[altair.LightClientBootstrap]] {.async.} = @@ -77,22 +68,34 @@ proc getLightClientUpdatesByRange*( l: LightClientNetwork, startPeriod: uint64, count: uint64): Future[results.Opt[seq[altair.LightClientUpdate]]] {.async.} = - # TODO: Not implemented! - return Opt.none(seq[altair.LightClientUpdate]) + let + bk = LightClientUpdateKey(startPeriod: startPeriod, count: count) + ck = ContentKey( + contentType: lightClientUpdate, + lightClientUpdateKey: bk + ) + keyEncoded = encode(ck) + contentID = toContentId(keyEncoded) -proc getLatestUpdate( l: LightClientNetwork, optimistic: bool):Future[results.Opt[seq[byte]]] {.async.} = - let ck = - if optimistic: - ContentKey( - contentType: lightClientOptimisticUpdate, - lightClientOptimisticUpdateKey: LightClientOptimisticUpdateKey() - ) - else: - ContentKey( - contentType: lightClientFinalityUpdate, - lightClientFinalityUpdateKey: LightClientFinalityUpdateKey() - ) + let updatesResult = + await l.portalProtocol.contentLookup(keyEncoded, contentId) + if updatesResult.isNone(): + warn "Failed fetching updates network", contentKey = keyEncoded + return Opt.none(seq[altair.LightClientUpdate]) + + let + updates = updatesResult.unsafeGet() + decodingResult = decodeLightClientUpdatesForked(l.forkDigests, updates.content) + + if decodingResult.isErr: + return Opt.none(seq[altair.LightClientUpdate]) + else: + # TODO Not doing validation for now, as probably it should be done by layer + # above + return Opt.some(decodingResult.get()) + +proc getUpdate(l: LightClientNetwork, ck: ContentKey):Future[results.Opt[seq[byte]]] {.async.} = let keyEncoded = encode(ck) contentID = toContentId(keyEncoded) @@ -108,9 +111,14 @@ proc getLatestUpdate( l: LightClientNetwork, optimistic: bool):Future[results.Op # are implemented in naive way as finding first peer with any of those updates # and treating it as latest. This will probably need to get improved. proc getLightClientFinalityUpdate*( - l: LightClientNetwork + l: LightClientNetwork, + currentFinalSlot: uint64, + currentOptimisticSlot: uint64 ): Future[results.Opt[altair.LightClientFinalityUpdate]] {.async.} = - let lookupResult = await l.getLatestUpdate(optimistic = false) + + let + ck = finalityUpdateContentKey(currentFinalSlot, currentOptimisticSlot) + lookupResult = await l.getUpdate(ck) if lookupResult.isErr: return Opt.none(altair.LightClientFinalityUpdate) @@ -125,10 +133,13 @@ proc getLightClientFinalityUpdate*( return Opt.some(decodingResult.get()) proc getLightClientOptimisticUpdate*( - l: LightClientNetwork + l: LightClientNetwork, + currentOptimisticSlot: uint64 ): Future[results.Opt[altair.LightClientOptimisticUpdate]] {.async.} = - let lookupResult = await l.getLatestUpdate(optimistic = true) + let + ck = optimisticUpdateContentKey(currentOptimisticSlot) + lookupResult = await l.getUpdate(ck) if lookupResult.isErr: return Opt.none(altair.LightClientOptimisticUpdate) @@ -145,7 +156,7 @@ proc getLightClientOptimisticUpdate*( proc new*( T: type LightClientNetwork, baseProtocol: protocol.Protocol, - contentDB: ContentDB, + lightClientDb: LightClientDb, streamManager: StreamManager, forkDigests: ForkDigests, bootstrapRecords: openArray[Record] = [], @@ -157,14 +168,14 @@ proc new*( portalProtocol = PortalProtocol.new( baseProtocol, lightClientProtocolId, - toContentIdHandler, createGetHandler(contentDB), stream, bootstrapRecords, + toContentIdHandler, createGetHandler(lightClientDb), stream, bootstrapRecords, config = portalConfig) - portalProtocol.dbPut = createStoreHandler(contentDB, portalConfig.radiusConfig, portalProtocol) + portalProtocol.dbPut = createStoreHandler(lightClientDb) LightClientNetwork( portalProtocol: portalProtocol, - contentDB: contentDB, + lightClientDb: lightClientDb, contentQueue: contentQueue, forkDigests: forkDigests ) diff --git a/fluffy/tests/beacon_light_client_tests/light_client_test_data.nim b/fluffy/tests/beacon_light_client_tests/light_client_test_data.nim index 445714199..f088616bf 100644 --- a/fluffy/tests/beacon_light_client_tests/light_client_test_data.nim +++ b/fluffy/tests/beacon_light_client_tests/light_client_test_data.nim @@ -14,6 +14,8 @@ const bootstrapHex = "60b747000000000018d305000000000023fd2bf54d3f71f13ee0ced4ab const lightClientUpdateHex = "6cac470000000000a2a6050000000000989604e65d66d380233f025b10f7b99b8bdf8610ae627ed61825266fbe1aa15a9e33aeeb92443af6d49f7b1b4f6c8f973be374b548db40b5f32f82db57cd29349a4fe9c882bde3ad2ac67b67795c1f89de57c6f1e361c60415375ad0d19605d0928540d3a8819e60842076668e9907f9d9664c8a8a5a94f658a0da1cdfdac6114a0781f13b5e402fa4283f7f4e2e973c8706beb542fc4c83c7de6b4a3530a8174859a98bfe96587ef166bbae213b268ec21ed0ad166f4e32ff820b37823e533ba3afafa8eea4a9408278602ce5830d474355c50dbe34162e53482aa093de4328f26a602aacc01134d3e3dcc55aed33c48a5442438ea51b9af7fde941360f5506ddfc77f55e341fdfdb41b41f39a6507186e6a3e9131f91f2e48098b4c0fa502caf3f28449cbe9e540ea50c70e1f899e77f365758565a2f344d90d558d9b8002a6b6cdf844269a9551ee0f6bde6f3135b917295a0d935c5031fa7e44dbb28cdd37a6c2cbd1a949c3f097deacbdd1ea3f29e117efd04989955eaea2e97ad717151b414d12d3a69ed7e083972c1dfd65681ef7ea6ab4f2b549c5032db6a24e07e8f3e52f656835ee7a4ea5ebc6fce106530aecd990d2730d3c207c3191c8cdcf6107052051924391546ab75ef796157635761c6bd1c53c61f1bd437a5b3dfd302178876951e388070b161655f2cf9a9bd11d96590b37315cb562c33aac5067d21d943d2c01d0e1f6773412e5474fa102fc7afcfad0448288767069998d41a123701208c11414adcbf37e0f6748f4a27eebe5bf65d155039bbd90b027a0e27aaf327895f1f6fa1cc47b23b08edb3f0b375768782422c85b45643f051da52e3e584fc4ff84aaf8dcceec257db7a0a516669a3b4664e87cf747da6738cea5163c3ebdabeb7b9292e1cd7e56933362502b8139cfa51fd254011acaf58d0782904db71dca1bcad4fe4630905aba558094175007610d574702ab4be6b9a743a031f850dcf3a2a6950d30b82c76ea37dbe7a949cc2897bda78b921684614fbe5f9d59d96b2d0b3e10d4b31ea58439e93ccee6d48cc1e8020e3da96456ca9626ba597943c2fb2d53179105881f8a170251f75d00c493054c770572aee3c8da2b73558ba6e3640e61210417d0c0bf9954d4e0a02fb15b7fb043a90b09539a73c67c1b9a4a33fb11987960547e4a7e08cbd1083d340ad9df29ff42d94c44f02301d1c41539adb93013a6a05c30d51e69642808985f82fc3983c2e4fbc823d8e657b9e316bc56116041d43f0b9d071501790163f6bb83b9223e1ad84213922d889102a230ef141300adc09c0244019f0a694a05d05eaa23e9d6cca506b65dd9529665c38629979a70f04def4a9eab96ae21ff84254c45070cff7430e8fdc3e4d5c2ee6258e8c79b63898185390151b295c2c9d9e2278c6b5c15ec364dbca1fc9d02ce5e60188286fd49e5d28212d0930aad3980451864fa8d4fbf036c50297ea652a5cde1c03d29793a300f7316de7a1a20572320484f55827cb24b4a2617fbcc9fab7fcf2ed34f1f7408d3fb8237a020495f10edff34c8054efe1e58eeec2cf197352a41a1fd84f873378675339974960a6bd4bc604dfe9eeb48c254d79b02c0f599f4015f18da1a566b47c275cf617bdb57dceb1f939fd75d77bdb56db0a19c9cb8ac162876dfba53d7b9f3b5a953c7d3a8b0013c152a2aa4070a8748f16c999e9591dd315ff0a3ded207c0986feba3fd518ebbab34f39d3e1a8c6e20c90528f5c84995c5ef8811ff38b7b4f726a33abd6b81d5e7df4abb0d1679d96b90416e675c5db1c0b33cf83343be00f99e3b6af34bc5975d67990fd1c0eb0c355d0d97c769e0fcdfbb7c443fdc0c4b339f2014618a390da2f4f0c3c12dfb1548ed1db6fc6a8dbcd6ec38d2e76b6119844b9af369940731baafa375490290e463020ab93115c9ef785693020a2ae99cd438902180c130f4f17008e92a88759128fdf186fd923649857903211b5d1f133b3bd30f94825257c3f26510ff784f1a99eaba01364d1b3357173a89629c60667415794271f26d237b16efb497fe0a76a8e4085c22ed8a5787e1ee403abc8987b138fffec05e12eddfc2c807de040c3ae4cc904468eefed8581c8bc07c4280d885be6fa3b226c36b75b4b6af8903f4d06b7fb7cbb04db3ba75ec290e1d2bebc0bb4e572923b1d7ab7fbda1da4239d2b4f3aad0e9df0aa05a713623f83680c58d88c8b980d483f4cfd0b02a5b74018d23f251ed4d56260309905d1f0cc8d2c66dd0e671dc7a8eff07a896eea22f31fa16b182fa2bde02acabb01f3b5ae10f796f43558580b2275dcc203ae38475ee872555d692202e429b5f9ea54aaefedeac8ed7f5c548e33de3d501b68a30ce7442d6f27dd376d380fff252f32bc7f47184c25dbfa5efccabc860082370f13c21149de4819fffc69291916b004ab9f36a79a9f73a625b761f62ae9522dedbfd46270098eb151c5f7a3451f8752804a23e30ac8c7fa86a597bc346b51e1919bb096a58fa5e4bfe19569244af3fb8c8d489c22d1f4880250d302c351d2a811cf11a88fd2c7a6948b9c3dfa474133ab62669f2859821dd9fa6b84b18b781fea0e5204ca05a49fea6eacdd61edff660e8d6e40e1a188ece121fd53942cd75ea111dbbd404d7f119dd76b0933cc8ebc39e7fb0bc067e45c18584c94c380757b45c74254d16a4d330bde257b69e25075a85124a6612ec04a3f0b938298fdb2e914851d2a914041963521755192dd89ec303454a7af020670cdbce2ce1f0b8269b968f439ce66962f3bd6a7c6b54d3cd02e279a313b395368af985f59b6640283f61cc49e40125f0edbfb4120a05583028261e2a873eee391b3b523387d8267196f17ddc77de45bd14659856cd6491fed63291ee821322e1d7bade7d3e96b795f8bb432f39f6b2866ebebe98d31d84f669f4ca9d12e87e99f84b9e146651abf57a43829951db1987d25b24080b084ed6a87d0ce08dc53fb6af4dd53b321164dafe742de9ffdb7403ccacc822e7daaf47891627b340e72858272c232ed2dce2aa2904a35567da0bd2fe78cb90c097ea4efac77648bd359347692a78ec1399306879eeb39035c668527242640baef1d5c37a61103ad663db69574a04265691cc9a97159f6c55f88001f117f6efbb309fb263739da6af3812ab13096d8dd588c6ddd8af230b1df8621f3830d54b24b487b16e583f803f90c457003a3eb60a4e534781393cbb20bf3e013616ca2e38ed5ae9d8216577c26296aa8fe3ae3acd60fcad00febb98494d87eb2b2aed98585bed8bf85aac89d1518e7efb8393f540da6865fb334d250b58546a008351e97ea32aca5796edaa9ab8af11b00cbd34d079be0ced0fb40243c0ca9de6cef725b30e64f49a4cbbc2a1fee815ddf28f093fac49de823515f04adeb007a94d0f1a1dd51345813ddce14ee2364409a02f2df581f10bc8f065afdb458af78a7fedfb51f7d4b465bc0e283505dfe3711cf2ef9d6c41b1b717953873909113deae867faf3b1472687c2d132517b0b0fd2cadc60a418e9dcbf23cab4df275e5f52a7d294e8811e226e84fe0bd1e54c1c62d04b61bb4520faac9e66fcecd23579fd53d995eb60a17c7693863b8d72eda62170ee85540f8554b606ab57dc99ef632aa284766df9d1b6a09f88f5be56b43bf7969b43637101e8b83a695a4ef72103033cdf035aef03c3ba5517a508dc1ab5f3a70dc03e1a80eca160400a6477856c6eb3c9a039837a4166d74f629b684ccd53c1851397765d53ce27a650c38bc765faa6b04b4a55bd22921d530f5185581cb644326cc7295a0b4232f6e5bf99dab39e65efd1ce686c83cfa6e7e282a92d3efb9ca5ccabfd8e48994a6f6457b302145febc135461acf3d0e8895ea17c8b1be2453c8fb93663524d43e0cfa2b82819721ee1c56b47ff2ad85945d551b5fc9594ed39c85c8dac75768c1b3644a1f7789e37b50cdbcff5f723abb777cc31de5c1d685cfceab1a1196a12e906febf46ffe6dd22482d544c78c8ec68cf6017b7774845ecce3f327f9f2b9c33441143609374e19f384ce6d299299faa6c772d54f92a3c0e65aa2fc5bb0e1141ef079951998bc95fd6d83a8f7aee6e69e16710cf545b78e97550fcc9428abb5be97f885bb4fa033f64c6523f705f27e7ec2fc122d10dc7b40f0feebf7dd4c9727ac4177b7b9a4f73ce5bd10d2b28257f80a8d2e0bee75805f2d7b6d6265d9e320e7c2c0f1f6b76b95210fb72855c6ce6cf1c6649280fe7b08d0a1c1ae3b902db2362fc6c793e943ebbb57e6aacd31bd51b97cc6a2748ab08ce337f5866cd48607a55f05da882ebc2f2e98702b96b3a69023a092af098a2ef13db0d9822aaebc5a99d4fd19fdc253fbeab76cddb0e85da6799ea5113223b5a9b151f1308d99d492a9a4e6e57853bd3ecc33956eb761bcc5ebce595bad093802d40f672eaa0222cd29eaec72561ef37ae01f60be77932d3280558fe0e21f119a6b02324037e0eae9060169d244402c65fc1c2e927f073622738b627ea442ffd95a5cf0127d88c25aaa8d5d13f3999c5d8b318e71a12f12cfeb4e6eecdf9b54c8544ab9dc4eec3b528be3065788e047e6f4839dbefb968129fd31294ae006db22ad249ea43ae0e8d9fd613856488cec3da153533c4b6b9965baf87e3533bdec0284a331bb38a1e557fbce279b279c383c84376ba869c2615dcc6ee6954317ef223d0d210abd6b75fc28bf1b409d5686e8c88c214d1a8cd4f31bde26a2bef4781f41d6e0a457a392e7a9ab8b94f0a5048d16d11acd7394a8ee26effca678252c963ea0dcf55cb879430d33461cd966bf588846752e06758c7da2357a9f61973d5b64ea27a2f8d086ae463896e056a56da23a06b9010db0fd4bc51171cf21e61caa91f0ef21132c19a008c0c611cbcd2526affb37384409808ecbf67fa5f32c9f44f7979abd9092b7ea8c804cb42db76c778714c1a01e16630c97b98e0f53e742c05d54be141872f8393ba9fc7110011202d8d98e580ea3bc4324f269d769a094e67ff8aec9de4faf1c7c225b972853f87774514accae12e6156f35ec5341fb97af4ab862bd8aae1c6f1b7bdca51d7d53ca9f06ea43559529b8db44e20019bfa4056f362fd5f731aa6e2323e4d098e6469085efad05c88c65b4a925b3602f8b5ca2ffd16bec00ac00e795b3ce9138c97608fe35293017217f85cda7d34f659e076bdfa23f2e189677ae78ec8412651dc9b90fc94108a778ab5ea6dc236c226ca68a1d171ce103afdaa2a1c3e7deeb0a889a5c431532b7b207a37204fc69b403caeccc042e85d24e7456fbb9713cda2062a6c2fff87825dc07bbdb18319eeaafe3663f9daca6f3aafdd2c59bcd26e56d528569add1d7dd0327b0df8389db1358dd354ca142ad60c0a9569b92a6d5947754d260e933060fb2ace0779262f97b00b1b0f498c021b2e18f87d7067cd37d3b69df3ed64f92b196346c40b2734a06e85b60f4ce41bdf9966d3d0a5bc2495919416777aa32994a4ef1eab7041048c6ccf2e0dcc608dd76a98a2aaef603c2c17b282756e8532571b28eba85720709f1aa782560d44d27efdcc50a98e222dcb10ab432551c3a0fd32cda7c4fa375346a59ca5187426f3626b546be5390476c6d262650cd324e8b6e85305694106343f3370fc90bf7b10900fb117dfcc2b9de55e41fdefbd21fc49c8e4d755a30bb5df072b2742d549d237aa4f8fd8291ad3e6201917cb8c937985132c706037fef55606ab1d1153a5d9dc78bc5f3aad6f9f332b849d31bde7a6d3c2c12c7536c9ad777b172216ce4a23dd6f199b505747abf9dcc443a925a719c808b0a9a24bfe3f1d83c29bb8e55103ade68bf94fb2a9aedc0efe7b1409cf495a61620d8eaccc2072c6157a076af29d1a9acc5dcac5e186f55f87c28d3174e685ef9009bef9ecee703294b256d1e0d1e210c64956acb8f6bce04c97312d34e516183578e3c13a506987037e74e7a36e4bcb85ef816ba8afea4ef5d2f8edb391d2a783aff39b04d416d6a76daa49dd62166a4c8d92c61b839a079e7d3913d87a8b56b0988ff7f5aa5620d5e857b7d579ddfbb021c89c7090b4c1974e6d123f4a4218a20226f01ddd898ca2bfecfd6fb4c5cf0a45cfdac02c947c6f52d0146a292004887adcb74266999cd775b94d66d04e2b7f99dff70e1cf427b9792bff64d3bfa7374df3481669e48d033db4142811aebf91760c2dab732c31103de918b8392038b0270f9dd1f173644a4a9180a2e4ccf6e2cdee877ac0c35776486be8a97de1f39b71fad4f4b0a5aaee2e84124a97a4396fc64e2f1bf2111e96cbde26ccd3e2849dc046c49d1df1d55ed515edf7fa01c9ce18263cd528154246b72977644473a95ebabbe62af81af7880477b52b726ebec0e68546bf56c06593ca1c3b82ec160c270442c2d1ff1be36654c1baf52a7b8b4ee9359937f72389995057aa7df2a0f51913564b7b6fb45f9074e9f654eef7a60402e0d6d8eb7b01aa45aa2942d78fa94a3e064458a23e9791fac0f4d96bdc67ed7cb21b1a05a13089976ff9ae27cf02781a4b374a79168630fbab8d057eb008a7c7829b5d3b7cb3f715444cfa13340a18c3ed4a6f2e2d10612ff655c9686f8e29b9b84d8baf0fd5ed76169b78e62efa86e2d105452518a7a9d53167f590ae79fc847fec6045b21727d667cdc9ef4c0fd30377e17bc33cbbf1ebdda9edaa247b7d99cb7702a1f63c45f64b045c2846df8ffb2501fb8c7bce80948f11bc5091bfec63073651cb939210275829651badca1a4b3d3e5e1b8979326c1ee6246c258967ab58803eae8f917832feb4f3e2a04565c3c776db1b497e976a561d56fbc738eec166937c501b9ad22075a513dc4f6fb533340d4006ab5498715b1bd5c2f2b5c0b159a1944afa758d9118734b3e026b316d60c8c627a7a58e09b6bdbfe9ee58b3df32f36266d3c905de360344cb3bd8bd4309f5dd24c7f9a0e38775a7185f6979fb9a3ab1d483b29cfaaa77f147c9d1163a739d07333a067cf34c1ca539e0e9334eff481041371575a0903eed6d8c6b3ac52ae9ba97c773c375cbf8dcdb362824d5e92f4d0ddbf6359e5baceee078c06d3794ea0d35e32d192411d80930cfc927fe1c72fa81567080f9b89007211f1590452f886993d393e1e373faa673df8b9f8d2483233ce279c0153f583b0410da57983dd9c74cea1f1ed89777e68dc3a6b899c92606c9a2da639829468c760ff15e53fc3071fb7cbf5c0a9881c34b68eb5ec47c7417f26cbfd0fbe65a722a3ab0a0b5903cd5132bc7d783f33c9dafffd1c4f6243a6c215f470ce918ed7ee2742a77cd2eebb52886a310dff99199ba90904ec6393a984871908dec2306a6c9aae99f89ae9523a1ec4c877638421fbb451a3d3ba514cbec20c2d829292b07d2550ab2efbaf6b08c890c278c1e5b02cd34794356488a7bd4ed935433fca3d51d96aafe7259562fa585ffaf8ba4c600004145819f9ff22374c0d575439447c4b8ccdcd5fd7b4cf80efabd7d42f77bbeb5f9ba77ef69af99e7f3be29c0d66cfcf69b141766975d9dc69ef4068a52ca452c268d1410f6ceabfcd176f03f493fe4136a384998e3a188332681c9f5a5e5e32eab8b860beeafebcfda55d11c4076d284ce281701e54e5b510e2d78366355d1e151fa00dfa78e6d5230ca2186493bb87616e616fdc329e8cbe09a8763c536cde893dbc60fdcd222e8d6c11fda7d2a0f9e48da76c64a0f9b4bdc5be52e0d41b52fb0dff33d30fadbd329270cda400cfcd4483af3eda227672276b149e25f8253db629995623cd3b1205d6525a875f33151e1071b9de9249f6b59227123c9897e4e8932719504faddb58624364d404c3373ef98f699f897a79757290734e95dfac9681f2aa29da96980d000ddd9b0efb14b232512a850afc1b162054196f853b02c02fa2920eff35bcd413acbb436c659aeccb17d72f9e395a97c2cd1e6ca6b0c5fdb56657e9c40c5461c6ab42e03e0d2abb648c406828da39d43577ce172d5603a4f3cef643a0cc7ae04b7af2f33877c58875225569bb038bdb261aeb98e9d138e82ca5fe514b8f12d746fe55b201503fd38f6f9016cb52e73df29d66ee98ef71c72258792837a55ffb14749079151e8bb67483ff2a047590fbf43135e73b454cc6b3e266d1b4e96d530785da6d6447fb9e09e70b7e9476cc3bde12d92ade883cde68ad5bd2558c67605e90df61106a4378420bfd4cbf101705080db73d840483867ec8007ab0ccb22362ed6a78e83a5a12f88e57b4916ca5877355be55ccc1b17d99677ba4a07acba89c7ae6f4405abfd1e4690d4fb9d8f17bef5dbd45bf4a75113dad0e392b5ec8550edb751db397079a952d854898b8309f0ee471575fde8434e7e30feada8a1faeeec478f1738fef6433a882491503890f7cc55d3a1a0777366b40c231f7b6ed68e4ab7443ca0d52705b1226b5ecc130ce4083c20ae91a799277a234ba55f0a9ce2735611e2a2c064addb20dbb628ecb096bc4f2eaab8ee5f85212c68ed50f362504f6e45b972f5b3a9bb592069d660c375012d691f36919db26e84ce9de048d93facbe766ae6824542d5c9359e1b1241ecf946f3ce58904e528af4550afe1646c734cf0631a5ad8a1a8dc418480865693766d008b439e6a84336efb88c362e7b5288bf7c03f2d5df260895f043ae9869b4a74f95b3024b03814227b6c79bb06ce9f1af453eac159b9154c3ef93e4b1b084e0c03ea3a65b38808932c549b49f3d94a746872de6e56988fba767128a516b04f87770bb7f3ab51e0f5bc71e38b9fb8ca3eadd74ff40dc121ac8f4399a5373929fde10493efaeeb1a7f8f657163f15ec36fa8b5648be5160c3f6cbd825ec3a88aff0943ddfa9eb6b6b11c9ec1c41b9f3465a0145d76319f3588fccd1ab0dffdd38a245bedfa62077fba704541f78535e4fe1babb52fe374138881774195258f761cec06eb259d5a13f62fd6876b9129dcd96f4d40cf7067556a42c02fd6584818e19fc14a47e4bd5aaa04d580ee60b7ff39a702f4cddb554636639ae482c9eaaefc4a88bc4bc50f9060b382d9420ffa6afe04be19f88e99ff90184f93fb37fdade981b5ae11d6adc1d29c3210826dd9a21abcb733ccf2bdff4a3cb9c9d960ce72809737d4e0de8aaf9656ac945591821636be5792df6902c1346d963e396e82ebca4728c730649825b11f94c3877a8b9cdbf4c11e3d4ba91db16a2bf0ad0669778c5052f60bbe9bfba69cbe9d19dc6aa36cd47ee232ff0253363761aeefc2a5f31b2b81300df76018b31161a973c84483055f25cc726706bfafe7a8f4ae0890d7fe403c3362b58e427fc4ef3d1f0ed9db2798a633817d045581d8422921889db7afa152b86a3ca01e1adb741fecc40c800e03c86e4cc3f54ee80f458ba055865d864406d628d4482d8ad6550e5bcd51b5d8652ece6ee8f4a4deea0b2e712f4f2f492c10f59a602a1eacd987d4f3ed0cb0d414309ddea382cd92d4cd00f83bd0bfdb1e0a16a7b9040cabbc11748c7b5cf1cc5ed465244551a08c7676509dde9356823376fa380dcaf78c2496eb621df058ceb8429658d7972d15e8d88b988100334c2a1d20f0eef7baad080ff87ffd70baada2223984b16476a05bba50aedb5921c712074a6d501db411c5f1d5bc303fab76506a0d21144a7a7ae8782ed581706bf3245d1cd57347a38f331d28f697d3dd3e85e83ac302b54a368ee4a24c8560697aa4a9e7c3d13f88ca1f24a906f20702862db77d57ba4e6596b0d48874caa390051bdd79955a07421ac1d0d5eb6889a5024c9eb0e4303b8fa8056db38fc63b590b81c879a8cae226894cbb9a3cbbfd7285d5be30d1fe2288b2e4d245ed781ea50126223433bf835566b26939e84b43725241c6b8e2f15b1d87a80ccb786c1eb544e169dbec9702227b7ce8a1ed8b68599ff0aa0119b4e4154fc838abf4ddae0df0621ac535cb57b9aee9fbbc4325123ca2e860d62169adcbfdf627e7e32023454d271c18a589dda429c17ed1e9aadcce2111fd907b938c00a9d0ba76c83bbf1e73e63d95707bc96c9c4b5a0fcfc7449f7f44992a533e08fe11e9ac715a67a8520b99935c25a29ff5883e889a44c2ef3b6ed641cbf04c7901c1e25adffd0b0ce6bc5075168e8aaf15a4b4d43e42d9ec5bc16052d71c38e0b3a7e942ec13c699ffcdfbbf8ec6624565ac42b257702e56def74d4501e61f318e23e030ad854033754595f9647508eaa0b01c78f783caacbf18aa87f9d419bbe11f21cd618628372394cc043881455f49d275f46230c08cf2f4ae3a848b1ac198a657c15cb4ee5c3c006d22ccefd06c601942cffcb763aa6b67f4b8aec54a4ea6e66e8b2897ec637c262622e117b7ceaca60759c14f12b8741582bce5cfa0f7d86743e0da364170d9677cf448d7fa3590256ea01dcf11905726b88b15056236d8b996a28bce21fab5aa79bc62e1b61759bfc80d044910d3de3cf7b107d15115ec7285ec4bf3622a33fea896ddeb66c1d8adf4af7bec7d637105aac13d1ecf13708beab130be604f9a2ba28676ea45e1fbc00262fb5ad791ecd3454d807018f14580d9fddc75328df5c2d4fefd13f810716011515ed787f0ab85e36152f0f0699ec0eaffc4fb61e668dc42a607e62b88c3b51bc352694ef931865ac6bd2d62e9a126868963e10f4ca7a2b879b1e462578ac723f9410699f17b25c13f14a37f1de5a08681a0c22943514e36e6fef6dc7f6244af34a3b7565dafdf951ffdba646a16fdef01eddf01278816d357427738ffb9951ed28b8c036c536d0028cbf2f9147e2807b984909cdfce613a7678d77f3cf488280cb79e6b36607a6d419334c7c3b3aa19c621eaa4da59a1257a43ef73245f5f5833e03e83296707755381a98c567251c18327517c24c6e048b1319210f552b9281d95cdc532cfe29ac3b1e53a03b7683bf78e95a9523d063ae14b8ad8a3fff26ea904be2ea6a1d0ae27afe59eefc68be8af098a6308b9f48996e196a501ead76f8302904dd984c65bd90a2287e2144fb2f95b12ebe693cf1dadf3020a37ec8604da968bb1abf61edc3209a3897c2f501e974e6659b2d962aa7f765640a6f1ff541a714e9609261caff65ffedc9e02b7b4092ed6913b19c27eca0ecfa078dbdc5c1aa45c5c0182ac64317ba2f3cd08895e4d451cb781dbe8e0e5863923e05aad7a28fffe8bed937b86ecd98707e963f4c9de740419aaf779a467c2dcc91f5ce61a9c3efa1db6e5b9fe291320ac8bb08c5fee185a9696102254cd1c2846122ab69193b8317ff690c8107204aae1463ea8750dc9f6a3c17653c8bf09e43b99e5a31976898b0991a3227dd04c63e51e21494824e09310dc78632438b1af7ef31b194fd57f1788cd8747e8373a678a8e24a65e1ec77518e951039763738fa331740d06336675452414ca07e20a0d682a255e983097f3ba21802166785f672c5b2d92e1785b6b4981da6d5493ebfd9227fc97533aab03869f0f93f6d95cb276be6a804a5a3a03d17c2f1b9838957bea06b5a85e531fb26977ee664262347abc5cf09badb3f07885e16bb7078ac682399576c1a9d17dc7ad4d3a806e3de40d9bff4ca31dfe0c27c94b5da20693a6c6d36caed4fd12f075efba3a514a9718522b9a3e3b98b1b525bb0d2b3723ebf90e547c09844330a48df5ac3b094d6aa71db616fe67dc08ea7e83e291f9ea20f83556cdfe4b91705eecc4cc3ac5ac8819ce5234a4a780c61d8430a561444c8f1424797dc6197d4e7adc323c1050815dc9e6060f88824182a1d25d396029930317bbaf33b69317cc9317c689a90b4b6c3b10e7e6032f3ce8db91f7ddfdc3a51b5c020410a5454ab3c5ba3a6e55daf3669d091f658da5208550ccf215758c7d9e3fbdc90c017a6e71c0b6cd2d15904e8cd350f972d02a2565b402e53eb64cb5334871924c9fb008f73de8b136b36129c80a986c2d1e120e4aa988d7fdbd67aa04f7df8f5965db71b38383e5b93ecd5563eac558fc308f046355fe8d1873ee4b557287af1660bbad3c42ab90c0fd8379f039645d6d0773a68ffd46fcbb5f29354864e5730284aac4af17fc59cdcc5d4f69a552e2eca2813d554a69059000daf4dfe0793200977913152f7576b670f687422dcf24fe0c883f49533744338cc74326894f7e34a7fda88ac67eea9eb69b3dd1cac8c078075e865389ede7a5b0302b1d7ab1dffeb4b71dc84db4cd3aa6dfdeeb90040f80ce425b607dc81dd4f1698ab2304f78811bcbd56ca4c2726991e862bf39372e741b88c90aa22f55537658934ba63bb62fd31971c3c1429efb83561fec800bb9f54667296ba25869fd3ade5acb1bf7cabaa8b22c1f04951b7c5c0434d3bc7a054882aa9f22e7eeac8822049b9034c1ca0f8330613fc870cc63f80077863b5aa41b47b563cfd6cb87163977582dbbb6a26f10c33792d51cbb50119d82bb3f210ef29d8bf18a05463a766fdf3a9b0f1317a1d4b6e66ca01f6331ff5bcd1821cf35df7e1288e3d72ce727ded3ad0ffee267e28737607c70aa57f72dc4aff85027dd2095b9408dbc0b3eb9f024f8075ef328c8b2a49914b29cb753be1edfab395f3badbadf397ea352a523afedf0298deb013e7baa27ec1efa27a320f32796c1317a4a356324a867749aa1e081199275c8b324d48e9c2839bcdb599f98ef7e232872460ab76f824da5a9e17ba287eda3ace8f83699a3ca49f912812c7d1841bda38bfcc06e148a9339fcdf5d47f7ad712d65324081b40bee1df457c07ba281a5f48213a3e8ab4c5813b23f5801af73f9fc376986ec85bc6c21e5f33fde307736e34fd8afb38c102b0420377f2b825d0df980d97ded8d1e8e1fdf8cd963f3b6db8c5f102e5590321c1e1eeffc18519775d210d0b384c47aef3557f5d1a082632dd4192362fb3ed2a25ad4210e90ad4270318987a4eabbeb747d1c52bc308db8e917dd765480fceaca01ae79554a455be3e9376191685928444832a002c08412887f12ff15446292735484d3c938666a347163f855952c05afde5c7b322303431c67d14966e5c1eda6bee425b1e75b52760e184252081993450621fb65ef0fbd4e0549ac5aa54c4caa6b14169835976a453961fd28271211c813f54df9aa9e1fccb6067dab0567ad0cd0a540b9b29ac72410aad5aa9887697d20622b17dd10e7267ef1e264039f1f863eb56220da771fbb7c05ec7a57b1b43db827d88a411800f77b0d19c6a25e9572d1b8a22c3839043ac4aee01acbf20d237e30bf28a1717e04ba57370ca7add62ee3a66e8a8261bf03d6978c55b31e4fa0d61624ed764e9e873ec5097620a5e071c06908d824b8d4917d3000bff408867a6ef997788657be516240a5f981967b98799fda4fb81f81a74044a7288604eaa84d8b2dabf2af50b7a17d8f4df6ddf06efdcbb45796fd7ba5f60e03fcac007119d3f672102d92c1aa3af351d5b147ed20b4441c2081cc3a750f66dbff850d05cdac64493f065bd762790c2d1b92ccb7c2e2f5a42573c05a693311f79c3d22551559be7b8bd4c5606bfe9f4d0e6eaa532093e86b38e1d3ecf5877ee02ea2afa478ca44d0a3868d8b9237f08e23e4df2a6fba9a0f35d46307ef1777f80e1cbf5925a6cdb413965e8c85010a9b08b5e9c1417b3ba7243c1da1fe949a7513364451e4089415602559946f67bbee3d6b6e9e66b35a3c81d8357b43fdca4b99b6844b7db9bb708e3d5b195460dc9c277410e9c1ebadef76d16198abee591996840f589a05188c49888e78d77b43e4b4b04e66a14bd76c28129bd83bd82b9c845f0e0df532257ccbf6a2199dfd42518feb9a67e1c26dbf626b434887e5ed596191abc9ca00a72449a546156ceb18e0ca7845daa6ef92f8c1650831cb9b04b4554ecbd0fb7747843f25986192e3bb14a78df5045241bb16e71b1bd76f4b64c8e3e4b882b528ff39a11f09c466b317f25220b60417b174ff44cf3211fbbbe29995a928f7606e07399eb85a9e184b8ca20ed9a70503f70cfacbddfad5c4f24d58f37c9c1d7e19337f2528e6c83e2bd8adebaa9034958513b58bf43250a991f450f226725defefcbbcccf21f30db48e0304a7ea60bd7606fe17b929874f10bdc2ebd95d23faca0f469bb91d9d4369ef9b649159a9fbdfb326afeeb6b71d3f08385fbc49633034f1264684c36b78c78c9afc58390c81abf7b6c6a14673b3535c3643ef414814d57e6141118b2bd8e5d89dfd2690958e624b0ab51986ca5891bde40e598762a3690f33e19f3851539632c1df0ec874c972527297aed8063a03e3df65a2284789acf8d0610128a9b9fdc6940939036f1c06c54afdb4b30a72ef94c492e89849b2ce6a585547cf87c82bc1e0713e7745decd69ba0336a15f699a7614991a43b5b96fa3ca15f7ff49ba44a9187d94fcfe7f5a6451528c0e150aabff8484e5b06ffacad688bd6dc618a3cbe021249b94da8d33d44fd1b498b0588adfe7da16caecf738e1d951c4586b6de564c4af90397e5ec6675ecbb0fb91a967d8e947b846c5072ff009610f5c4e2cec3cd7bfe848a22e7113b92d12c7bd919b29f5502cf16eefd03534d726709e7efb24b67bea30ecf902fdbefbc17e67b0d81b2844eaa917f84f01ffb2b8f4c81012cf6ccc5fdc595b6d3358e89e872b53a13b3d4479233f166b4eb4eaf6f170c85941f73ac28c803dff00f7baaa5e2e6b88ace3a78913896516dd1a14d3d2764aa24f7126ea1faa1900f1a07d619606a6784519dbf3bce4df59a5ceb1d47d26a0b09ab216e9a029cc87b7b462ad06ffbd37dda90cf8353758d2a6367ad5f1c399076c9bec0b797cfbb596f7e2d5b69f418ef10e92ed252362d3def656b7466bc6e0a4c628fb9453c66f7be3845d9a6e417616c7fdd31924bed9c28af60a7478587eba1f71e76b240744144e88d4a783730026077ae89dd80d8d4739bb75b99014104cdaaa590b75d657059103486a4169291fd2ea1e18e446b61439018ed558d7045bf0702a0bac1dd2735079f1e04b065024afbbc7f83c91aa2a287e2f38b1f3c1b86eaf6516f971118c14bded051427ee65a23dd8df31bd069ec6ed9fb2049b11898d2d3168685a5a84ba6bd5739849a1d3a5cf35f688e3ade76b87c3b70d1e293a6484fb2b7aaef76fba524219afb1cee19d223fce3cc8ab591a44835d785b9fe028d8a31bd3c9da68ca3e57c3bd462ccbb5024b8aaf8ca4e5d683756c3fc78c147209f7ae30d129ba6474df8bc3f4415924d78d21a881ce40ee88308b7aa6c15610da494caed1ef18cafa686165b04027ae8bc33afd23bec6098195234bb8f6ee6fd330ce4aaa5939c7c30e1eae62d1ea6454bae7bf127c54a57f6a2c135eb47ab63b4677023c5a03a7e3c15b21bbf6369eb47661263235176015984a5eb40a4f8f7b1983d48b597a14a87779103591c670af88bea9887982a98a5940391feedd8d3b3974231577fc5ab43fcdb025ba7bb032aa229ba6b03b31dcee3e62410caf8727bff59c4a0ec0612af84ff7a1e3bc73a94ec190df777d19002e0dd0598d0771fa887d701681b39cefee406560ffe70d5b3fcacc972b2b50743a7c4554c5d23c620f04c9e7459719448a02b3b769ff8e3fba9a048a0dc035b48922428a12a99f37a127443c4f13229a5155e081118cdbcca60e172ac37805500f1f2af6ca196ac55974b9db594d31d71d849d6435da27b90f1fde536b499c60b911c0ca6ae04ea1f2fc037567b3ec2051d8ee9397c2353248f5e07c8438529805aab782f87a997d50834a1c842cb0d7780ead2198dbf77fb8c55c93df4a364213311997e89cabed082e9de036b8209c238d4e9cd4bf5ff17cc1b6ab8f95cc7fa18752e19fa270a123fc2040d41a34e2d296d4cc9adc9c2128101c693c97eec1a117e1f6fc3decd5b97598db0cbc5183b8f64842e7777275d42556c0390fa84398888716cfff0387eaccb2b0689277e6ca85bbf69eb8fadbc9d329518869a1d43e276b934b69f970a92e603282400daa073c6f957fe9fca90853d2d796096047063e95ccb1d8194b68022f977d05276ed850fbf3eda87fb091f6864ca3d7e46c1f91b490c0361961a91f049e1d6e6b97efff89783f1e9235ac000821238766fc6d20efd3e2c95fea742815ee4accceccec1b25a65f1cabc9ab6c17d27e7a040658b1b17911f052f64d8b641b73eb54264e8c492ca5effc9514b1f9c3a0dad9f60a2b9fae596b65aa5a959bf6133203071b7d09354e93d854fb82d04637a8c407b10feb8228b98e4f84f326874d227d3badbc132047b0fecb1b5b10b1a344ba16ebca2b7dace65996b23408a1e2b3f3ba2febae5677735eed3abdba25821a3f91a2de17edfb37cd7b98bef7783e6bccf83ca282873b130ac94f0ce8bebac288408a2a998d21e02ed8cb7667d51703988b1ee2a32c260b93c13909516b75a2bb16e3fd378543c1b55bfd836adc34597190e03bc1342df016610051d2702e2a8b2ad8b98ebb0d317a2f9b7e7200f43dc25ae58855879e2b2c84f5be2a151f3979cef8e202897e8f4a8a5fe1b8a6245397880714687079e65a3e5abdce110f1efb7283f5133140499c11de0e9935210f7bc28e1761a4d697cabe336ba0b6d29ed21fef65349ad9d573ad1b436c3a61e7604e9a0659d95e2af6cb397400f7244bb85c477fc9a0b102c76bf9330327ed9dfd36c65ea3d9f836f1b1d8b2afbfe2130d30074c141f720c9865a396a041ec10af5d1736fdaf630ae1a9c86badf4e4827ed94e205fbd1fe9b9a8fa53b525730323ea45cd2629573303b6bd7c33393181985edaf032aa14960adbf867a7bdb0b15a7529fe59f99c814d0df86f44c5615b0ec5e4b784693169da12abf9b395ecb56094241727478cd8e4fd2fe2d3f2d578996c8f58087d983fbd318aa33579aa7b3b1ceac1ff2be1c0f97fde0eea06c3a08d4634a0a9ecbf1685e1c52014b2b2d418efbaf2e636c5531b8e2ace666dee29c5300d7e9a49116bfc7761204b3c1a5e9f8944ce052a288bf4e1eef63ec586517b360dc42a6d6e4f2814eb8954f544a413aa212d8bc91aea05281fc78985d5288bfd041f846bc43fada26dda50c94a5ad5ae70ca7e35b442660f4a6fdfdb50ac205a2d2b03ea59044ff7144b7226332293b156610a38d7fd40f83d1831885ee55fdf3868f2c6489144cc8973d97fb512898bcb566f0c4b5a04f12e66dbc667bc22f60ee32ae3c51e280393bac9071bdcd5b8ccd1e9eb34d2beb07908a87d936ca4d2067128fbbca1a4b9d3ecc8461eb08ed9efd9275dbb578b6340df6c46ef0d29e948adca6cfa4a3f0e68fe1d0f3c635137cedf747fb3f6b101166b714ab05290101a5a1ed7c12b6b4f2c54fab0900cc98a893b1facf85ff3497a46d22d56a1afbf270c13cb63a0d8021ea9fd3643634626fbb1b187c1877fd3939f89e26ba95fd02caae8a30f45a3a708e9bc1514840c9e133e28a71164192cb315f8814edcadb39e0c133942bcb7920e89a8ba297a583ce3e286ed51cc7aea3b6e48472cfc3db87a8f0483e9e66292c0b9b05bcb988543f4014b7cc922e7579a55d3f6e08a7813bda17a2ffd23f4cdfb05284a9034f912d539988647effff95375bfd539637569a97b2588394f3fb0cb531417d0596c0edf248eda7f185d6fab3203cff721304a020b844a93fddc987b674e49449484aebcef3ab0d4567faccb2ba412c928ecdb027ee353acd574e059471dee857d790e83b06a2ca7b25f4737964e60bf3dc6a60a022189636e383a8d2c23568309568a2b518c4e95bda9a72a10302003b791d5116b603914968d2047c78a8ff0c7263681791063c29db546233cf9105aeac3f7c3efb2a38054620038cf019022ed705a3285c6fe9a1f15b0885d6b343de7fef8a2f840c2560468be4bbae430fa75165c62574eb88e7b0b3f683cdfe6b1049e375f819b139dc69623b35d264bd75a68ca8223849163d9611c3b7855a4f8c89f2b85db49266342a187b822972279b38df9b6d5a281a2b414ad6698175fd51f7b7a009dc70319fbadef87cb970d5c2efee7c834c7fa52e88750db53824e896b051152a3e262eccf8d39eed50f41f76b129358f9332fbac3d624640b3220cc67c4423239bf53892d2777eb16717811c16c63a54a06c0a5b6690d9dbdb3ac94348ee734576634da9794a52f7552dcd7f48be11e8d6af32bd23c0e2957e0d4d0573cba7215d13a6b0b60c9ddfc0e99870dd31a5f06b5051b5caab8d6ce30c1cb94134343765458e6b3749f6b7184c6564ced4ebf4f5a7dfa906672306cb5423819a5be104b5031f6f4610a5ca15a5ecac713c36cc3b57216eacb7c4b64bf9cad9195f5477f3c5b935e44d73f48a557efd754767de722f4c08499b74dbb6dbefa4fd46da27671c68d934e8ce8e0d6fffeab8bebda3dd1ee4157db9a1de5994d1691cbf71429369c219c45a7084ddd2335b41a288f81767e93d33f716aa8741ef71ef55abb53e3a8d393a32ee0edf4e58de2a786daa2264c7881f75eb8b9a91590052ef224e1c8541fcf9b77ba8f88a16234cac97756e4be58487f94827a1ac436b90c645faa8399bb3d715caee996c7a4bfa2b850459cd748dcfa451b96fed0fdb0358fa6ff925aefbc62c037ee2764b65c8a885628ee0f710e45838b97d6124c2dc71684977b56664359344b01c868af85daabcec8e3d71a925e3e6da79e2b3ab19e0c526244398e35746daf8ce10917b1231cccb4e0bedd4b814a4a3abb7ad827cab3d8df1ea6ab58c0f103d5da1888afd8f307134c1a8830644f42dba05e44c71d071bbf72453c33e5a9795fb4091cf32ed8124a7be431f1341d6973db699905ef65c0bf81713043ce7c2fd4269f2abd2c73c5a7b6fd62ef8f052a302ea1ad53470d3df336f99b3a885ffe83597fb889a6f70d8ad8a7fc1064978aa9f30aad9d072fccc173e609ba6e0aeb417d4a4ddea493b8828c1275a34701f40383f5d7bc4566bc46aedb54c8e3c8cf5a7e5d20780fcec2c8cba98fc871a41ad1b41e5b824347519dba98b9be544c8be64e4bb90916285ba5c2295ddc1dc8aeb3c2147e1584ecc9d320ca52725a2758f7c7e2753ce44e5c00c5c853c7fffcd7421713e8256d2457ca375a05a4ba1f148d174f8163341146c8b29cf708f39f9960bb0fc53fab18000b18cc990136155ff01a4d5bf2cd1fcf22e5b87cde9d8271542a1aa3c878ca300004c82a3701419a943575519fbaa02cf49b6328372595d77937cf85b286eca0cf494a8bdcf4dca363bcefbd76cdc239e91bd05052390f9a2f60119e7bcee90977f59df53ae999e6d3fc8cf329365ac0898dac8ed1337f107e236c00a95ea90411b6efa0f99a778ad8321dda8bb5ae7955819f745074c416216f2d18f35d7ae8d930a907145cb0e2b7909bdc5789b268935afdd089227b6b214465350423a14ea7d85cc1e025ffaaec445715b5bee8cc8fde7d025436751de002c8c941ffc539e17329020c37897b7f98cc6afafbe200e88b6064fcb4631bef4c081b18a32aafec766f5c40d66720de5d16e619dd7f6bd36acd2d726b47784cd37ca0d6afc697007cef9efeb85613fc92f53fde44b9651f78d546da46292ecdc49b969569c823328a4133c3f03e1b639d79b1fd0da1e01e5d43c9978a240f703e2f88edd6f384095c6ec71de37d057c4047ad46674ae1d0cd6ac502fea27b15ac3292f999633a02628e04f82b17520df51e1871a029482494c53a9e95ad38cd08b1016ff284c86f2d1f666b1bec8b52d0ba67f89a70ec80f1b425b2fc38dfd462db5377c9e5ae794b84ac840e56f885293cbb55e11f27663668174c603b3b9fa417e257627e62d9968a05835f424c44d287bfbc6c95ab3d409d69fd575060ddc95e4ebbd6d27bc62427503fd4e4ba8fd0bb0f3b4bcaf972783595f895fc12cb5ef3f9ba510481691b6eeb4bdceb3b13f7bab2c90d9c429aa2e4b2803e49487fe4770303d3aebfc8feafa7822afd1729b727f3bcae5ad7dc7ba91876c825292315d2b25f03f5db242a400a80d0917956210bd4c24bbc480ceb41a9f63e199850b3be3565af3a9ffd245a8eb04a9de3f7113e41e26dbbeecc9f5dc3275ec118e8ea9cc5d7e51e73616a89d27100626b22aa065736f5a6f832e65d1d877d7a8c857b835e41933bac9ba42116577774fb04db0f22a8ef00f875bc14cbd921dd31bd5f380335bfbeaad25e1e298f4c23707715f17abb3cce2c318bbc84bb2b14d82eee02cbae9108d9d6f494366d8ebf2315dfbe50ff2f080e87aa86dd663c8560dc6614b390d51c38ed430e1a1990a0a86c52e1cf12f0a50f3e9911e1e704191d47aa861cea94dd2be83776f208afb1ba05f63a3f019539c8a4a9354cc3f6368a550baa9ec25c527c54b1794ba0f45e5c39f2bf5b9236d9fe1ad0774275f542b1cd35ff4cb7d8ca01796072fa7fdc0c1ac7b19e604c4e364a599d6a23253506ae437b4667c74266b360b5cf55b91704de2ac628f618c90f3df367f77471315dc950e45106a248ce434b0e4540d49bb07662776ea1ed5df6c26452cf67bbf360a02516c749b3052a41fb0931e17cd34dc80f2b8951f18e7c2293a73626212cc39d18e62723ab95b195eeebcd1ee512ac80a86289522b2d9380d92b017127335d693b55e5aecb7f70bf0afafba30a0d758ad5615eea3060327fac645f33d0529578503b462f1333d5e0f43c47ae3ac2c40acde57d0dbbb9726481e5e2df33e6ccb4db0302edeb1fe8bec44a78c12c1d017c4be2629a42a1df0421d3944634d9be089c7846fd580de410c1880d0b130f21d435efafac526a8f655fe05162ea7e94db8e3696ceace5ebbc3ce80899317f55d9235cd6bd22590fb77d493cd0ee43b631ae89c8e1689790a5292d6e2fd7b7ea25834133edb1171ed3606d028fa9c6889ae8d59df1a39250a1ceb64bbb12d1702ad08fce413be3ff445d38c85251db75506adbb98733c6ef0ffe92cf74c7f6d3d87d5e54cf4401c5e4a265344f27d8b032a64f18baa11b4668b156772b1f627b2c2eab0c996c35b1df7b3ae0fa70b920fa13b8abaa63f54dcb9d72e3723bd5b83488877d9f8c328a8ee3c6a308d5ba89d9aa098b4b02e8ddc5b4779e928c9f45395fd5cc3853e3244595f8b53d20db7864ca20c0321eb09c52c2b02dab6ab628d321e4cbafb47daeb3cbb8b966804c954a1568db521b4d83ad8ac1fc538ee67af147aa5dd32e382e12a245e4a1fed1e20af202b9b30e0e4afda900a8c503a2a6cb6d6ca5fa4dc470d941e7f9c887baa369470ab84fb6d13bcde663c75784df729ac36f1c6eec39415aabd3853300c1ac1a7b13676a16bdde17cd0298a91cd2bbb0a502996d3f0d021e426cce639d9e9020f828ca0854521738459bd9e375c18e69202e0b2b9553a1fc091f14f1f11a7a206a06fc2131a8fc9539421ec5ebacbbaefd85f7eee22387298f0c73fd906ac92966344fcf2267858cbad80dbeaa54d948d8cd992c0f2852a58f6aa12b282d6b4a2a7334f596c16800abf1b95ff4a6671b2dfd98eb92af7382365613d4e11ad35116b7276806e818dc86e2aa8a931c645e761476ebad0bc9ab226f6c633c80fd6b5672ae4614df4a541e6ca4475a01e8b3a5fe19ad88806c1929eb788acb2dbbabdb65d368e7193e40a380b3c2490677eb1c78be97aa0416af8e7a30a782feb9c57c8f8a3113472f8a08ca5dc39c219ab56824a717cfaa2e5119ef44a62118501b80c7344138d25db1e80014d2a1d66cbc52f5cd7a82de93034a5e8e9a17130cfc95b51a0f27626de0d8d9621b72a27d18ed7f70ca1d0a4e526906e7c38cd0e959469da248c4b314ab41ddeeb4247a4acf9c716de1e5ee220645ee5561f5af8fba63bc23d0bf58c79721d72746bd1e109c4e0e1f70922159be248cfb8b2b386b4d1bd16bb4a8a330762485fbe364b519189e43ac2dc56ec3c62f91ae1ff38ff38b0ee009cb20813310e74bc024012d5013ece11044f4a18758d89560a2a6f95c7a74298870de1281ba0ce7253048ef19d74aa7c9436c3e3fcdec6f305b697d724d24c410e3acee4b6bd5ba35dfc02cefd996502825070937ba676a7e7cc9167261e7bba65bd885812a9273bfa40d925bbda7b92ed68b17212e670d14211102507a715fa5206fa02bdd82f66a91a6fbeae5e03dc93f5e099bd1cbbf231ac60f1d0f621ac092a0fd045062ee0e0bcfb6ce28cc2df1146f6d040b1b8ff34e9d3a09d101284a84530bbd3dbf35bee7f03af19fde394281998d19630843a03451ff639add8f0bfeb4c860c8374bedeaca50ea9a03c994acb14c564a68f1f81eb8a26dd12c52cc41e03c2bddd6ffb516d243fa4b80be6c2a76581209cc2674d813f806e28937b9052f3afe670104bfbb2a678851118c93f8ab0c269e0148e7c16d75d759230a98cde802591b5fc2f483ef953b5c228ccbcc097669d3bf50fdee95f94d2b60bc967357d8ec16177896f0f30d3688fc62dcad702703c52a9809918d3927777fb9f67d1b83582bcc67b084af25080458e5316fc0fd8e5035403e201dc0b7c9656248e2b8ce6294373aed36291036c9a7871c6af1414ae9c4ab0b7be060b6dd3e71941859a33d54a768a0e963a44b98178638df200ff45fd08dda77a141c4be78bf831c072ba5ccb6a22905e677f24e1b4d8149f6bfe609cf7ea2b13278bdbae1fd2bb746f69b4d92077c7c4f1a7e4dd957209a0904439f06a0abeaf0f4d4c7575fa528795e37358dd56ba258c3bb586403107f0c4f0b2d8c638c69043fd6ea45e425737c80af98bb20dae12e0390d06963dd51ab84b9901a692853078d153a37c10374657de40ea9bb0f7876df1d77bfb491b3f492c17974e7668765419f6f615a0e41cf1c0f1c162307bd7550470852c31ca93afd9a8bfdfa27636933bd0e23b96e7b84571cc648762371fc23d1e334da303a4fadc53bace6b5f5d3188b3b90c975a4b4e183d2d15d89e23f2fb0bce8d3511852e46eaa34db82bca4d0f8cfca9f3149121bee0a923457e564ac4a0ae31f2b37961e13e91f7d0623e4017735a643fc3155174f3e098d9300530235b12d2256cd670ce34c553766f324869c180d04c49d0c3a4245e4a1d4a3824921cc3f3a40ea46e58751bc2bc33bb60b95e26d005f73d11c57cadafd29ca243268cad5704716614a8d81221200dbb24fcefed82cceb60ad0d1f434f6bc5d582d0a293b61ef7566187c82894229354ac4acf83ec6aa070c8e839605e3b327e0529e53d828f29a400745926f910c1cc0c377c21a5a8473d69ba8dd334033ba86160b3b57ad2e20f6fe437acaa5702d15bfe62383be8a92d1717fa2b19a83fbb28dfd99c1317953c2db84d6a8e74d3e1ebefa89884e482e42849983bc3c16bd80830e27bd8da85f6e0781e36a65856ec72464652b2b876d526c90b7fde5b063328144d81e71352369e52b7219b9f2a48d01be2b9bbcf876ef51080598ebbdc40d7470ccf7f80abbb3e772c437b5220d50327e181b2ad97d40de12a5cf811c3b34f3fa7af5488c03d845332591213996d5ee5907f4cbf558ce13166bc5cbc3d3ed0a1d5a702d49fb6e0bbe8286fdb289960bd2f49287cdc6a35eaf625892f78c32bac107eab0e27937d96a878b160ab39d4a64ba5f63520acbf55d40d7af3eb9aaef4a69c61a20b48349439252e5829f3f0159ac3a247fbe7610c4275b5f0b9631d5ea5af0d9027a0366415137501813a81604d94795162ae74f031258d83172a2cf94a57e239d296eae15cef7e5117a7e704a4ae9da752c64e89292e12433fd4501d82ca166c2d36765dce5bb9d73777852398cf9f393c517a51804e3b39b1cbb399e98ae8c3d7c67f35e1d275b4e60bfcadfd877bdf5ad3cdece06d8bbbadaf18ad98ff85625fd753bab8393c2839892f8883b2a0c7617af6d04bf29167cfa20580eb55e175e4e350ac277e467929c4a9fd68d1ee50df113116e3c6c1479c623d50aaaf31860454bfeac0726f92e665dca15c4520c3929aa89c8e38e6da88e8f8ab5e3b6292453db63af3a07e5ce5df36cf438f79a3f75451b7089de176cf70f64c357494d885ddecff734db541060cb9d3dbbac5ce6d4607170b14cf037c0198fbb882e07950c3b4821e2616d855b46ea9dfbea1c11917c9dee4a4f314d42cb058a125fb9ba90db899d58bbae6540ae6e0f08acf6a12fd9b468cad52d337b969d6c214eb4af8f48e23b96ffdba3370f98b08e2240074de658d8c1e5464de47e2b20ba9b39977402aeab6c2548360076cdb8bdccbfb95ac21f8f4e3fcda147720e8becec2938ea9d77abf1c321f5184f7d477a7d4d5de851766f005c5782d068cb06a86a2c4778ac77046015a5c66e8e755c2c68cff941ed05f5dc60b41215996f5e58ebfc7775f24fe17ec3bb39fd45960a0826abffd32c446de2b5d25f81b7d6bafb9b4ddf5341a398c9c1f13d65125ed95984233cb7b969fef0f0e48657dad0a2725a84307cab1352deb574e7c3f70982b178d9bf23419e5c024b681b87f7c65ef839ffd01ebff274039f764e6f927e1bf4435065d23d6fbad30522ced613f3ea071c1fe15694b738d123f8557819e9889a78e83a99bc357dfeabe0e2b44bf7732ba93ce0fa12b041be7a15cf2be21fc687e882f8cc6ff1e00a12b9e7e737b434e8e81bb41d886098128f61639bdfd27b1fc703b287ccd6cf67ad1817970a98c963ec857bab86a8268f583fe3109fe5f2b94678a070c0fee912375edab755e57146d8be450435b8929598f2400a542fb2e277a3a354c6c27778593d8431d38ba7a85b9cae6af00be8db87549b26264c49e9f6c462dca3bb9490c072c4722f8355f346833865de614b423e9982475f086648c97c1855b1b7d59fe18b5bc79edab512db87135861b603e66860f2c532d2807db0c51c90cf3c0e1ab68ef22cd6108a4aa782271e3e33868168b2c84664edc508de42a72e531d873d29c9ea5a3b0311873e781108cb3b2a366179ffd7f7fde39915df0356022cc97499b55b5a4b671527abf1268e8afd144f1ac8861c200903c01fb5d0e3301c4e9800a38f1f0c0d2d7aef16579e077fb222a6d1d87a060f7f796cb34dde39eb285b7a1f206e03f4404639be29b4e935b24d4bca42963323aeda4af4a735791b470edd72b33c899b7d176e1440dd3eec9840efde4d9916b2935f3f3b14f4763067ad233698e23dad81b82458396aaf8900e87f80cb259a2452c12c79e4c52ce7b31130b7b24075aeaa2b5a7523559bf7bfcd1505bdf6ccab8fd9274fc5bc9ef866ffb261cd29d06381cb0f0049b360979b252f90583ff591b90bcdfa2dbbb9c6f69a50b7ac6eebe4d1095bab079520d5813401558cc7b152b553cd421dd0ce88086b35d8b4e09947769b6617787a72e616fa4847f46d552487aa6e1166be9a07c0ad47390db5e2aae4e0e90b2ff4d6df69a6ebd200eea5dc20202bbcf05b257100a3a4f81d423b6175daf5801310f3b525663f387a9d33a7c96cd81c881b17a2ae84955a82b5269b22ee2632228d7984d70aef82c53045c0083a9b0e679ea7404864b58487580bd8f333ad9798cabce8b7135b71e7ea3242cb4bb4ea8d1ae7e7cd69293aad2e453159e89b16e6dce94e12786cd2ac2dff3542fa8d2d946645831639efe53635c7ef0e5e2eaa33d08ff0100b47ad0d458671b43b972cdd7822b096f28282750c4d5cd49cb45f9cc8e18d40824f49e613f5dbd358fd7ad9540f4229c493a0724262f8aef8c14fa5ae55ed40433c89d92e6698aebe59c86020427363820ee128c2c483a1d65cf4ec5d060d66113bd6f4f0a614861b0c1bb083316e3db2ef85341a6e722a018cfc60c71867205fc68b0e1b5aba84626799dcc7f53eeb81264fcfafae751a3a782bbc7b684da5dda2ac7b2b11cc976cf49f74036ad3b1445ceeb5119efeee80976153e26328d0eb062d17daa09169d96a9998b6e9f8c5e395c5bb49769b8dab181633b65a399e8cc8b8c0bdd1dbd6f4c56849f54528387733a86ea14eafda3a9ba590ce90425e3316e97a3a68286b729ce5eb6aeaf06b2bf24cbad28dbbe60d448b254d86c3704677d0bad65b053008450ae97445dc7380c241d2deaf5dc3f5157c875340b5dacf7838c794e16da05cb38531e8d963e60e55af21a5f7d9a8b937a4ac7bf4f826f9830e67dd6b11c6ace6669a50ae71c6bbfc62b859256592a232a38046b89908d69edbaacbffe21f792175fc0babd476858f67c19b4a148de36a66314fc63750baa80d4ddc0da1066c58ba96ed53f92602deba3cb51a20e1bb1995f774a5c78976ccc50e9a480902bde436b45a8e5e09bc80cb75ac6c76f0b6bda928ae118dd9b0478a82f210cf234a924498ff4efb6099fb9bc1452f1f41aa90935aadec8eee861ee33f5fd83c6faa7af9324755e0f6e5d7a4fbab36e1f07b5687b39d7a01beb328fc913683866239fc33886bc05c96c6daac1bf329c341617f1840c8ced4edb1b40d8bd0fa014faa785157a582a92e0ceeeb10c8d742fc2e26e2a67ced59d5c2ee48a2c16c94f4090eddcb5a163cb8f73327099949febe6959f7002279728a01243a99e50a287828928e38c284d9a8aaf49bd34099162ca5adf3b31648563ff96673bdfa5ec99088416ff7b7b695a8ff33845d4f7021f560bb46ea73327ad6f7665dafcbc7f232158bf846c16cc7ab5836af8e2a84d2185b221168027af457694138bc6a6ad26144ea1b8ab0d4c37933a8bf0341a812eee894fa88d1454e306fc1be02377a4c9ccb238b2f47975e82a41a9bf80fe276e34fc3f54d2f59fe7a021552b87b1b07c4020b22dc971cec24eb84c32b9cb9adbc9838761d0da3e43a09328db7d1eeb891e33031f4f8cc06ff5d64baaaf13664c3983632579a82e5635ef5362d35009a23b84de8e38cb1aa84b2316c7c15dc8b0f893a0b06731945f6b24e80a3554ae20af4bc78b8c74fddbcf559a7b8011498733ac692a111f96eb5477127b472fcf46299d79251b7c10e14d6121e0e972d88869271a49b1320c4843be7e6bd575f8dee085fa2f2e8e5077729115a46901a0dfd398c60a0363d6bcee0e30a92d4698d126f273c407439ba719688177b44c73b9bcb79e095620a33bd387d455c8a8f6ae2de1f260359ae4283b72e9ccad1238a5bedc66062211e1bd0034f745e271969ebe96d36866e18fdb7c6232ae846832b811c821276091ad08e680cea063599ffa53e71fc591f1281262def91190af2d906db6d60ebfe031c00cd6b667c3b03fc29f51a6439ca7bffb9ceee827b464ecbb03745fac34349edfbcc278bf2ec8519b23b503a83aecb336cc673ae724606388278a3dafdbb83ffcbf5204734f0a61c010ce013a34ab088182ac7a93fa62a97586ae077472c4c3d56783ab20bb7bff83e532df48e24b5abe78dc3d291fedd55991bad05edd46d6f0778ee2f69e67717f43993cb58289eeed8bf5e17f1958f80b52054b5af3a32efa949c375fe1147ca560322393b42f753576be7e2a5ae2267ccbb7243e9668a6849476fa380dbec80389d907f3a70990e305bb8890839649a864a2695ca4a3179c913d82d05c68828b0b994bf54fc29ce1c47b3e99b9706533ea9c42fa8cb4167726fa21181c09f549c44e474a2d366b892bb4bdb860ce2a33788083049d936696b49c82aa82b95a1f576e2174cf6736684a471e47bf5afc84bfc56affdd9933c554510eb4a6df41b8798c6d596029e4cffeb2b350509d505858548bf718ab23383a51200fc75ffe7dec970fe7b0e7b9b68496241653d0c89adcb386a3f474a24d3e33f021348b620b845d2c8ad5f903a19dd96feaf71523b46dbff302144399a5aa1be24c362edd925280c2425204e9513fb4b2a293dc7d8e64fc25c38d23194299fe83be882c87b91cb06d0ea148ed36eb960b18d3f39fdf9ab89b4c32c53944f512c9d354c5d8895bcddffa427a7d0f32d00a8147ddc9d84c2e54e5735e577a0d1ae5ec7acfd09747a72c90930732afc13d6fb84df15ffbf63ab63e2a7ae098be8372ce73089ab2513b9f2ed2f99439295f1ba24bea9cc6099546335c1938213188a06042ba2c116669debaef68d87ee79eca89982971cd24caa30b914bc69ddf88d4e929ddfb56bfb469a5068a4f7bff270619d8e9d1a4651a5f0acc17f581a21aeb1dc816862dec208c0d79dd22066dd27626c3910b9d7480b4654d221103a2b890c1902dc680965a768b4efd982e7c37464597f145ee29c2fa52a4ad5d0dc630bc19697fe11f50b17d01a66146f14cdb8a2c24aee36b8adc131feaf748e4641c31f1139989fd831b49c9ed930ad6714b6b55f9c9556622b1f0ad859527636b6b6480f74743960854b0dbc9ab4531038a1d9d62c286d4a8cae13e805e4c5ead758077ed25d44a8c89e29e98793641da9a224c0a0bb7091bbc8f69135c0e24d64e33969fa927e7e9e73c831cf7942e83628a0bd077ef994d97c3c0614cc7ab287b8de2131823d48e6f3607b424dd4943f8d57c5057db872266d26268aa10a5746e3552f2db260ec18f3324a8ef942276ea6d374e273342c3999e6bff5efd5519a6c4efbb2ae6ce14628f75455b3882f0456167d3871f964a91c5d6d8131ac3b8611148c22882826ec664fedc05a0f326f596b38a3eac04471f717b67f619e1e46e18aa23e5062d3ca4a4605637ba6af0ffc42a3c9030d669216511d94dfede96420fbde3c3bfb6d87ae0a5320c57a15b8f8023b71aba2bdea552cd36879d5a3f1020f709c6794fe90e9542c43b2b64dc8daeac4f99e277485e8be21c13e045731227f0dd67571370b1e314fd2b5a076da0f283f11b4a6cbd4cbb493b68552744f9e9d5aea08f75537d38c3a80e8f7730b5298eff75140d1498b7bbf1c0eb72af59fb4837fb23c8050e49a33558bb0a22a364163a8604750f8bf268120ff998b2704f80ec94ce0f02ae6ebb5f8a9e0987c203bfedaec8b592330ef9f2ada380e61c08bafc37691e5267c4409f43889e50e2715102288c97a394c855dc5838c66690133ce9c98089df06b29ba572015cc378d77500cda567d6971a515e9d01b34c5ccefa1f1f60441db8e7b4df2e220f1b2fe92625ea135feba904319e06b3528b2a60997b0d050ed9d3619bacdcb3d28ec00d94d8d98b09e68e6ad4ccccd0d1577b3eca08bfea469067722d6d74e899d97f8ea65f30531eb53d9c422bc7b201db7ec810b7747466798736480d6fca807b2dc9b48a41b13ee1d17241391f84566f706311c22e89103160826ee5c9ed291acdb56a6a9bc3d14bb83c2ecf64b81b40e638a0d29db4597ed9021fe603df9a696d352115c8a6ad16592051d0531eca9b9d9908c4ef12a8d395686a7dacc1b6d0ffb34146f8d24397f1a6434d255e01976e5296efa969f6c11a91ec0b4f59fc28c86f8e4907d013c2b63663eac11bbb6d63671ebf530736ec6cb93b5a2dcec397c9148792367f1bbe3373af86dc50df6f996aaa2170e1cc378164f2a150bec74269193a315f474e3e3f2f2ef0c58210162ca93b1ae300fb62e3d741fcff3c26eaa0ecc748d59fde7096f175fadf422b1ba0085164ec63969f25fdbd60ddfd6e63d620e5ed4bede8d47118c23a77752af856941f69ea74f9b18cd4146462da2bf390cc6780affc2fab93e4faba560df69e5bd19af7bd11e9f9489b93e8f939c663a3349606fe2865f3b274e903e4b72835c8c34de681ac733e4df5f86daffd1011b76e06a9345f39d910fdd99aa4dac1bf8f99938fd74ed387a4ff87fe9ace6ea6b63cbcfe4abb6470cafb3182b3b1766c6e22895eea852a9445f13df856bb9cea400f8abdbc6218beb80567814e6825fb3b61cd0fa6be5d36e23895bc60690bd6df7f0258022e64b70479af80598f3610c8a59390c4df6ec0b42431fb1d253f54178d209a3235cf2970dd4b8e032e685c2c2d30869b27d09017de0ee87253ab4f0d2ffbebbd6c916aa1c0f03759780328661f3f0d4d2382c3bd573d7c16c4c7c3a3e22d9e3fd6f51ecab0594b3135ef09ac84521b26c3e545932ae0c23cc1f5619b3bcfc6966a78a759187ac214eac5853a0baaf655c1dd34cd3a6e0af8b8401acb0fcf8bd6ac16ecb66073f215222d2d0a534010a245655b4f4cc52b038e21944da6c3337c696fe6e219ffbeaa50b5f384156359ae7204adb511f270314774f1fff1ba9666f5417c4a050f434e36b055f6376d7949219f9e55f50aece49e9ed0caff1e82049c55d0a6acdbf9d18acda80c3edac7d45dd9fb3ddffc4a43ec8a36b582eeffdc709f397e29c7da997f9ad132f6a8964710e2eb873d65aee63f760e138a47c9ef6c1656e289b2402bd2fa41cc401c6edda9cd896080f8e480c92b4e8247c27b2dfe103d80d3ef2709664ec4f7d26187b4afab2ce414f4ee21a89158684bfa894e6db15d4b772e092a6cd858a78814e2ce3fe4b6872a9785bf5da07d000aab8d87f3f2196279d50d33ee7af40641081ff767a6cc563b4f0d39b532da5a829364b216f273a63bc7851a083531bdb6b78c3df3b27115e1c8e2e5ab36137c9230096f557d81a0a1fa373df90a3f20e6733e51d14503a3d37feb75f6eb9670fed7ad24fc6e21abc5b454a7905ad8377a25609e10deb78565a97ce1dcafd0026140389c5a73b6a2891bb9691491769f82a20620e7dc2864f26c57d240f67a9523c3d7512a102218120b89375a95d41d2dd8d7069d7871a36e7996b76069d53c5f78f5d133ecefba704ad3b34e6010ebf880cd62171d6fb8a36a0476d52991e6bee6a3ba04296e936caf145879c567fc1e555090753cdbfefb00decb788676cd30fab7b2bf19ab33267f10dcf6fcfc17b76c6e51e7281a8de37ec3502e33996e1a10bd271be8103e10fc1c45b0b68c436133959bae479cfaffc300883863b58476d8413e5346588e2ecaff6fdca590e248df560cc346507ae26ebd2ff7932d1ed12ea65ae1586cc4d37e6696010fa1dc20548a2791881b809404c87b9b89f983aff1377207f1c12601a2222d27a6d83790199d639d186f6862483e00f92f8534da0aa757cf13e2a67a443d23e411a4f499ce1c2f7224c29befa3bc374775ba616f1fec8ebb8a20e9f3b626b6315d195ff8552aaca0031197d1036c2edf2c7cb9368609ef7583b29b6cd086a3cff8311c8f8191d68f274a3ab95f33ae8491fd419520ec25394698ae48fe4e2b53721a0faf70ddd207f41cd7861ef663c84e6e6e311a3d96fa744e29c66d3424c831d10c34d2b015105c48830db8855489c3a03d52d748183b52ed6b44dc75125f06da53897574ae9fec598eaf757ffca7c0e99995e449d8bf2cb7ac8625bd57bc4772462347e0cc2fcf843c2637c8a37bcaf4b4d6836153c4612377282ddfac97e98ef9b1b36288b174e78f0bcb8a3eb17677cc357b3502e356f66c7cf71a800b84a34d6cadc15d24e8763d76908091121d0d818b8295811a7decaa97672f94e4d0d09b8cd58e4727e95be8728c727fd14144a35041a0072572c33b1cec2806584b3ca2fefca517da0a2fb59a9198335667728f3c4ad17b366b0c406a19490c305bb0c0b52baaced30322ae63bfdcb6d16af41f8fc2ed009159a0926f4d4e78c97b304a2d6a76952dca75fe8e9df9402231ab05fc5525903af1f9d424623ed07d350110f74922f8aed62d939463194c88e64cec41298a7974246ff011b0d1e21eac6d3bfd57291ff12da200bf9ff446e09e0e09c23188253f90fe8d69e3f54d11ee8260d3f2f044ecdc82321e793dd5f84e906c26d10e450b788f5f371a0d6d0ccae2b3bc50ea6e9f7dfdaaaca61fb7ecb1222b982d5d1bb2a201706d44fc35e612e9853b1593747567f1e063b8ba1b17cbce33bc36ad4aa9e471acfb3cdcd4d94b284ba006a470b8a3c013aa745c780e0fb70ee2efd9736699a09c325a7eaa03cf21d03194b943db8f1b8405b6c443a53cfcf0776634449af0f9634b860222f9bb201da1789820dc0a1f886f689daf914f0201f898aae57f50b6a69c0e3861f6ce8406f0d4cd4d1f7f3bca7d5bfab0ee29c96eb156fde8c1eea37e97755905d28ff0b07e183814474e49b8810561c731399237662ba2d0c4ada24295c32696f372c88c623cef47c4e4df0727bbcd8bf7cc947037513edf1489a190e1e2475bdc6ad4c9dafd4ea102dde5543d86f21fd989a96c97d2b35d3fae28ec23cf796d102e1173d5541242fff5b089e0489f1dbcfda3cd83a275496e7f59ea3ab6eaf8c667f70fa3a38580c0113b1c8de6754693c73606073d5e5c36a907a8dc7fcb428f4d9df5b6c8b1ecc6b3829f498b7b2ae73cea3f019434c3102b7068dcaa5fea54d38c78f7a1b925f6d71c8d85e6a88a4a9bbf3b1a7abd447a31ac41fc823ac3394918973aff3a1f94694bf2440c94cc7608a456f250525795b395a04b76f1b3348240cb4206849eb5ff79c745cadc6208c1976937c5a791ac289f88ea898e8c31b46375f4f0a0cbec4f2ea71df3b330ae5a626c73c86f045112c609b008ba4fb6f4665c5949358be602e6375dd41e3437849ced1e2f5ed1b249f18cf713fad5b4dc2b4ac34cfbce9bc7011c8920a53f6d2b3af9a2a6b56f91a6b1ac743be267cf8cda89637746f0cf1fcf88aafeb621ad9f6d1e22eaf6167557ad80b48622261920f60c69b89044c85f92cd063e8352048d4c9d4369a27b1179ea98305196dfeb64105f973b68956896976956d7d954f8b19839531652b0244316214bcdcc6435fa273875cfb943b2e2818fa947f4ae80959b65a0a166b89b3e09a2dcbda58ca33a3d5d1ffe4a1eb1df16611bbf478866c08734f564a9e162362fab2201db3c94a32e81dddbb72c239579c72e3db5fd7208a82ce484da6f76c1033fe493b30d32c137e2e7b2bda67be72cb74391098c5f745a8818f7ab2f085db25acc18d39d4ec99401325255511530364df9910f120a756f4c6b30b3c31faa5da48ce9e737b95a12b85a5f48c4a3782b23d0f38443792adc2becbded6925ae91458aee99d3ac168f5d233942d769376f84312b99dc9571c0cc7c0d8995130ab10ad65fbcd599b8a799431f1f8860ed7b176195ad1437b051f02bc620b4aad6d98301a25a4a1a4a76e3c34c78b01c00ceb1a46d46d6b0ee418257bfab7fed9b9276f3ac9c18063a7eb29533a40bf4673f875411a31fe041c6fe5f2358952fbdc6c60802485f4cc1d0fa204c09cb73991fc9d583732f27c240a3db10c2ead4d794a5fbaa7c1ee90ed6ea2db80799956a711f10bae503ea8c89c5ab6f3d4437d8366c4d7d7e30e00e988189f858b204e295a80937e582bd69f9d0d371104ba78ec63f334e12c9d33a52b7bec9a2c2b3b468c6317ff50a409552c4fa9c0db92e550480912c2b450c564c9127819553bc41883a4a6da5ec09d7c257983a0391ebdac1fbf10e61b4af9c126cff49774bb0c260a81c8f5e1c028f86bcc16dd1dc1d67a864ee0e81991d8127e22baf3e4efefba6efafa3a53c076c243254e4bed099d326843d3a72305db511b7c673f585b74b62a3304c8f1ec9b515428860f1e799bbb38a3956dc083277f344c183bbfc5db1d58136d2108c8ef8daaf7d0d84632529b0e6f38c1f4ca53533217ffa184e1709b72dce5ac899415a34be371104f4a68b378c9a0aeb710f234c344ca06f8e3dfdaef888c4929d1671ed2b4356d05b46cde53e503a141424f8a1234accd0f5521499a9af255b13ae04e5bbee271712c659a00d9b11463515f43b896a6b27bd64c7c357969b07895b5155091b97c9bbd43050a67e3ecf4499c60f6d22eb8316f2c7c7363a6dd3dfa45ed383400e8558b4a9bb086dcaf680d9bb728383805b69a0859892e16654e94afd3c567ab0ac6d55708e9b0e995e16ec18024e403137988c4c9428c294bc0ada9ec8a853d11ebaf323d68d886aee26a9e9564aea863b6301e7216b26c54abe25ec42cfcb49f4f0a406d38e1f9b997368d3cc9825fee6490e498b81a47e50e41b60030a4927154ca6a22349c88ee0e8f75f199319c7e42d34849a533fe8a7106b1aeab25e47a696de3a49949647f96aacc34ff03d0030415eec7f845c6a861b3919db5f4e0a2a3dbaaea7732af1af9b5fe6e4c8a6d875559ac63ca11afc080dac2963b14d6d4bb72cc0a71f330ea02860c9c0b24a03ab0398909d0d19571e27ce8a4aded55a8aba7b0651803f140be6bbede6d375962c3218b1181a7f3d8a4aa814bea8be054ae6359809e4cf3e0f4de7f5d6feb3536cb3709ce6b0e3c6794f3bd1164cac81e2130e09d42754b90cc558d3a7fd415a38f6ea43e3e78cc9eaad7e53b3b17c180d71843707b08812b7cfd77ddded89ed371824cd2ef04e7e135a222ed194a51296797516b0faa39afb6d0cfe061ae9cd33523c38849239b0c2ca2d07184a93ce52fd49dbdc0695a069605858a96d30fd36c23671dcc776bb5546d03d8756d76a301d5482ed8127bf39b03f8ffbd4bd5891e2cb9f536c27e52f31dea2eb804abfaf4eb3c628990fd4cbf5c0e97dfa0db85b6857310781db2b3b450ef25a04a49bbcac9f3ad5a9ae8afb9079e47395151c6038e785d17d44e0ed5e36e5aa5e431dea61a3625795559ceade65927b939a56c780ed95abb31055928d45a1a7586090b8d5bb32451e753bbd33164d36d174d4610d4928c9b8a340c571b76c4fd5402c87d5ea92294b48f7c8cc329a2e70f4271ff6e6bcf63d35d3a91e5728ae1b2af7aee524ddbc87fc41d13b9fd236b56fbff61cc0d41c587923d20f913318a6c5c6c0d4eb2965b48e5636e95b5db7c20e3cfcc4824632a22fbd50cbda72aa661a04d81a7f03798f6303b8939beeb2ac25e8c0beca61b924c2cb501c8ab7156910205ef9597411826ba0dca0e79cb98d203557ce2f8676465b2043489d2222e194983500df0bb9214bbaaf4e06d3e53b1a70aae30374d9912b9603ab2ddfd5bf4cbb2f7364105b69841770e8342a44fcf5850a6590b11c03e190aa22dc7f20f25519fd2cd7df7c92033ea340c01c1f334e701567bddd927bd774dc03523c3c10abb2290de2942a452ecb83b8a99942fdebceee737e89eaadcf9986ea0deb12297420b7b06c49b117fba3727e74487a651cfba29651960fffd728794a0bbd4040eb18837bbbec15a83dc5a6222b6e5d5f11115ec4ba4035512c060e74908c56ebc25ad74dd25c188015974db3f28ee0e607db29c91b82e18502d8d48d971c64877e240ad401c53920ac4700000000003040000000000000fb66426d30534237bc4dd18dc582657ead640e2ca2ac6e1d82f05909683947ecab43e2ae0684fa74bbb547de4b82c677b437270d7e99536ac49bf083d4c997bd74caaf9b424a61134a5366d950c3cfc9a0186b3c83e81d599a5ba454e4d85a97613d0200000000000000000000000000000000000000000000000000000000009ded2fa14005a1f9ac6d628b64a31fe23142dfac71890d0f4e8b29d0da12ddf80e8d8d420e82151ecd560bf1454429ddb256fecfe7def0d60969bcfcf61917fe17fba3727e74487a651cfba29651960fffd728794a0bbd4040eb18837bbbec15a83dc5a6222b6e5d5f11115ec4ba4035512c060e74908c56ebc25ad74dd25c188015974db3f28ee0e607db29c91b82e18502d8d48d971c64877e240ad401c539ffffffffffffffffdfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8ce33564dfa183f877ac5d9433844e90f9e3e7e2a5497dc5214588ba4a19022e7aec3ebe601e7726bf8abe7c0ace3fb717980de06024d04bc3072aa5841b1d75966479419b1234321991a24237ae1d488101a47eae2b6c86a69715ec1adb56246dac470000000000" +const lightClientUpdate1Hex = "f6c34700000000006db605000000000032e35433b6bd8d415ed3df8dd54570e3e238ae6334e8899a54bf9804515d8d58293fcbbadbcdd05ed855e2b2286654030221f4177adc7cfa7d39ac414de06a9aa3eeee65a64fb83399522005b11170f228b21eef30be6f481d30695695064ac29484fced5c619a27d9d61af73af33ab3ac72b64b0928facaf8ebc967d657d9e0f3623e752709992bad1bdf9c3a3b253b8ab10cc8099a7e8d2ad1a4d0a537aa7bb3f04d57f877d3da9e2762f3d479cc18e154a9f4e7f5879ba029387f5916cc8bb74902e3aa238a69197b56f619216f2cc099eb2778eb3129e54913705613889720b33d18ba1e4e636dbc917e3d12552a8b80447b98a89ca3417b5d669cb5e45cd772619820b6071e80efd9cfe8c3898889c38f2a1be7513813b59493a43578e584370e937d211f8fa5ba772f1bb8aa43b4917f39846f1870cea8d2fcb2759643a6c8330ddfa5df4868ddc5e2458f7026aede0afd24a01fb8c7eec60cf2feec1eb19a364573d1dbb1d954a0a22498bac237db96ec3ed7e9c90e1cd1f0b59b9ba8966c02169af100668639cc15f518d7e45b3eb1286a64086dd1c0e68e099bd7689dbef697b17bbb01cf902dade9678a0eb6078f523423e9ff2b4ecda9ec00c58cf0f0aa7d8504585d2c379a404c8e4e7ceb6c82ed434838c70eeabfec212238a68210d2ddff385fddf235d9aa5d4be5cbc8953925ed17f66dcf1e6af1cc16533526000037f902d3b21cb9a5563b3816c0af8eb9496c74abfbb3ca2a98c02162d7715f846aa5051680e8f0bed3ab191a47d683d70b6fcaa5f9e82012948e68b501b20dfeeb9c8f121e30c4943390afe5445053be1d1535c13b35abb366e637cdc34f6691ceab31ecd561eb429a484ce7bfae2c7e23a996eaa1785474108c574d28ec1068df44d404de6302560acf7c3c62f06727f84b2f56814e23402ca6c4d2f38293d1f59db356c0020bc43bc6fcdf69a53c417a06c1a59f38e931796bc7318621aeb6ae4f6cebde9141055d25eb10e9a1211fdc56cb6d8ce08d9e2e8d488c17394730f062800345ef513ded3bf3ed27315aadde8bb01b80ec025097c547ad8f949ec33d116fadda6b5c39b84339279693a5d20179606df0659c30386c9e03fe133228c83a372fe2158aa19c3e621159ae5af7b06bf86f2a7aabb9f87c0a56d3707002f68cbe835f7045466a7dab4a718356bc8616b4dd23eb0ffad1c5dc33d9b07eb9a2aa665fd27941d2b045d85ae7537078a2065f19ebb362735ad313e850d80cc397c046ac630b7aa9098668744e8b9118c8f2aa24799d11c9ca0ee6f4abc31ae3072896ead170f1cc93ac4969f7fa4864f210debad59b6f494c85d11f3fab98ba26154f66a6f3fe4c0254b48af4293a32b0a3b9fb21d0065f3ad6bcda4709cc1178052e1d9b31f4b09a4e525861b416d89637d266b1a099b3fba6f7d60978a8e20e47eb246defc4e2d75863d2e357dac3398fb5020b88937f69e84311cd950e37096e24b3a18138cec0e0f17eff12109fe5df224cf86f8f18d38cb1fe66e0e19977a7cb6509eb9473b319f2e8daa3d84ec5f8b4482fd9205f043e37888d5c1c3788f6a24d1daaaa659f9325e0710d5e3a3898e5a14a7d08f7c8cc042ba7a216185a27afe610556e006a583f1ca53056b534873fbd1ecc4463108f3b1ee13c2ba260c7d354884796a22f1ecdbe779362dc25b24dfea81bd98bf945ba71e5bf86a82aef21816bc143de5c83353288672b1dc781a1714596225e88ada77e2db35e04b9bfe2d59b2ab60deb465f9284016ae28e5b82a39ae32fe74eaf1724cc13a5f361ff74c764afdb8c300d0475769735f38f616a14e876b06ef1c5bc7ee953679e64bfe6e9200b00789a632d9da537bcf58bdb157b6f39e224ea4209b22a99934c6b5dc996b7dc8b1c3ccecbf74688bc1d217fa4a76c3cdd709a61610c6a148bcbbe4f4de1e6c4f9496e0a1071aba36c3117980cc0d59a004d16f62c4889334be3ee652029d71b8020b317be7900dac2837f7a12422ecfe1c4830e7edcf7b349c221064799c9d410e1570747061c3f9ff97428aebeef4ac99cb3ac49d1fad4084458abe87b9f5ade10d1023575fa977025dd44e3ad1a925ea947350a93b742e2a16ada62a2c5311d8f931ce0e0829b405f1947a732a7cc201aa751e425bab1f6821539a72b9f2ff667763c57a82fd3f73bf0be6e9e458c74d0350c706fbe23c6a263333fc947e97ba1a68185ae56a7ec28e79d7b28b9f5e755e476f5156da4cdad44cc8235c3cdbe5e365b21bce38c98411bbdfd53d2bac0f210a159599091970832e8646b51029a89cc31e80d55eefa375c2f80a29ceb466bd39b0c7307719e3d46e2cfd5378eacd712b76aca5eab2f7a0027f02c433e07605355ecbf268f4393351e426f72751fd35869dde03a939e2d3c6edf48cc41992d9008973a1c877bfd105f584e9a8f9a80773ed04978bb4e7c75e40a17c832b97da60077309f9107dc4867c8a9846fe570f4199528d9854696627b9e38edfd106a21fe75b2b1acc0bdfadeac40b6a1f9049d4565a52072ef85df826be94f54da5c1307415b2b974fde09add587db05d351d8fb5ad0cc146551d4b4b4e9d6f50d5eacb8bf183075a8afddd728f3f64a587f0d1d22128994f2e7e59583177e8d9c35c28f7ea49a8cf9d6829baa7bb06206cc0d50ee0b9e7fa12d8206a6f8d85a58c3e321e35fb98711def57fcf0710348a5343d2f8668daf4cb9174df54fe82ee32e08208bd0a25949e55661f8615ceec260b5fe632996a7e0970d24e3eb7eade45fd4f7941a0474b12448cd20d4f74f9d9335f4f66d5900cd65d9237d0d84f8613d6782b3e9ec86f4dfc35ed35c1c527a882dadf739a9c2b2fe9bd2e06ffc48ae60e8848c40ec2c8247d1b20ac5fa3be82a9039bd60b4a2df1da0744320c958453c939e257895c6ba0fd071dc53f958b25a1624264842efca70ec9e76b7da5c8470407a60d7e5b5713890aec8117ce515c81738a54be557ae3d4a21757cbefe33ba4bae817c0e7f71cb9c1e148e7de0d8da05140e54abb778107227890e8d6fd86720cb82f9d291f8a548e8435dc5386b26989b6ff9216950b78c62b1fc431de15f7eee2778ea805f27a1e959d510453c7da837c74fa0dca389e8d13847450ea785243ae10404d416266a1bc932f083f92fe3e164a068b3e49df8be84e157d6356ea3ed35946b55ff0c99d1b823976d808cc773b42c307202251a3f8471c1c109df4c9aabed858924ac1436d5d8cc138ccfaa9e75c0690371aac2f857ee745e3c52d867456397a9cd0faf88784c2098ab03657d73e6229622180fdc6517b38a6aae550db6b452e4fc1c96a72fca3df2a4524139126170624f7ab45bf06b62ac9668c4984a5a7e99cb26a1e6d34b32711c364e51c302f7a595459eabdf7378ebd1aaff0dcc4f005139b6f87de4539f1e8afe920764b234945744d0c10cd3fa025cd2ab4234b243ee1230ace37531767a8e24b8e0c55e0378c2f0c766faa3458172d77d536475d186b8566bb49e13c333e2db0aaaef312f4e156c6ed74ca41b607bf34e23262bfbcc2ea6282179cae7e97a5cbdaf932621af355c50b2ec2f8fa0cc93694632e517e74edf1060dc138bf9f2c6c26d77d32decde9738235d7fc7f6a1cc0f5d07b0ef94175f9d39522ba04364bdbde6f39e1b411aa038ecc4c063a34157a31a23fca632dba59c403f8e416fac066c8fcf3924ac85853387585251477310b38d9b94432c7720713e5a312c373c19e5eec495979b1cf6ed387d9a6fc2492e278395346b924c271aa5253eed306f4c38456b347a22f1b29fc4e6c4f790487a89ce60a9539d358d2e8227fa265941748e1fab3d0ba0939bdac9d6474618e48b83efa3c8a2b5681060cf523f14dade122af7e506fe14288997c1bc9b39933582b63039b238aae2e1f9368b46e32e950bacbc1c3b4013143685b1db6abd1f01c1008fd523e23a7cca9d45d8f468dce35a55a9ceb160a2589d70f62b5f95c1d3c016796b41062093c44df5bfe7faa062e362ef3ea9f6f45b2a67481d7a5706071b27ef49fbbaa75410b26975dd33010014069b451d690bcbf52c3ff674238628d2543dfdea9fc787c893b83623bd7f4361592ee79120aa5b26fec714fa64177814a47c713fdaaaec749fe54baf97e06b47aef5017a25d23bf9938bfade298406394c9b93a63291852c2baeb0b37cc963b5b9c4f27851d2e270005a80749f758ba559cb6b49f87498fbd5d7cb73b0b7c80d1b090b8357af9d839fcedd639d8550964d27ee9515b1a8b41e5e0c043c6c17d1d720c1937135f001c9206246a596b5fd44171c08758410c5b4732d9f6cc1630579b042716d435c4a28d4357ac3ce6f3e82a8271bd0dd4c2a651f76e9f27e0296a02754d9e4821f9e097df6cb2bd1dbb71b3062cbfe6bff628230a77040ac1eb7de4366a8f36279bab06d2bd344c9bb90d6fcddf755922ccc7a3e349c0a560913ad49709e7efcf41943f5c8e4e922df14523d51e6b30db99d724fcfc7d7fc48a9613a9f5b3e8038d477ea53de48236c4d4cd771e6876ce995a75d9a6f3f6fc4ff7b881bdbb4e0fb8df6ca9b77470b0aa01460c224c980e35da171b24cede15c1751bced4eeaa8324c68e6695dfbf9e86357b593d6bff145d6361aaf310f9e82b29058534855b6b1f3bb09e90639c4eec1c7b8164b599222c1437a68f53f5d15627ec35b08b1bb0a1a6d6b23fb08dfda5d20b381396fab8b83b5dedb1179b39afc4e9c379214432f1fe394fb870a8af78fea7ea6349152ab9596fe060445963623cd14471de897a03b98a08374549c7e201fdc6c741191459c5e38dc8ac6692f117536df5d63537bcd49b1432a0730011e0de910010fb191a793be051c315be14efcf7ed8c8a94def8a42479f19dcbbe2e182ed24c22fa9e6027490215dd70f05b476b0a118998f6adf755cbb8ead017433182a4b12ccdae2802070b48c8cb10a2978dddad9660d352f703b760026d8fbc270348097682c682de1ab12f707bb6e4495613f1b935b5369a2468c7dcfa30ce9e10bc52b0249fc80d078075da3453fcf040d6b785b94cfc6c9105180101533436b352732f56d1fde3462bbe4a1ddedac24528543807a828fe1c79f67c3922dd1b5a37ed76b6a8889d47190a2b1afa3f35aabf67588577963485a1ba82c3aeeff7a3cc4507b8cd9aa3b8b02ca24321b8936b087d1ea3d5a0cb4ceaddb59845c7e9b654116c4ad6209b57eb53f7eb79b9abbc16864e34d3f184a61d1dbcd870a4c5d771b669b6ed51b915739a8223d60807575f5f1b85b659b4d9c608e9de4c6bad60b9a9c8f268cc13a53ebae4eba007dfe11f279696b598db9480ddd603540ff834a838447bd1226a8e42afed5fd0485ee6c92b53f0044ec7e4d01ba22d2f89b34d2a4573aa03d96d580699d7289c6438ce0ae04f35b721f04bf410c252afc0966a12d693050e6b408220b641bd4997de8df95a9eae45e2919c30ddfc079ae7aa809cc68fda05d7872fdbe1804c1a9fdd6d7c9c4eeeae74f65b1c1f9723e035644a337f0c846cf489e0f9abb4501486e6cabcb18a598489a6c6c7f4ad36c0dcf083acc5d83e858e03f25155b6d2552f9f6916bf81937a8e3c457a89b6418b125b56bbc7ca40c16a0247baa8b2126aa35048cb0f151f212abaef935f6dc199fbd55f575142acf9231989f1a7e64c73786aa8b4ea76ad171207b61d3f9cb2ddc6cf9dede907bf709828c845f1e11627ab8dbaf06946b30528d16a5a8b50d3a2d9946b8f37640fb734245d3bdde526dea2787d604d48ab9c5dd79af0df69914444899cc0aeaf93bda573f4aac640160086ffd4985e566b43c360a3258a5c80f04d305797ca9be2457a02279a988804d2ae15b38a3b66821b5d3fea6fd7deefd65b6f5fe7a97e5140fb36e035803a568aa7274b15a5fcb30e452a15372abb8257d7f7a91437aeb60612d341e62ea8fec26b447e8d2e02a49a3983c6d4a9a060662a4ed93d0ed874074083be5d712bf4051f37d3194e51a5f674573985c94e6867a601746b418d17e6fa28eebee53e670d26742ea7559e60b5afbd552b804cd94cfff0f48d8f91b34b3c63630326d266ca1ef364999907dfc51b8e59c590507a664d19fd0d188cea0d3c67b62a7e199995903acf0337ec8d5aa2dde760d3b30671d529a47a039d9233a93243c8ad3d6089d8d24ac41c3efb3b1f0e0c3b0bda3efe2baabb3938bdad08d0d73ff9842f154a0b1615d013959b2d400d7483c226897bc49b0571ad312fda3781f4e8607f93177f7b4112691680af579cccebe0270b727460432e0356b1383287a6e07235ac22188699db5978fbdb45f4710520662a4265f751f03e9b94b4b68df23528e91e3de52e73d96f5ca3e7b06d499cd06cad4ec84b17e4248865add0854361946a6e868799a30bcd4ea115b6dd14b6594d9f52a05c47370422af37eb131044a4a9cc510ba3d60849f086dba13e49f7238a3b6f27343ba4b7528d71c87f4ec5e2029ec4ee4503eacf71ca1ae03b61822bb1e3a78aa70e6d46ef51d65911da29342256767ec036a5c232b74e0083fa3086372b28cf318a59bffab3d10d3cc876815d96f132223d816a5bc2a68ac4714f15f01c9ca2d2e2fb0b25b824f1e5b2687ddab6427501e9f63a1a4ff472b444874f6f55b821d61aee035360fe7971e749c469c2492e1950ab62f5a6b87d135f701f5fe5fa878cd769f03774f420070f13837604b533d44f4ccffae1f5ad1c7b6624f6c0914bfdb9dcbe3389ca76205560567cfd2925608b89bb1cdc7df4aa6dac912db40d9746a31c6bb130e26aaff0d8feafc0c9d09e99eb8d03887a10fbbc9ee3bcef5e6b5b3889d4fa0b3e4be2cd1d60b273530cddd6eb1a7f8bc5b8facaa8d9de1189427b2fc55d81a48a7465f7d537a3e9b8c245893c52271d7e89f594e2947ff743bcefd27a467aa0ccb8fb6cb0657c520a02723f838b6ab49305f5526f6bfa44bc2c7d27a7e13d75808659c0818f818aa666d17f72bbf2b86f1bcbb8c045c61bb0d3a0507b5bd7935697dc861d27c3d35c256a4c7ae9bb1939dc565e2b3bb0b7a2adefe7c318897a767e456147ab2dd2aed00d27f32aea89c5443910c70fe742f32fe7e452a5bced9be5e76ce756155fb5d084c485afae9880576cf8c1af7596b0224a08ff954d99e6f9ee3fccb44e756e492f5ac80a87d21df3ab536193d5c6b1c19d48969825ec7719a53722858600b59850014003bdb4dc8f1c703380a2e229528a6e391212d7a051511f4b27d474259403e748dfc9e8b5a900820dc452dd4d2138ffc2ff9795bf904cd0da91b37c7f05ad7c066904d7d2c91ece42ea3d0b115529acd585c8dbb03cced93f5385e6918acbaca1dbe88f044c0b1069ab98ff34c0b0cb5c920346a8136318e0bb3f12df460c26d052be573a6cb0ed285d2e4925f7ac32c280a1b23a46e6509cf6fb58379f2f5c55a66c751bf53aade024a03611a6c3f2db3ab48e73b2538e8209eba66b79cfa8e42ecf842b90cf63b9e77c2acebd63916f325bc31de03bdff649111c67f774bdbf06805da37e4655089905f540771970218e0a933092c006b6bb1fae36cc863f28973b581cf00a8f744bed81215e35e56997326330b47457a0dfcfd7b50d35259fd920b8e911efdaea9b739f7f0e99b278b1c0247d63f35ae923b3e68c9054391542eca3201a6c1afc221e4d6fb2c9f1f2fe0da54c86a45aba97960b5605dc7cad330eb9432cd3c6be9427529323880ae704bf37da52f795beac0d8931d15b62e9b25db86d8dba2dc6cca755800c0ec8bddb39df325b8b15e195dd90d06d6b823b4ce0111e685957cfbabf7c9ae2fe1e7d1bc48234d30e6d8e2cc0382b7b5d03b689157722fc9ff9d0d7bcc12456455f172498a236044ae3e4cb55ddb2fac4259558f582e847d85c790620cd5e270df8d4f7edef99427525ddcdfc8854f56a142a3f3c94f6967df211f61ddb211be9780b626c955b30d911cd66780bbb176b4543e0ccb88ff36a1a2f468cba53253a5a69da3c30dc9103bb35959ead6e1f652de9c2d193f6dc3e8ce19268922105688011b41e54e4a74845bf0d3fb49a74b3d6a6184f96ed4b142f66ae948ec8975de37333f58bb4412da2bec0821e0f455cb05f4ca7e5b5217f756cd80a3b5a331093a38e15d7c78d2e632520476d0fa9f793da3307861c359ec1b91a0db6eb2c71c85768d87c85f1bf72aa25d8ffa833e006701993b24003759f7c20b39112535ed1493d78996ececcb57c2d0ccb5641239fd1847967f6a5e2655998ecab3f367ecfd6aa4437b413c7263d2fa2fd5228466d3e2b95a5f9edebe5e0d18dda6d3845f01852a777c03321121f1b685d9c65def15df64589f24cc1cbdc3ec663cf36d270877b46a00013ea3825571e62552903f5d770abf2d538af220a88fd5af7d4445bdb4259b2afd54409717e60338523df8c6ae1f8840abe9b3fa3389871df2eab6ebb99d9109195df1de925a2837741a3ad8fa5af0da51e3dabe0a8c7158ef753f1adee7baa9d926919c8d70ae202efbadb1665924dacd8bffb33d3099562801f7a68df2b83a6458a8d536dfa80c73d69b1a54788a506750792626105c389231848caa0d37c1890a9932cec256c32eadd663c90cc5e5500ef5a8cf33c03e490ad1d58fcf0a77969b51c4e1606ba59fe7473cf28427cf1e2b7684e53b2bc3a80f52e652357d834f076345fa2db454cb55fe7cadbdfa0fb1395f4a1321d16090afeba4f39b46327cb33abb6ead875cb80ed17d3de211d0a238632159dc9bd603ce7884185c1a2dc851707f9f6f9ba68592313a38ec485566dd206a2c2251eafe026583f166db15d6ad9fe38b4690be591f012656a2fb41217dbfe43ec0d8a9e0670fc160ed2867386e0e29d191880dd1e53d69ade05265d112e26588bc807f69343964144b9b8482f555a530d9f646c2b75f8587e3849f7fc15498f42c25fdf21074589c863ec75e6c1359ddf4330509b916e58b4beafbf6db8017993271072ae1d53fe51a36f845b110755541634f2d5324fa390506dc64a5548d57a32d65cfbacd66e7281a33fee60aee3a5c80e1190618dd96d350810bc88cd549ebea0728c3d9cdef033e6f498851e7c386158f5a725748e4b748e0795154fcac1bf7cb26acbba894b1c2252bad372b37591fd529dbb481a77b03e4a7fc11cb0c63ab5dba3007f3203ca8cc4b41ef5363dd634bc10e69186f4507c714e84c910b4df0dc1e494e31a5bda06701eaedd21717fbaf98804ef60d14ab072095bb63d73398cbc61c0a53f73aa03fabd3c2948cc2eb3c96bd18fcb9208d635a750abdb038c2a1c2e7da5b7b84aa50d04dfc4e7d3fdd93304d8c94f519dad2918a24ed45721f4d55af460e955e90b8927ab8f74ff5cb86208ba6fded969b7f9b688aa6f14bf353a4e1fc6fb9f6c3c2454b6b06311155161ee5dc52b1f7162c63002a89231887c2fb20d2138cecab157b91a00ad0f81562f2b269aab830ebdf9b774432ec92134e7e43a3070a6c9a878fc57e288550751d9bec7e4685d05ac2e8be7b564a96b6a9a2bf4c16cb91d0c79fa21f6b6b84b70c2265c1810346c47bd87fa1de178353620ee8a74e6d64294f7fe35e02c74d87db70a91250ecc411bd537870cfbbd187fa919e51d527279915ad435f3a8568e92d80e6519b97c09a5535ac594530b84dac45898f3b9c4d564c7e8e2acb421ce93cd6f0402cc1f017af6fc93754b75a7c7378a0bf9701895893f5bf2eca9291b27f7b86b84d33cf6d2df00db8147088c4146d9e1b33cfe3c2955456d856da07cf59de97487072fe1a446c8e22e6c5e78dfe2dc95ef7dcbb9520ceabba3495d9c6f56d671645536097db6a9b8e9aa9ac7daa7935f69a6f70992b091943ce6b8ca773e0aa43572577cb1490704099bc2328c37dc7936758182000e19ed5ecff66ca5d0c6ca5a91fe36aa6770421ef18841a1a99f5b29ebab323bdd47cf5fca51f5cb22763be647d6e05cbe9bd462fa71e2c19d05b3c0329ab7a5ac4fa458a30879aa8c1a3b5536af2eb5400715448928231964e4d2d8b64fdbc0c5eb2f231aa185501316fca6d07026821e85326a0feeedffce9bca1274a77ce682a9d9ab5a0e1daa93d5d08bc7291dbe15829b4eb76ca7ebc60d9d862c7dbd86e3a337a429b635cd765191d74a4d52fa9b4a12f0d65d782c235fadd74ef46e3441264a7d5290d91d6eece78a0cb38c83b18d425a96968eb4b8254648b15ae9699ca43bde24a90443c6e05b0842bbdaac6448fcdb3d0ba93667597493bee287a47b47ce9f6e792af3fb8c24e839ad35f9456dde606de28d418473d46311037d5c30358c7916f877e769a6a1519db4ec99fa2d2e289468bb766a910000fd71fd01676f8fe269ab9243f8b0d74611ec4a4ebafa18ee1603c32474da1e3eda9a57910b57d85b7f669226d47182cde2433b5b613961e4da1a6038767decad62303bb7a8996528c46caba5e360f3b97f5885a9410c86f687ff8fc2527250f9d0bd7042c5ec7f8f1f69f72b200cb9ab1d74af5c1605f19b18a0106dfc1c3d9c51888db12a9732d8a0ac4a18ddeaff383445fb63394d44cec0f1e3850dbc2687ccd11f932abf5c8086b96717bcc7dbb5338da6a22ed35f07dd9dc36f60acf4fc0ad69bcf95d350968e3d4a4487627a57eed5945faf94721303c9270ad79e3143a7649cb1724b358e697c8ac70a14b105d21d29af7ad7c445f0c9558b25aca616620d66637a896a21d2d86912a5bdeb692bd21f8dd6237c2eb70132ad6e97ddbb25bbe23d75fa848615f2fbf998a199e31519fbea2db7bd311e10ca26003bbc4df5c4bf84bfd3149370a6e0daef76c59e7a7730d4c891bf347e8cc35d864b30dd0dc72ae61b2a83a26280ab290559f7595c743e8a78b054313a5f2fc41da5fc60101267ee07bbaea42caf25b2553cc62278eccf9bf0b7b6e3f739db078cb78e6499619b8a5c71be54d0673845f2ca04331b5bcb8beab78a480299561b1f57611adf31a9edc0fe8ec7177b63e5828b8ab3735bc4ae781305b4203f53040c2f2d7cdb06706d6b1ab1aede9606b100ddf7b1a8314ddd200b67c51466a6cbd810566907d484a61c86efacd1f3abe1c8f92a9a6dd978af58135ec4cf85e7eb2d0f2edc745ab5be4eb7ef47257c4ef39bc5d333d719dd88a3c74eeb509a358900533de09eb4e8690806f362cb2f78ab9a792a65d3fa560934208ce4cf68da1aac757765157d0d9004804ddfb254b1ce76e0523b2fd3d83cdd004b15bb8261cf2d16875f3f65e7de583172a49039d5276560b793c0a71aae33978d56020abf02d66b70ee0a88a5d02deca6dbed53588f1f6922d9dcf874da1e1497a5ae9baffbb2602b45e0ed398595d397aafdfebe5806c0f596e203029b5de38b5c7a5ffe00847f04404fc725b20434dc2ed5b470d56db1c39dc80d56a951df5b7ba3aa497d5297eeec7a673f0d4904a348b902bec02480193bc278fd6e7ded2f07b9fe1f4dd95f1dfa63f669b53cbf385599d1aa2b9a53a823b00ae82d41c215de29c961f56db95498b03724b9fbc762d9aac7d961b8df7b9a1518efa169b060e7e41f990995e7682ba365f382816c8cf3368ecc0cf9f7b7fa49fc5845ad3e98c57cf1a19acf7fbe3dfeb34eb0b8ee8fbc1c2bb7ef3be1362b846171859d6113277c5933647fd6fd8b1bc8dc2c6d8e42dc68692390babaa3b09b6e3c8275949221453016d079a457e9855101d25cc259873f9334510f0f0169840c85d148a0fa2036cf3f04a38b13d860e214a429b92f73748cce24e56cffa91fcf2f4207be70e2d056fec9dc3f81d2cf2f6351d5ec3cd4c01ee3e8c58ac2dfdaf031992f21ffb514f995a7441accc2134ca29a0b0e721a964d1a302a7b5832b9698a92f83ac50087fa70ead562385cceae94965bf4bd1e40054c969fedee0c73fbda48f42f070e5bfa98dd9783c1ebd1aa9da6de2acf0a284fd691da5126bc77b26ab0ae956a31551cdc0ffffe9630a2cc0cca9dafc125604b607b33cf40db292369f8577a8679cad4b7c9d30152eeec7c00ac196e823985b8d27361ff1b0f2bd7c9f027834ec0dacad695abf774879b661fb77a65af20d6a87d386fafd64f218103a8d30f5905b4d2b386a2021802fbb8ce30f435aff6a19ba7ab541493793b071fa215df6da81567345f3d1d1b9f098c2fb63338975e37525797c6d2a7774c1ddd2da3d0730a80521077bd6edcd4670039d98e792f6d98e89224eff20e05864520801a1b1f2bf3eb4dc794eea899bee13ccc5d5a5328060d0b2bc53af654fabcf56a76b36e68a7c770c5558b7df0869ce78fc836a2cec7e67afed6162ff257a89f2eb12f36972a40e9ca0e88281179a861484094304c8034abb01d8cb0eaa633f59335f58da35be6100de84b8af34a9a7a80857f8c43f27ac19d8fa3b1e9c4c5a7cf68053aa4e87a6bd916d6301d290d038f0431c28ec38b78da5d9be2daa819ed2ea76079dcab9450045fc661fe036d9b63d8c31a9d6ca2b8cce56ccd136e81f781e24bc387dbdfa4c9cd5b632d4a0a3c6423b39800587eaeee3c8d23c5dffae1ccf7e89381c7b7af40170d29968ee68cb002d1866978edd21975c8e5a835ae7d1146f998b7d6c2bc9463b70caf4ff49c244c1173f9b9e950d627edd5cae3eb0092130fde91174dd36c423d96238c4b0af1477e00298bb1535a0273110eb0b9a2d4ec9cd4e42b9e86ed4c170c8e8edd2aa29536564028052e53f9acca43320815697f1fa8b5b9dc8116131ed31ebc54b84250174640329257329e0914229462b799fb09404b67932e95f8055a97fe60afcfbeaf164b7b38a0a79f0ac9218aec0e07942eec2e04f5f3e1c79eb4162d297fb43bf7bc854f8e3674dfa9edf173376e8ac50512eb506f465b3396fe3281f3fa92ee2aa6bed1b221a7baf4152764fb168751e216ce04d966bc04ffb494fe6008016349571a26cf2ec571ea37be58c98296ae442262ce6ac41b256f273a1310e9acea82bdeef3134169ee75321e8fc7e959682940eb6cab0caa646ee37ef63fb1846fcefb281ac5641e9ba10e70edae8b92eb66101064b5ddd0f828555273492a89a08c3db032f0686ac5e1c8d43abd777ee3a09b6dd93aa6215b7cea0653268f8b49c2f1b46cab6e0788b0eebc0aca0ba3f58f825b736bb5031fa4bd713c04f02bcf5cbcf439c75359188b6b1e2c9f1f99af10bb425d2d49f8af6cc9cdff844d3912aa59ed8d8b0a7abdb2e00a4400c48c140c15b60fceac620cc35f20f6ddc8891d99daa946a876aa485c3514241ff4397731445ec9ac67815c2d1a24a5e1de1d71ef641228de34831eb923b4bda27fb188a78b80c7cc6dda188dc5f25104fb1e66bdbd917d1971740f43cf99a80976d267617e2b984d674dd5d2f15199cb97b5c5d724f5e33827b8ec14a8e9283a5ccedf7fcef8b3384c74aa2c4e36d32036ce82478d9eff8e070b069497a80ce1079108321f8a4d273486f3f981b7d405824a8effbd4343c53e94058fcc57505f4cb428435d122015961d1d40d2df31ad73a65dea329b6a76e48240166b1d0e2c85d83f8eb4439e4446d8106e1e43d7ac5ddb3e034b4ce7db5c3c3e8f7a811a2171af0aa35295881310def98ea8b3ca0e98f2bd88b2a6f9528074737eb7f9e577e007a694b3b83e40a1a8d2b0fd1994ef91b9a67759e230c06da53b0c16d4749af6749fde0b8fe1d291e68d1eb185a84b83863418c0dffd173ffc49307b9121cd6a8eced3ce6f0d9d112dc8088cb8adcae6ad2dcc3205e86d081c8628a9d60ccdb97a9b8ce6ee2f1ed57680c92a903d2f578abd3391820c93aae86fb3a08a00516344692de11387cb5c7fb3d0d5a6a66e6e58fdd2aa7c5ea9aa6716d68685d2ea5b8e4b001d3f299e108e3dfba8e8ae7bd343cc2563b231334a2eadb44222c4b7fb238f1a70868ec721d077f08c7d5de6af915d44b3e55aa8e2a374997c2ba4dc703073afa0c7fbc48c69381ef048b8be35faac45ae2568390d5e12e05ce5ee687d8d8f3c90a52edd72df6af5976053a4c24f023995d02dea832fdd6a768cc9a73cc27e12fd3367b68af12fe8a7f43776829830df69d66fa84826b105fc84adf5afc6a0a4dbc508306d062583e7fb62c4cfdea1807f6819d40db7cbadc764253a71ae75e6e9ab4b377733534940af150f0649305779aff699c0cac23ad819b7927c4a009e03b2949c48885ba0d009380f278ad901fa2685f3349ddd39b4c22854ce0b3aa4eeef056979a0d019a88438b4e16ae6e4bd7a0f1dea7ea3b4df9625b0ffab7a7e65f1df3eeb713bbaf2649b36244d6ff4b1d185bc423a572f869531d0cda5720ed51feebaa3aee028b5f87a1c42b4f39e4d2b5c1747e95e2883cc8b894dc8ef69806fd808f95df787d023226c06ca657ce3b9fd17864c743e6d24a64f85b72aac2897971de5e66873fbfdbf1b0ec91209d6c1a546057ebbe768202cb159e51a288b7a18c8200280d472074d0e55aa5018aa443ebac85750c1cb74519c9973637a4641f379e44e4a04cb5930337bf4d6404210aad5db5af4b1056cb51ec8b9e5c188f9e4f3c3f33ddd6e052fd6c14934ea984c2c31066dd8275dfc4aa194d25222ea8cfa31e93793c2c034709019a930d623a43563537c2d38eb61a1861a59e90e4eefa4b78cee46b2f9ef3864c1e68f04b5aa16ab3fc2200e84bf0499fc8778f1ea931156267cbf89ed483c072703aa4fd7340cc53a3f1ccd42889b7ba7cacfbb82d220e62d75314e779312e2d693d6dbdca422c686941817793dd5e7a31842e0c111a5c351a671c65a062f056537e4e9def9f1a5b3cd63ed9ffd3b6875af53a1ba25cda9c673f38c2b02fc644cfffbf37b65e178a2b9aeb962961a796147c436d51317115877879ba374149278b6b10c31d0cca967818c6328f7f10ce26b28d1156a3e77c21e395812e22c9bdd28e1e4bb360c7b2cac99c4d8f60b7ea88ab1b34d91bc3461af2855dfcf886e1cca278d40ce604714d6734647c2fa280204fdb41c69c544f2e7c8be1f1aa10829b1afc13c9fff0f42b9f0693fdeab1530cd0947c2bcb9b0d0730fdef7c13f7824ba93df57f8be50b6637e4cd7dd0156bd9417568d5e5e8cf55118b00095ce6d05469aa8c8b3c86bd5a72c9896574366ecc197affb314dbedf636220f6d846cef5b0700f6341dff0fb99a9c9a575b4baa095dd123154f43bb3cb8e738ea2c3029681fd4e286a5d037043bfc2cee4c0eae8944685361daada9d9f7d227acc8a37e818f48a2f166b5bf323e65d04e1044ce6a70612478614646818b6fda13140e6eb98b2933cb21fdf69c2522f11c766ce234b52ac713df0fb4e14bb71bd288c172a2032c751d9416e3231e85662aaf4b1c9b6342fe8b9b48d305c303804dce4db4098cdb382d0d6464db11013035f07979905484fa4316ef7b3a4695292798f680aa375b3f837c4c89eed1a8996be1c85284c601497ff22f4684e42350ddac7e48f02e0bfdb2e90abb4e37a5ab68938565c89c5443e8427061ded91d93fbeb4b4d95eb2a4c0b64961a87a018548139656a2e05f9a86487d042e01f4056a800d74a8b5d9b7f2dba6f8f7cf8fb40870312d92c1104eccc09052010d4e160cbed6f8914772dc2b27f613fd12cea552584d279297de89d4d7254b3777df7c872a47a3da8a7352329d9bbb6c05df5531efbb3df21c69e0071635ed41167442009d47690f8c5df284dbb03f558271b13c802c2afa4f807208ea89de49fe2f50c8ef6364dd2c02b835ba944dc5d150c1750359c6e5a49993110c00e69516cadba8f618c5d7e4542740482ab7eafdc2ab06e4744dd72dbae232ee90108c27fe54f4775ebafea9776e29243eb0d7125ceb55b49f1956c94ba2041aa2a7acc4eed84391d8eb443cffc32eef00eb810a6e45a70ee15f9aaaf7337cdbaa7e69f1ff59c0cb5251c132a43cc4b1dfcd90767135a7060fcb6052cc6c816c1740f187c670bc5216027bb5bf9da4e7d23a1c6c694cfde7cf5e841cf1d947c560c1c362daaa21a7a4fff70c6718cd54947e3cfa04b7f4d65138e3b36106983dec753a567380d2836e15c499bd73bd79d7bfc76bb10800253220cd1739a593d8a28dda7d09722cb8d1b758827c3b364d2c5b1ecf1fa781e2a587fef71970500249487dee427d399c77041360411a2abf0d87a51acca3b102cfdaabb190d270dc0a90e2e9e220d3743ec144ac6cfca5a62c196d88b2354b8967c5b1aa51772ea28e66426508bbb28f70d57a866ace5bd2c01854b9c1107073585d22b33a7ff7510e5172e0432ad597848f8260d7b6e77954110f81d05abce32f7f468c733fbdfa6433657d3a1416e21f1adcc069e27dbb94bbec8f14b3a1895281bf310d4f9d729972cb4c9c7b898f7323aca5c251fec619751a820257c177ca7a164773219b4809918f10308d3b2fb2313fdbe5a1078932c24c27b75b495ad1e786b8f25cb7c9768364657966b8b18d334b26ea9790d8ccf9817969413968ffdbfbee41a581e9011540e6b471d4d20b5b1d9879413709733b02d77cd8852086f4ff3c007d041f6c61c9034870507ed316080f261af532224027bd0f22179470084688cc6067ef9c96e2394c522c838b32cc853788351a8505bd5c4113dc2e346acda2a010ee34c13a6d3220cda03b44b6578119a8f3b1cd7c65ea3f106b4270500dd69d68dd28ec931c1c05351a4f70d363f3b47c9fc071d3c59b43c8bfb3f1870a941fcddeb1739068a4c11adf03a8afd1db85c815de1c41abee5db2dbdee213fa7ae57ac0633149ebae3dcc7ad9faa0e2b7a48be6a41347b1c35094d4131c8531bc370156662e2dbaf6e7dec5559fceada86d411886de7e73656437895a6bdb0db84872a230bb1edbbde453fafc421c002e3c113822c8f7c30674c4739c84244ab44f329ad32d5730d8bcce34bd84071db067ea9d92c0093f92288e44b69511a0c7591149a4da75144d7d81ffda7043efb4bdc9740c0a0f537df3aa491be702ecafe432ef400a1c7ec8f63e5a7bb307dac28d6a680d4591ad190559ba38516918b01fab7d32a7d3274a82aa88fc083162b60b7cc8c6369b7b7f6897553f41454ad487dc56e4092fb0b8a0f587e7be2e9a64b807118612446dc8352aa8eb81c808964329f1408a6a95c07d5b9323b54ed92d1ed88f0fabafa21e765b3d5bf5647d1db8c76d187704ba4ada1fbab103423d982393f936b8f5a263696878380d268732c55a5729c45eac6b809d66988ebd3bedb89eab49a7cda1fcc8bc3b1342e9da872e886a8553487e0ff7242a1f96f3c2a873c441e926b7e13f2429e96b7afab9f1f62033d4f55d848ddd32470646211da7a06387b49b2dd96503a6f37c392473523b3319872f3ddf5c4850922db8968781ba5f0198d6ecf907fe8345c3b6b3a6a75bb9ec8e8d08a925a7a9c3be3bbeeb36dbb12c7522f6f6a678749de1ecaa394e1ac1e531b67907af6ba0aacce16e64ad01f984a2151674246f9a06748945d962b7f8d500fabe947973c53fd8bbbd5b7b3ef8c2d6ab2555ba023c280b55f534a9f375cccdd5a80ff26a284a7eae486b4f10dd5133ed978c097598bf166673b56c351fbe12bc179b01ca618c89a0293693524ba3681e4d0cc650a8332983e9de978bbd2855a1c3cf480042b6812a14a6dc3fda2392f084453e3768146a75941fa697f5b36db4b1a594b1d2cad41372ac0f8e53957e09d0857fbcd713006ae5cfddf01fee067930a3a45eecc14778b78293f32e5cee5b1c2a8188ac7f196bee750d25f40377d250e370be44d449cf2aff55716eaa57b9c93d7f849766981b1aaea9d395ae0759c2350b57269c32c29d24781b15da233b24541898e3fc08de406694285561b7647d5e457ddc3e7eb1be89b63f29174c182c540a218e6c7775fc9acbe1db23327e5afe03e3fc5474b71087def89f85b77b3fd77fc9b8f4487c493d8103fe22680bbd78082fbc759e9a8dbeaf33ff27ae66d02c86e1b4393eb6e82b71750f58574e6047ba80c05d577dc5149366edf37ed353b6a147489f156b2dcc78f72f5fa8d000d8c4bd4aff32031201c81be51822752fb2d08e25727ba769f6adef6236a07aaa06ff7d82a41b1d25c7eb622e487009711b717b42d5ca15cd10fe7a0949ab6eb0bbf145c7446250012c85f12f2bf243165bea7d4d0fbc37e9e6fcd0b95eba1a13cbf124f6a2d660cee88490ab2ecd64dd483ca3c39fbfdb0cbf862c0e8d0348a03ddbba2b3aabde901bd51b8067b6b8fa2c1671ec38db9aae1695d77ada5e2e386c8504975c01163b5f863bd38ffa225fbd735ef1d0cc6720a801df2aab01dc1cc0453abb1e19f1f3e327468d7268ce7bcb39b05fd520b796d9accde3c715a9708ca01d829c69d0d7ff83b0a50a6e2e181fedf0fe65e778fb45289d1b1c9c8ecae8e3a901b27606fba98f77f71361813df7a1b061e520c024fc301e52b6f39a6834cb2a094706ffec482992e1bb20862ce46b0307717571c999904cfb82a0fb3d01747e39dea5bfa5feee81178601099e70cb6e90b8bae762f33059b1f3f712e4f645373cd0c226af6f8ddd1b9244910eef9c6b9211dba090acecea08ab5e66eda064453ca91af274e4637922adb7ea656ca525591f285eadbb90a1d2e3df2c6c3f98aab2f15a40c11b6d1a0046b45c1c8e68ddc0fe035d98ad1ff98302229f81f1483f988f73bd866e8925f1d6b228b311cf65057d66fe159fd27bb4f7b6512aa7519ffbfa9c5bee38f6f1ecb9f876436e19dfdabb0472e8f5af6587ab335cb92ea401b795cd0776ae043462874a66cdeafcf9f025d647cb5594af3380a38ccf74e839ddf6ec1ec650b1b902327af4c3e6342ae76991603a217dbf1ecc0538977d7fc5bba034a31f113497879ec689a0de5f34cc61ea93257baae23e38a557f05672356bc0c76c25a7e1fb88b04267b5d5a1b5c9f851acc66df4f52555a8094f9f4db17c2566b0104e91044f91ee1ea685c1263cd48e2d3855de6c4e4254e2d240fed193d544d51653cab33be8f8bc120c76b04e753e94a779aded6f1651676e0033408b26fd224e75c4d67d64ba2ef4bc9ff66a55ff9dd8fdd92d588f0bb8e1548aa2a0d0d04cdef98268d0455ffb39db98c61603ec1e438752dd1b32a0987c1196391b78ec235d3f43be2eed17cd50f8b7f4986c35e84b5e8894370fd20e1c092741a73abb8ea85b910fe01647c70abeb9af90d03f2949a12dec13a2ad5ce0be9554b784415b7d81ac897fad0a800d233ebf44dc8a2cc5cf5ca218fb13b2782fd42fea46693c3c5132bd9adafa3b0b7a5823c61243a749d58d44baea8cc94f737b4f9057e2906cf7ddbc2a165348ebbc924f5c187ed27c94b6582996a1eb069862b50077a9ded698aa38af95b6fad4e4d5370fd6311f0ce34a27ea913325752287bb23d2adcd6a297875357827b4ad96f18b7ee747627a7180509f865fddbf5908d496521359dbbb237aaaf119173f3a7033fcc83fb27c49113ed42174bb8d98ef8f5b279d07b4a0870f263d05808302b818f68c06b641535d7990d7ce76249880746374d1f8ee9f3d0b5503c219e8e93a569411c401eb6aa254c0076fe0f2a65a688b65babefe848f20465c3266438c62f8cdbd26398833c4ab7a9bfafffa561491741de5da5870b500f78e5ec0b4a68b283051d7c18f967eb002e2a9d40ffeac825208f8dcf1298b71003a006ca29ee8b652a4798292eba5c90d630d41fa88baa9ab8f2ab2416ff22639c677afa8086efbc5ad5b3d4d6ea2502c22347891c5e88a6a5402aa7c4d97751ce52212831f12b48e906a67c1f5cbbf008e094257ff9483123b77cb916768165bc0fdd3459bca91040acaed593da4d9b66f194434e3d2079fdb874b147f35cb09308a18bf19647f484607894df0c0f2bae2f560386da3c57363b1d9c56284b1beb87f0361efd80271c6d9862eb7aa4130af6e47e686e725b4334ab6a98285c0bd0f091e88a6fd8d386923cdcc81aa2f845cf8e25a531e73df8de5c12928471e3c1982234d5b01e469452744ae48af6a539a3a32946a7bcb7f776ce381538bcf1f589ef6ff87b5db9c2407f5f4fb491a4424f64d50b12ed88052cb20fca027091a8dfb3a595b6328e9190a611790ae7953e5c608f0e60e40bd6b1ffc84927946cac21904a95b7959972327f3b401ebebca7876489e7d0c8956585a346afc85474162e8fc2e051d2e17b5c0b71b85c48cc90ee7f6e43233049595a3dbd5363fa202165dc3f040b907842629f598218f9dba803521490cfe62afb7396a0d37ecee3078d8627d0291dba7aa216b4ced6ccdba68d44eb55b2b6df91eddfac49d997874f9cc89a14b5591b591c9492d1bacd02fbfe0f7b9b8fb80d18f2ac13cd9903430d258cd3920542c2d5724d4345e8132ea6113ab2330cfd2dae657f7f94556d5cf36e0567bfb0ac3707c750cd8641495e357de860abbb3e96b3adfa9af78a9aa4dfea8bfa8e8836601d0cbe9e11c84c70414daaa77a2f520714d41e8bde287f6969fb1d1a6eb154ea426bbc9e8fea8928d820e98ddc14eee36e6c92636aaf9e0bf80a47302333960bb2f83c0754956c517c4443a4d8f65143ece52e016f4b864b3bbe7d1c473406c7c5c051a66d9dd184a4203c51c6afc94b3abc571b6f8431cb0dc4007353dfe12cac570a5b5f3b5297bb6d762944f43478c994d67f984cc1dcc250a9d2dda78fe5a95061d90b78d811230d7f57879044c50457dda6a6984fe8c7a335f594e55239f485473c5111047e637e25eed297c25b8c05e2fb83f627f5540ceddfae4c46dc362dd4ce1b786b973618ac31ec1c1f56b2c0fa53134fea5bea65a5bd7b008dd48b602a20c134a5d517fa0d89c95466d78b7ea4aadecb91ac18109feebf586c373fa80eb4c870d7eae889381b1100b0fe1ce27b412af9c388bd70d5aeb9e9b74806cb5add250b58bd63602574b4694c766103dd87efd8e40b829e68da062e280c8f02fdfe3099e49f083e6ee595956e81fb8c6aaf8faa57861166927e1cb1a69c35fd37afc536f957d6932d90e574754a7702dfddfe77434419fe2cb9f7c82e848c29798b9ed93cc78d0a7dc360e7b371264b733ce2c6edbf31108f6c761d97cbb26718d9485d1c9479f4ea0d2f536a8bd171bd11b82b51816f1b5ad102c8539adb26dd1ca3c15d3deb06e1e0a5d590d120a81a25a487bc31a013cead96e1b7daed24eb8baa5907bcf1eae74894b5f0b260730e40cc7aee883127c0e193b9588214d2147fccf4f8f915a16beb1279b35300a8c66ef178e9af6b4d47bf14c1c4d09712a0438086c89d193b365f254260ad1c824979523252d0df3d3dbba7573f70f3474a5b712959f26882c219e5e45dfcc2a9014dd58eec34faae6bb5014211f0cffe84304edb452a59743cb0998192658e8fe0bbc83933f1b72a8991d68f1cb4ac9995d4f2e618524f7529f2c89296902fa79be6aa64d8c5ec0330ec1111f4e2e57d31109cc80972599fd5e65807014e168f12ae59c01f3d78d1e3a1144a7587ada6538d3d857e8178d3160fe561daec5956ca1aae296f229567692da8a0331fa9121c3c9335db9e84a4c6cc6e5b34fb879b2b00192aa59c8ddea61dd58056fc666ffe1734190dd7642562f1002a42cb9d79e823b799d265ea9329a3831b2a62ce72545d8b3c37f4f4d2f2a365f5850954818fd475f87b69cba0b7bbdf4cdc5a637a3be1aac4ad035ff7dfa3b705840351f448fb334b58153df6b72ea3d6804c15ed5d133ff80015e45930336ca5f6e478da9f5e91daa46e2b1562f9d85da4242bf052fd3a339972aea4267ecc71ee888aa30dc5fb48529637999ef19cde600e8f233172f2160e17ae8d1a9d633fd13d158fd93b967d344cf63f7f0ea56e041509be7fb1c56b7a5510e2d84778bc323adca901ea4b50deca86aae91b4f75ea78c9cad11cbd8762d161c07affbd218375506471b47c286dd362bd63ea0ae1699feac4e8fdf43a5196b56f929d1783d4bd71e5d7b820d8a2dcfdd0b459e525b0fc0b787036894906b91e2dc53f6c8046816662303d9da992d12963ebf3db9a385d0f1a5bca434883c6a38de547bcd6abe0a79a2018248a660c8751ee401bdf1c30c72e14bef6748d8e2ad0c9e8129471904439dad4dd005059d3c483e23f5b1fe60620a62dc3ab6cda4411cec52a4e24c0a73464e71aac41b68ceb393166f5cd2480781682f5aee5be115e0928adc0b68b8910bb65a2b878ed185b0dd4718335e6fa81157944c7445583115f76ec2c2d109309a9d9acb6b7c417d2e5004934657e013123eb2e3ab0fb526759f56d16c2fcc6867bee4d9858d35173b20708e5bfd181509ad62a8cb33dd543b8fa98be74bdeb432303a5a96567c91dcdedbb36682129858cc0225464ada5d946e01ad008296720ab8cf75a6a1026d06963cde60d87f19512413c8a790c2907cc58f2a6424c484d0fb923075f53e9e66367d17bee7be6c3d80e53eb5eab911380fe408a0e57e45a0cb6196acac178eb1d64f43ff3b647a377018035cc74f83570da9ff202fd43561b7f675304fc239f7eaf6d0303f56009277d17490dcd0bede02b35f7d79f1944c6f006a1245420bdfd5b35f75ce5c77e4cf1e8ac77e8fee75894ae18145d7a333fdbf6590b0896117828fb10e6042639c8770cd0175c78c10f2b2ad2ffe3f73aaf7594dbd0382e799735e195985b93da325cd1e95cd7cdb788c7f70502ce0141d79974338842820e329b6b9dad836f5bb2232bc672c412f2181273fcf1a52364c4e4de29937b6af58e1ed2a9ff7c041b42f2b3b9a6c6538026dc83ee09507b049fe00df96e7856a81936c3504eb560d151f053da7f85197fd784a98383cf976cee51c84ac74c6c7c84fde58158676de02e6f7ea295a1128784ccfccda25dacb44afeecb8e3e46f9a3a61aa740e3bf075124f1d20804fab88d9f91f5840b8234bc1a31bea924a641e69d17d53f7d7ba57c03cca2a7115acc7c624f7adb61c2b2f53dda8a7ccf52ce84580669345103bc37b3e2d2005ab52ab2ef8ec9fcf1ddac381c7a27b7bb936293dcb8c45fd9abd517f3b0bb31a3c1c64b0fb21cf74fb49e2d8920c3c156151b95261502018cec5d759acfa387db6fb813a05a7505358f3f9ab9b4172a06fb4572ef14a360a61f24ba5d99678d179ae28f3de465fe15aaf3a8ee1c878ade9806181f144155b62d977bdce8dd495fe3ccf61a0b07552dbdc8e0090aeb9d805c788d6c2eb658aa806b40a079a2ac33956b8f573c6cbdf2c01dc84aecbbb8f7d14eec377bc6dbdf20b9725c58535d6f454bc8293aeaab595999e742b71a8089eb05dfc8c4f9c9927b4ec85bab0e905d2c9c8ed9c786b2edddbdfb102d343d4fac28846b4cde76165aa5593ba3ea94ac14858758dd25a407337c2b2b1f79ca87fb9b0e5bc8d621bb73ab4b61daf8fd869c5c27e26051a6d3fec09c692643a854ef3a948a77a0c3519acd517de3b424a4fdbe72864787433a2416e31053f665127353af6f5131975fa04b449a16be919f63837fddb4fbc43ab9046f09cd87ee85f82921e59b8a33dbfcdaf09bd27236229b2d470f12608ad2142a302fe11aa70d033bc1e363c157898b992e771f0f56e6951f7b3bbfba6320581c90f4ff48bf9e9e5dc0c014f14e8b544403788839a9eacf7e6c8dbef3e785754e82047126b319b822a04fcb6592a09fbc6d460538708c6e468511e441783614bb3720e326a61e30999ebbc6bac41aabcf96e61e90ac09e680b3e199f5fd4ac0fba347de33fa24a78581011732861953393f18a4b08aebf3a3748ff6157484b6e74e2e589165e37c5601a2c3706745728c3074944c5a6ae14ce402fc5bce57357340dc915297d6464f8dbe44f768e80ffa52fe516b0a2abf836e28b549a9b48eaa2250bba745b1d093d30f5d90a8926e46b7c1ff7cb39291289337b957ec2b1aee3abc7824b9ed7d790fb5ec278ad1b42525cab632160f38451302a7c1c675d519811c0e97a54b85e1bac3aceef7f7b12d8195b830421711f3b320b0d0bc4d49ac8f05b4bc505278b9b7eedce312fb2a7182e24f669089ad128a73a13fffbbadf846200e03029fe776904e3705d9a5211055235452b7b36e2df84974d33befe918cc40abceac5e360251f529384e0fe0d77ea6ff9d719e2c9483c86b200587095924731a4c5ace86bb3a939cc612ed2a8545dea0eab34d935dc33961840ca0bbcf3ae9f6703f8dc03fb829356b2f6bff86a0a939e734ffd998dc910d15d4cf20532b8fda148fe7ddabced6c84f0e2bdd19049708d57029aeec817ea3477ac33f595c5954d12a34cc8a272acfff5cd1ed599dc9eb9db604fa919d4ebe0715ad439e5aff0ae597fd1e832cae8a0c68ee9ed947108b9dd7bc11bcde3ba0522a5f920327916d2c93ee7d04d3879e0ec21b6de19bf2b82f23c9b4b1ead0245f683c0e58e94f206589e481ed824e29f3ab4ed07e341a84739576d793c803d20f8d6cd34793e9fd739b9efd859909724ca25e5a73558971af4fc092756bf3d53d76bbbdbacf05afdbb1f44715c04ceda567ac9cc837c8404eac756d7d5e5880950d41db4e86a57179d664718749b2e1b76e5f45a8368f3d86ae5b0b06c9f4977ee4ae7e218631117d6de04befaf256755c9028256bc17e13eb5c72337f14c89fc2c1d709478834b008d1895dc4e58ada9b321a17704fc82ca90333a74c2c4f22099f721738fbe3c29b28e7c1e912b56a4c0c1c57e584389e18e47ff90f9955c93780abd447718ab08e3cb6e6107aee8c7ae0865e50d468a44d6ca9c76af76503a04ce1cc6a67e43e59352b0684c4214b06ccb8b74212ccc929405846f3a1c6ded5af5abc1da30356cdd361219488078a200a54991b338fc53840a71617d3a483d6aaf579726f21314d49e2b8c764ea48910fda73d12c09fe19d7c02c5a72c501a6ab5cc39eda6ba1ab3b75e821778d91b754523c01a0ac246f778db820500c235534db4050f92bd15ec79a013b796e86b3bd916ffa5378ff589e556e5badcc2581ce582d2cbe6dea46e4a9b3ece3cb32e8ac49a23c14a0d32fe0bc91f44141824c7adeb67d65930c2a088834ec77e3129f1eade2c8b61de9da6255e2da1e1b09ad3827e51a0f2e4b55eec7b8a751560849362176b6625e228911d0d4ed8125b56754a9653b49af59bd02bba330571b39e32c60c1557a1da515dac9799b920564e850dada0ca14d7cd9a002a854ff15ea8ff191df2812df375ac5984e1b2d749fa0c358ea83fbf87ea481d46a37ec2c97fde71cd5c5e2b574faa239381353b64b27d17d900bde0a2874114966aa2d62f8520271723946d242487269413dc0276fff6e490508e0323088685d15e60167d74de56d8f0de06364e5bba3156a5eb127b53d4f2f0abce1ec557a1d6b0b61379766dd1a413e8f955bcb0bc15abf9414f34c328c5b95a05d67c0ac3bc775daf0adb9f7680fb8034e5d66b2508f58985bc226e04c8459a73af9ea6d700e2db27ace0d07d109ba845965868d4aa6fdc1f53a793817b518b0baa0692c828daad2b5d0f97d3e2a6abfbabd5a965004bcc3bc05c967cc91f332ff830bc879c4bf847030299af6a8d083ce1aa2554cd558dd55451a850dbae585df395b3c9123058f40aa02e19693f3ee1067a4abb63965f9b83c78c84f449aa93b9944a5c0afd0843854787ecc11f08cbc778ae9a2745b032d3ee03d7db6ae5dcca132dbda94ffddadd070e7f8c6a80f8f34e00828a30018a49e3fc53401fc6a78175aa0356f7d17ded11239ba3eb1108a9c929e1730039e9b9cd167ba8a190e495861f5267c9d5a3842a1c7ce165201d3858a7768c5cb95f10dd42108c79d4dd59db7a6b12d2d90a3cb7333b92cffbe0dd19653cfc6d48d8bde1e533e90568b42f3ba1b122993c2d0a40a75a458eabe97515f51d20b785193e64a0f4811b88002da6318b1c87b79098e968e188d3bde145028711abf1c4775abb80191dcc8056ffc6192d5f1aec2b65eba9bf5aed1d16c462fa61ebd1c5956ff10337aa08be3544d6af95c96185ee4bbeb7eb57c5dda89304f56becc2813e0021bebb601bc2a22cf8910653d74037aff6897236d0a958520d8683bd9b9aa055d4ecc41aa28d4a97fd2ca4b281277de322cb6e3bae37b6887983bc6a2169a75aba48214747edd721cc81e93ddb07a9424fe10e4bbaa7ed8023d55a7284d981ef76e9c40e448155a60484646c61e2dba1aa4a2ba9b462b83c95b3ee28149d23836693ace2912d0c59a5042e571022841d76d5c4adf9d4679e4672b872cf737ca21a1e3a5a6496ad9920a91e463f21a8605d542653f2b0e52c0eb39b66dcea0b4c89ca5e9d4d14c340e7d77a50a225d5c77936cece0cbf4e99d0b7a4e9dcdb9b04693cf8694dc272147778ca89eb585728eeeb3beabc7f227a264d88fd15d366327898850ecc4e9ecbdc99a9bf7312c57ca7ad1179e1814a6ab98dc1d20ad4dda969bfdf9dd48ef22dbcba050bc699f61018102019abe2ad71da96069580016c480dce5d97225d1a7b44e9b248616e853c790ddd5d1213d9abae9319dbecbe2a003dde97a79724627d29b4cfd13e861c795cbbbed9083bd7069272bd0f354dfc7f38b4e79f5bf3b484fd9ec050c879e8abbe0ac20731e353d2c98f6da5c21ae49395b61cb4566c49f402f8454083a0ffc33de381d592082f5e830d5363450946285fb4444f190e0715f7a772a155ba2c12f7ab839351a23cf5fdd4d5278c8ddd1e14ee43ec45170ece5f8cb272e57bf93b9bd4715c8af176a3edac68f3858cae1cc22ffb53c4906bf787af1f7887898d0b872b05c8493901ac1a92d9af03b5613cf3caf012eb3c047679a89422e4c89a1107d88cda00b05473194af29b367f56ccdec63f96d684094b41e3091388d5b9a61c4084b7b3de5d51ca92ea20a1ad8badd2bcc5523dc815ded741b322ee8f539917c66c5dbd7e050ec6a12c05e271283c6447ec8405efde43c8aba13939ef0f4541368cba50ce400dca9fd96c628bfc5af660f7545bbf9b4c76fa7f1c3e1943d968c62952aa3988a8a5943226e08d861b5bf288a0c63397d7af866602787850d406e78adc13f11f021589b5d4ce77328cd798f4a598954ad30f8d8bf7e67c4dfa513c101c2d162745c8ab62abba098ce5b1708ae6689ebc9bed9366fab84fad9d5eb55a60db36603eebb281e11feacad87c4cf916649fdc4b896b586b896b7547601b3185f90d056647329356687111e38367a6e2eb9042f016abfbff1dc507191cec194f72535a357fae680d5e601d2cb3447fe921d348a2f859cccfb32c0a284885f29f0e915df53ab2f4f85e8477d796b95a96bc8ac8c334c71d1d50312891cfaf7fbf077311ba19fa446dfaca4152a5317c279a9ed7f04fb53ca7d0ea34df189738bca9e086e2a5a7eb84ac65784d18be70befd195ab005d8a8d8675a550af18ba3960d6181ddf1ad8dab7c65b9aba2b5c2deb40532e9eacaaab5a0ffc1e88523f01de525a1ca4c0f0f91e02c0e8d0f2f71f74d105eea2c829c6e55720f13bf4e3f0a83edb172285b94bb071d0f928c4da6e693f8b7993c0c0581cb678cd1d598e4045a3ad0d828a9f6e49b630f4f00d2db303875df98b6eae0f88a1b2244b5a2e9b3ec3fac3ff07f438ca46708b3b9b9b31b7ad4b9ca3ba6d21e964469ad2d32e4e73cab76169ef28825a3e24f203da69a74de1984157fba16ce65cb7be6e29152cb151ddb730d8d8043d5dd6ef1c53112f30f6dd1f57722ed29b9b41e83f07296f264e2e56507cc5e52a4250864fecc948ba9c0a4207095be183a011a8244139b0e84bfa9807d4b30c9704a98fad7f84ac17a49521bfed1d9e500c7f1759fc7b162f1ea019668b95e1396ca4a614bc0258af25b7881c2d4310b6630ccee260e3da43a89b128fa6e0a94b094a1907a9aecb347b4e757c282ace68debb750191ebaa3119f5dc9014f6e298cc8b6e03c80af79bde4839d2ec6768c5185a13919e7a4fb17fbbf2b379914677b836e9eb2c443b4a304d30d0ec5b93050be68ffe9152c4fab927c6c3558f6d7252ffb56c119c07478e4f2ad3d99c1546e43b4c874b5bda754ce5a95f42b706934b8ee94d87c8a3f529058edefbe2008711a8195052c75b58c150c24a9a84cc2cb0b4bc183c703e396172c91a6c7144cf4c9dce387fefaccea716f25d2046597109c82e32ea0d299def97a43c490b38fb9606f3cdd18818f34121036a0fa76ff13622833e00d5fc2cdd8d1d4a904c167ad2b9f5144b4e021f68626cbf0959681528c7289b6a0b546fb0885beff2781f066fd6903b56d0dab9f2b090fba3f046277ff8b6478f8e1d3d0053c65f5b54427e316998f1a59958f0d66bc491b1b87ec569d5d5ab0db5c26d2438977b432283e7ec19dcb2fd88b253d4f850c9ba7906f321a31b640dd2de3ba3120f6e2c4893a11f6a29c42c748b81b847df836fbedbf5b97dbe43e74de785b8c32debfa379e2c41aa169af46f28340a6661f3b76812407b8d720c677427e32aade984f3c6b11f2700a4cb28ee3c5ce5518ed849740c6c2633b35d492974af21460cf94b85c1527174c09fc0437885f582739041b514a6ccd94592626bef2f09d84a1cc979b0c91899e3d624a07d069902e59593aa46cc12515b896332ec2c9602060316f3ac9ac402e0f53057e18ad9cae16dd9900b18db2b5d8564739cd9b77e9b501457fd435729b5384b19f62ade5b7b3d7642dcf60b78167ccf074da7127287deda768aa9cab85e9128366d2db6af577b2028d1b9b3cdb5c4ca6bb24d32dc359c1fbfd789ca568414800ac05f450bdfb7cab50504d189d62cbe8957628f70fc2fbeacba8197a33152b66760c01aab8512b35b69baff993951ac631135e455ea1b2881c825b1317e70caf09de877c1a288659a716be16e168e6d5b9d44aa4d7e1df14e5300e8a537074fa89ac0f678eea06904be428d64ce9328834827a60ebea01cae190b38aaf13046c5bcfd0186365d5deb516d5ccdd5875d6d309676f02760693d9d88e994c84e2b05e2a04be7c920017db11e6d3794cdc74e0123d6ae2f38705717f1af9d246759cc48dc8f176e735b747f0cee7b45b8dd93a7a231a63e9ed3d3eaf0e4b55bad5d9c2f9aea1c74ecb6a025cd14f29384273e3476c4f3398e3af06389d0af35d140a855acc81671687d60b2cfcd41267f0600dc6d32b8524187d8260c61ace3d0f84b323e9e8006718a29fd13a4bcac08a1f3082011d71524310c488b90fe566b991637c4dfc6fc292e05ededadc3dbf187017a2d21c5980d9a42e09d92cfc6324b066011bc987486a7f92674025e413cc468b6bf2b140c49921b9116da70bd9acca19020780955e77a26c6be23f715f93e8d7a66397fa45e4a70083a5053a56b8e6c256e5331ccefaef3f578cae3497fdd604f470e5239f6da348429c8c1a4bff1e4d1cf2980b20917ed86c10ece26fe35931b2ddd1f52fa9bd6a8edb34286d593a15a38df53682c6b08001edf3783ec069f36f26d249996b34a65e94b0fe2caa043eabac83abdac3819bff664cc6bf21d123b4932f06627ea918390b5cebfe671b86f23df3ce367541296da8448e087a39527c20722dae7c32e0637700b229e6a34919a8286f86cb87dd3574b7188f7d14f0da7091c97797f585dadd1a746ceccc0d913896b0c5b91136ad47a1e2b8546bcaa582e9962e33a297c73ed3156605db410ef93696f187c71d0c1af4c780462c434c8163d0e096dd11c24319090fc7df325b2826b3ed29aeea483056aa694f55ded408e2c0a1c62d64b5e35e924a1b1448d135805c5856e2b77fa6b58ede2a8bfc1e385d7422ae95a38b3d5b59103ffbb03db396b4cb47f5222ce0682b1da026354a2f499dc1b9393fffc20ca41754695e51281113bbe9b25821a207b0be33c48fad97c3d26b8ed118bdcfac1cb89f3e6d22595f56c0f6ce2393fada88638e7a10de420c663554b19484c731c840d9390872421ec9f1d2a7831a4f8389d0f87729e533a586f7019878ca0e451f0fe147e0efe14be5c7eeab725d79319d8973a4c2c26804036d73f8908e74dec03dc732e5463e7f23af5855694c8e5dc5bef2175fcdf2f858efde8d1565a981687f47c978be6c60508f086ae4799f8efcd4cb7dce85e89df014b5ae9dee230649829506070e9a775fc1a88c094723b05014a883b97649abe073d889cc14091fa5995e4388b3f23adf031daf5e7587f2e95859f3e568349f9f3ae5b17b719c3e3d981b8531ac880a13201e3df3b44d04abbaa85af198ace3976342d3891f2f8d185ca541efd571f611946fa0879a57c4cc243687af1a29dfa95395505ff867d119a296306751684fad87efbb404139795484d73588fe74d9024f60b40efa12b5aefe42b82e43603a0e94f04684fd82d7378a67ca0b4d80115a01ea80d57b4cb8957624612b0a2378d0d28eb141a812d04f3836d6a41fca56fd448223529109586909b884e658f3154c8d108af45e38e1443f8262e8f03d6be57394b2a40e1c218666117f6fea0a9807432ef653739da419d094806cd060c82476f84a1c29c0f534487dc6db3c7dccf4a4839306eb31ece7a35a1b5a75cb582a820f04a8e0ccb80c4c107e047e79fb3808b162a4dba910a4cdd8ba95e25bfffd4daf93958b524ad54af4a6a4519d78a69e7545f75cfad9b00e8038887f286e78b6e9e6680788683306014f37995c7913384c872d3fcd4d891a1833c4609fb9ed275f5756cde9f92be28b3755e9d0c95c1b9dbe40e1b8404e322a1ef9fc2566ff9bde8fc11acb5534bb130664fdc64450bbd39d4cf992095c2cd841360830631a5aaa5ae2e9ca8aa3089da1ef6963eff740ea92fbd2d48417d133ce5ff006ac0da7349a4c4eac32753ba396d3df1a581000fdf9a99a83f86260492c2fa5bf2840f3388823701edc96e51b87d48067663ef7f975bb8c2176012bbfb27d96814e298fa53ba993811629018d3a1d25b49306aaf1b8c23830f422225832910f62fe1e979af8561dc6c345eac8913671afae297efab25ec555d57d81c74e21043e6652e2d290641dc8313faf430193d778396d36628ab248d315b16fcbb1764dbb1e8cf7f2dec3b5b76e72f7bee9848936eac75bbab7277a2c4478597d5cbd88637fd72cec034ff743ca604f57040dff05daa73ad819dcef79b23ebefdd3361ed12103d27391cafd2a417ef5a568d4fb8fcbf17248b22bce652dc7c7f4f05e748218c7e66346cb95a9ff0e792d54e3a025046968e9a89d18c80ec6dd3aa48210320e0b4a9ab47a8f9660c3589a5c065278d8b18448476ac337ffb14e2f8d77d7819dcd14ec809ec1649c2f99441413329ac75a4f127c39c914bb47f84f8f32be5a0d76d45cac79e6238e8c7ba8c7a0b6dc69930cf791d749c5d1b7718f99ad9d7ddafce596b9220775347b307e1f7dcb2e9a35c32efe4a1db8187b288491798a2d967b273baafb83230f364edac2b31b238972cf525ee297dd9bde329cdb6cf6dce00ae6c101c3338985eb12f406177e216eeccfa6841431253423d739c89a6f0ba121fa470e7a04693680d010b6a605fa445029bd00c1fd2db95b7bad7852e8e8638af6e3aeb4ab4d2d8017c1521bf43bdac65a3b3588cde752d9dc381c49b0b6dccc7e436843f6cc0ccdf66430907a79472d21f782bce6ea051111f8f641e27fc4ee00bcfb8d7a187636fc6d7487472d01acefd70bf7fcfb679468edf44a2dce5bb5bb1aa2c320807945da49a95e2a5d40d8d887e9f434eae768cd3a7af3b22fc28f18b15dbb64198723fde2654f7cbefea4b2c698d42bf7af4604e6e6072dc46c7ccb84aaf7f09cca646c338543296e9b15590b2cf2370ac5466f13d6b123e18954a373974e359e3ab8468e829a5b0b681287510be267daf833b2de0e0b3fdf475e2a84444a349fbe4a962c7f1b8ae09884c731ad6b2035ddff8883379da0ea9fb8137208d73820fae79b0d21e5f3fbf56e1bae8810962437598d336f292a4c9bf34decb8431cea8747c36403d90c2ce5cf23a676c90d0aa26afd819a3c177bed99d71d32b0ebd70c53155567d3b1a5483fd5fcaad2da997ace4879d6e1fe2c103393502711565dcf8bb145e1d571342758d473839f8eaa7e7731a9cd54e7d115194b55b223e104dd80d449713ab1af21d5b4c515c94b5050f658381077fedf9bdb63767fb870fe5d7664d7a24d6cb37df2197bb00b07c0e515f441255ca0a071c087790af94b0546da5f7907445dae603054045320f4a493c816a77b9afab6118348a08a0ff5d8df68a794bdf8cb57af440c29187f406ae07473904510e8750153fdcb3022bf65d8de603c9f6c6128e8b6572184a85653b6ee64c49138b9b6fff869fecf000c11148cc9ff412531e5e1b7154e307b2f2db5e07e515d8734b2a1e8dc398eb7514ee59bd3ccde96e92a46728942b7709bb78d3ed7ab9ca5dec65c7406ee55991d73264aa7b8b54eadab9cfcb664b55862503d4316b871f4b5e2d183c7858bd45e685466e40db4308f072502e310003f798404edc0c914ef2c51015ee4a2b056ba89ed5e416473e724a47d2e4c5c751d3bca7e08fa2b17e08e6149844ef3f2d748b94243a270309e8c8bc45dd7c195323a3e95e4753470008ab0dfb6b7dacd2504c0d9d0f40eae23875e76ef5d8d9cb744e4204488305043283ab0be2529b39dda1d3fb9a9984f14d4ce1d79dc6d995d6b776baf82aafd2e633093c3719db29a20ce8f78d42ffec41317efca9b6a801fff1e1c60f3478852e4ca5bb53f6e731c191e5a6df9fe5064ec30399fac33e3f717c60663f747b4e6de382b83068a8cb2a0f3d44e1984d68c671e485cfe6577fe2f3473ebe8b39bbda9afeeebd01f3b6d5f8bf9fc0e662844dc82c8901b089650259c656172750032f9104efdd094db9ab642aab1bffde972edd900eaf3b6d2655342acb7ea831eb7fbf5331cee55aeb392c9fe158adc9524ba17cbee8321cff7fbc6efec5763ff575aded5aef8bbcf6352e4ddb4b17e0be5e13ae3bde75686aa7a05a8e22340ebb002001719dd9bcb28ea84b6fbcda2fd5ea1557b1b0a6f65e46642238093ece25994a130ff0033b0358241c92133c627a8788ec8e55ed621e8f5a415614805c0c68d3ced08c6d87add92a944dc03d8e7060c767700fd93a1ceb816fda53fa40cd941d796507fc5da834bac87d53a586f0b2cf996e02ae99167a9236bdfad751bfbf0fb828b2fdb86a4a31693260bf42ecfd96d14731cde072d18056e79931cdaa56bd859ac679c1e7476f82952ffd64cf594402f3f7fcbb4af4e0055f7e9d52d0d781f22fec4a2b3c25888b4cd4b4296aacd5fb7884153f79ae2da9aed4e90ccb1c7c4c2e76279a4d2d4d9576946709f12e4dd6e0d9d1f20dfd2db81ebdc2c323cb05b4a9bec0e016cbec27425ee173a2f49c539dc11da8a8fa14aac607e1d638a3ec3bdb9b20c9dfb6240495e48c68562869d017d5cdc7fb2c4f88bbfd726f961f23e7e5f221e8fc38cfb95e9104bae612504afda2066187bc335d7a08d3aa4f0728b13d56c1d214a621f81a0de7c8f5753fdb253d947890ea2edd4a51dc53eb8ae24e62d7337eefa8bb0677e8cf8cb374adf454768abc4dddb70cc91a6805ac01017c99a40e6b18b50731da3b96bd0383fe6af0df966edff24a0c9feec3a30e634198deb950d4ddf1be34d51845be9e392e0f953d87899cc3a1ffcebd26f7029cc3d2c1e594dc8425cf671235dbd4a4b17a123246313b1b7c554219be1a524ba3bb8d0212862ad429ebaa008c3e29d55b2a9dd4a13283a14b679fab2297fca47fdfe039aea034e419b8f56ab3fee6c3298e99c20f7e3a2952780f59eb1632c1ed72c63b59c545a98b606c8d77049d11793b0dff95807da964dad43c33ef7f244f43b029b29fab2a8cf6762383d86b958b09773b29ba01bf6e40439f31b7c1a157bb4c49503235a486c2e5d5d41589878a917a8c1e2e9b760fb752b8752e811f22e95557faf2a6f5c1c843bc54a42642ed4dc969d5f0fb2a4ca5755e3c3b7fce0092224e7564c98f2d9440c61c7defebe683e08fba6800ac3fe4c674a04692d214fe6c0acf2b73556f29c0ff5cd2b29d909fcc1d19471b2d9101e718d4ba0c142b35a95de1f45013d94c574a3ce1769718fd2be552f117b2e960c89d5f637846da593328b390689d2a41d868575310912e943886f8fe82d2369763c32de36d1f963e7ac4ef411d15983866cf2bebc14cd38f05b695efbb1003fdc577f4e6e3995b5556ae15337595420d38baaeab9601ac1176b8609898d394a7ca0332696eca83b6e747b17e09344bfd329693a545c66d6a26ced725c3a9582f48f31e520369000515d5feb519996672978cec2398781b1fbb5904bd190e16e874fbc19466311e746e44947f781d0cfe9f03df854231638f66ca2e466aed10252e135338540c1e9d907c0c104a147793024b4e604f560a1ce00b90bcdf0fa178a69d86da17ab097a696466ce2b8f065497364e52854c965015aa57766aedec17ecd4cbbd939beb2fd1b5274b386f3ee42db4c1b2bc509262575fe7a0dc2da2088bff7d06a602723af3a8c7806a93d7c3657098fdfe5bc076fc468001d8a3844ad439bc763eec2b9e122aa65abd58445eb07641dd2b9d5a059da12b48db109bd7918110e254262ed85f12db330e235756489600c6a67aa3f56fcbf8940dded78be2cb23c3891fc7e9ac0bed436b09997708acf66099f5f5929f4a33b84bb3005665a559d26f56c4ee2f9d062bbd38176c07f92a3580b52257cc81990d0b90a2851682e3f61e79762703ecec4b6837ba0dc88cc72544d60bd630bfcca545968385aafe68f8f1c3c64f7ca8e6368b7c702af1bd079323dee39cbacc3d41e7dd6340f5d71fad3a0c8ad8ef6717058a70428786cb1a6d55cd49c49c3453a3618e2222a2c6cb284a682f8cfe4cfaec678aad20c15006d8ac1925182cd86c5d91cbac9379824f397d6693ddf1367abc97c7de8e3097b101a66b522d00f5783f0170283df0686faa84ea035ec76e3ca0dfc46d5ed2f0f396ebe13b257852a42d85b65f6f46b633516a5e3f2110f7100f9d2cda014d644377171b50fb15f9acf61715555f600823adfd2b7bece359d3ce3cb63c22246f6526914201ca77205982da0c3470000000000e183010000000000df3e975b4cefdba65152b71a66c40d41de744705f3a8c4b75dad6b3a81cda322df563b4d8a47caf4912f536e4d131ddaabc4dd64c22e219dcb948807e45d0b0d39c620a11d6139f6564973794837a6f63c5abeb9480958c70798dfb6bdbd9f391d3e0200000000000000000000000000000000000000000000000000000000008ea8e07de66daa7ff73b5b271f6b49e4750d7c776cab8dab49d7b3353a33e494609e49a4cf08c90265bc097c6602337aeb1fec2bad8094559a65f456afc8f5dc170283df0686faa84ea035ec76e3ca0dfc46d5ed2f0f396ebe13b257852a42d85b65f6f46b633516a5e3f2110f7100f9d2cda014d644377171b50fb15f9acf61715555f600823adfd2b7bece359d3ce3cb63c22246f6526914201ca77205982dffffffffffffffffffffffff7fffff7fffffffffffffffffffffffffffffffffffffffffefffffdffffffffffffffffffffffffffffffffffdfffffffff7ffffb1afdd83343d209ded369536b05bebf22915382bf0d50dd6d21d5e7e146a59a794cb4c0f8d7b92164e2c31a3b64e1a4314c1019c1dbe142bfbb1ef70c8c0f39399cf627eef2954607b7716aabd92854c1a8a2dca680a711c2bd1bbfb7664e892f7c3470000000000" + const lightClientFinalityUpdateHex = "c0944b0000000000c9a90300000000006592efc86bcbec40089236714969b722d8d7959143352343a8ececf2249301076dc06b2dd0db664b650d2ddc4eb93e66e3e04cbe48792e8a548c140aa6ce9b48782f61f2f22379f48496fe46f985fc847ae1037431065c266e5e35bde9c2d96e80944b0000000000181d04000000000067c56b943c2f675d14eda966f49e0770c14b787c1a110d8afde5685802c2cd72c6a6ac803b52db1b54f546df2b6fcbc1a183fd1fe0bb140aa8ddfe7f1273866de52f0093645d011468823506cb04899f6b502f64cc4fdb7096c34193e61ab747a45c02000000000000000000000000000000000000000000000000000000000066643d84b06888be939498f352d0d74c2f8271578a34f315c6387dac995a84348d38d1863bc3e3009228f49eb91a69b72a8400f3ece13ed0ac56902f2ba8be8e408162be20793635f30d56017fb0e820ce9dbfe8b1d4532a339a33a6bddc7c99b8e16b33456f81799718d165f5bf75861f8df8f0e99201d9fbfb7c288597eac604cf9c45403e10e0e043ef0c5734eefa2a71014bce222cd8f036bfc8fe2927e4fffffff7ffbdfbf7ffff7ffdfffefffffffffffffffffffffff7ffbffefffffffffffffffffffffffbfffffffffffffffffffffffeffeffffffffff7fbffffff930b286947d7e3d1b7c117ca09e6566c592dda54f714c43f039e61b475403c88cbe889c1df0b06f182776e2613cf6bbf083a6f2424ea1696c6500942010970740a0a334c5b1887f0f25fd76a87d1b8e61da540c212e8acf612dc369bb572d5b8c1944b0000000000" const lightClientOptimisticUpdateHex = "b2944b000000000059d20600000000002fcb02f3de6192458bed0eff1992aaa98d5590a32a01fe96ef449e6bce803ab99e3b54169d85bb91798a1a621ef158766efccd61116697e98e06cc0535adbbd7c4826fcc8533a06f13848c375fab80695c4d99a5d1fb1f05118d095be39aaa4ffffffffffffffff7ffff7ffdeffffffffffffffffffffffffff7ffbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffffffa10f053de563e0e40da3df39038dcd57ecf0bd58c796fbb76d8f0d1fe8741aa18ce762a82eca147fcdb9b49af16a75470e9ec1474f3e978a20f0dfb97bbe8c9bade890d7d62fc3122129c6abb7b691e2787ebe5f51fae7060f4cb04d364d468ab3944b0000000000" @@ -21,6 +23,7 @@ const lightClientOptimisticUpdateHex = "b2944b000000000059d20600000000002fcb02f3 const bootstrapBytes* = byteutils.hexToSeqByte(bootstrapHex) lightClientUpdateBytes* = byteutils.hexToSeqByte(lightClientUpdateHex) + lightClientUpdateBytes1* = byteutils.hexToSeqByte(lightClientUpdate1Hex) lightClientFinalityUpdateBytes* = byteutils.hexToSeqByte(lightClientFinalityUpdateHex) lightClientOptimisticUpdateBytes* = diff --git a/fluffy/tests/beacon_light_client_tests/light_client_test_helpers.nim b/fluffy/tests/beacon_light_client_tests/light_client_test_helpers.nim index 1c7fda57f..31aac9b08 100644 --- a/fluffy/tests/beacon_light_client_tests/light_client_test_helpers.nim +++ b/fluffy/tests/beacon_light_client_tests/light_client_test_helpers.nim @@ -13,8 +13,11 @@ import beacon_chain/spec/forks, beacon_chain/spec/datatypes/altair, ../../network/wire/[portal_protocol, portal_stream, portal_protocol_config], - ../../network/beacon_light_client/[light_client_network, light_client_content], - ../../content_db, + ../../network/beacon_light_client/[ + light_client_network, + light_client_content, + light_client_db + ], ../test_helpers type LightClientNode* = ref object @@ -36,7 +39,7 @@ proc newLCNode*( forks: ForkDigests = getTestForkDigests()): LightClientNode = let node = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(port)) - db = ContentDB.new("", uint32.high, inMemory = true) + db = LightClientDb.new("", inMemory = true) streamManager = StreamManager.new(node) hn = LightClientNetwork.new(node, db, streamManager, forks) @@ -56,4 +59,4 @@ proc stop*(hn: LightClientNode) {.async.} = await hn.discoveryProtocol.closeWait() proc containsId*(hn: LightClientNode, contentId: ContentId): bool = - return hn.lightClientNetwork.contentDB.get(contentId).isSome() + return hn.lightClientNetwork.lightClientDb.get(contentId).isSome() diff --git a/fluffy/tests/beacon_light_client_tests/test_light_client_network.nim b/fluffy/tests/beacon_light_client_tests/test_light_client_network.nim index 3428928e5..5816d964c 100644 --- a/fluffy/tests/beacon_light_client_tests/test_light_client_network.nim +++ b/fluffy/tests/beacon_light_client_tests/test_light_client_network.nim @@ -6,7 +6,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - std/os, + std/[os, typetraits], testutils/unittests, chronos, eth/p2p/discoveryv5/protocol as discv5_protocol, eth/p2p/discoveryv5/routing_table, eth/common/eth_types_rlp, @@ -16,7 +16,6 @@ import ../../network/wire/[portal_protocol, portal_stream], ../../network/beacon_light_client/[light_client_network, light_client_content], ../../../nimbus/constants, - ../../content_db, "."/[light_client_test_data, light_client_test_helpers] procSuite "Light client Content Network": @@ -82,19 +81,18 @@ procSuite "Light client Content Network": let finalityUpdate = SSZ.decode(lightClientFinalityUpdateBytes, altair.LightClientFinalityUpdate) + finalHeaderSlot = finalityUpdate.finalized_header.slot + finaloptimisticHeaderSlot = finalityUpdate.attested_header.slot optimisticUpdate = SSZ.decode(lightClientOptimisticUpdateBytes, altair.LightClientOptimisticUpdate) + optimisticHeaderSlot = optimisticUpdate.attested_header.slot - finalityUpdateKey = ContentKey( - contentType: lightClientFinalityUpdate, - lightClientFinalityUpdateKey: LightClientFinalityUpdateKey() + finalityUpdateKey = finalityUpdateContentKey( + distinctBase(finalHeaderSlot), + distinctBase(finaloptimisticHeaderSlot) ) finalityKeyEnc = encode(finalityUpdateKey) finalityUdpateId = toContentId(finalityKeyEnc) - - optimistUpdateKey = ContentKey( - contentType: lightClientOptimisticUpdate, - lightClientOptimisticUpdateKey: LightClientOptimisticUpdateKey() - ) + optimistUpdateKey = optimisticUpdateContentKey(distinctBase(optimisticHeaderSlot)) optimisticKeyEnc = encode(optimistUpdateKey) optimisticUpdateId = toContentId(optimisticKeyEnc) @@ -114,8 +112,13 @@ procSuite "Light client Content Network": ) let - finalityResult = await lcNode1.lightClientNetwork.getLightClientFinalityUpdate() - optimisticResult = await lcNode1.lightClientNetwork.getLightClientOptimisticUpdate() + finalityResult = await lcNode1.lightClientNetwork.getLightClientFinalityUpdate( + distinctBase(finalHeaderSlot) - 1, + distinctBase(finaloptimisticHeaderSlot) - 1 + ) + optimisticResult = await lcNode1.lightClientNetwork.getLightClientOptimisticUpdate( + distinctBase(optimisticHeaderSlot) - 1 + ) check: finalityResult.isOk() @@ -125,3 +128,55 @@ procSuite "Light client Content Network": await lcNode1.stop() await lcNode2.stop() + + asyncTest "Get range of light client updates": + let + lcNode1 = newLCNode(rng, 20302) + lcNode2 = newLCNode(rng, 20303) + forks = getTestForkDigests() + + check: + lcNode1.portalProtocol().addNode(lcNode2.localNode()) == Added + lcNode2.portalProtocol().addNode(lcNode1.localNode()) == Added + + (await lcNode1.portalProtocol().ping(lcNode2.localNode())).isOk() + (await lcNode2.portalProtocol().ping(lcNode1.localNode())).isOk() + + let + update1 = SSZ.decode(lightClientUpdateBytes, altair.LightClientUpdate) + update2 = SSZ.decode(lightClientUpdateBytes1, altair.LightClientUpdate) + updates = @[update1, update2] + content = encodeLightClientUpdatesForked(forks.altair, updates) + startPeriod = update1.attested_header.slot.sync_committee_period + contentKey = ContentKey( + contentType: lightClientUpdate, + lightClientUpdateKey: LightClientUpdateKey( + startPeriod: startPeriod.uint64, + count: uint64(2) + ) + ) + contentKeyEncoded = encode(contentKey) + contentId = toContentId(contentKey) + + lcNode2.portalProtocol().storeContent( + contentKeyEncoded, + contentId, + content + ) + + let updatesResult = + await lcNode1.lightClientNetwork.getLightClientUpdatesByRange( + startPeriod.uint64, + uint64(2) + ) + + check: + updatesResult.isOk() + + let updatesFromPeer = updatesResult.get() + + check: + updatesFromPeer == updates + + await lcNode1.stop() + await lcNode2.stop()